♻️ Refactor shapes structure.

This commit is contained in:
Andrey Antukh 2020-05-05 18:58:36 +02:00 committed by Alonso Torres
parent 0cf0413ac4
commit 9d827d4b30
26 changed files with 983 additions and 1042 deletions

View file

@ -24,6 +24,7 @@
[uxbox.main.ui.shapes.group :as group]))
(def ^:private background-color "#E8E9EA") ;; $color-canvas
(mf/defc background
[]
[:rect
@ -41,40 +42,34 @@
{:width (if (mth/nan? width) 100 width)
:height (if (mth/nan? height) 100 height)}))
(declare shape-wrapper)
(declare shape-wrapper-factory)
(defn frame-wrapper
(defn frame-wrapper-factory
[objects]
(let [shape-wrapper (shape-wrapper-factory objects)
frame-shape (frame/frame-shape shape-wrapper)]
(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))
(let [childs (mapv #(get objects %) (:shapes shape))
shape (geom/transform-shape shape)]
[:& frame-shape {:shape shape :childs childs}])))
[:& frame-shape {:shape shape :childs childs}]))))
(defn group-wrapper
(defn group-wrapper-factory
[objects]
(let [shape-wrapper (shape-wrapper-factory objects)
group-shape (group/group-shape shape-wrapper)]
(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))]
(let [children (mapv #(get objects %) (:shapes shape))]
[:& group-shape {:frame frame
:shape shape
:children children}])))
:children children}]))))
(defn shape-wrapper
(defn shape-wrapper-factory
[objects]
(mf/fnc shape-wrapper
[{:keys [frame shape] :as props}]
(let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper objects))]
(let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))]
(when (and shape (not (:hidden shape)))
(let [shape (geom/transform-shape frame shape)
opts #js {:shape shape}]
@ -93,13 +88,24 @@
{::mf/wrap [mf/memo]}
[{:keys [data] :as props}]
(let [objects (:objects data)
dim (calculate-dimensions data)
root (get objects uuid/zero)
shapes (->> (:shapes root)
(map #(get objects %)))
dim (calculate-dimensions data)
frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper objects))
shape-wrapper (mf/use-memo (mf/deps objects) #(shape-wrapper objects))]
[:svg {:view-box (str "0 0 " (:width dim 0) " " (:height dim 0))
vbox (str "0 0 " (:width dim 0) " " (:height dim 0))
frame-wrapper
(mf/use-memo
(mf/deps objects)
#(frame-wrapper-factory objects))
shape-wrapper
(mf/use-memo
(mf/deps objects)
#(shape-wrapper-factory objects))]
[:svg {:view-box vbox
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
@ -119,19 +125,19 @@
(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)))
modifier-ids (concat [frame-id] (cp/get-children frame-id objects))
update-fn #(assoc-in %1 [%2 :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))]
vbox (str "0 0 " (:width frame 0)
" " (:height frame 0))
wrapper (mf/use-memo
(mf/deps objects)
#(frame-wrapper-factory objects))]
[:svg {:view-box vbox
:width width
@ -139,6 +145,5 @@
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
[:& frame-wrapper {:shape frame
:view-box vbox}]]))
[:& wrapper {:shape frame :view-box vbox}]]))

View file

@ -1,20 +0,0 @@
;; 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) 2016-2020 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.shapes
(:require
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.shapes.frame :as frame]
[uxbox.main.ui.shapes.shape :as shape]))
(def shape-wrapper shape/shape-wrapper)
(def frame-wrapper shape/frame-wrapper)

View file

@ -8,9 +8,7 @@
;; Copyright (c) 2016-2020 UXBOX Labs SL
(ns uxbox.main.ui.shapes.attrs
(:require
[cuerdas.core :as str]
[uxbox.util.interop :as itr]))
(:require [uxbox.util.object :as obj]))
(defn- stroke-type->dasharray
[style]
@ -28,7 +26,7 @@
:rx (:rx shape nil)
:ry (:ry shape nil)}]
(when (not= stroke-style :none)
(itr/obj-assign! attrs
(obj/merge! attrs
#js {:stroke (:stroke-color shape nil)
:strokeWidth (:stroke-width shape nil)
:strokeOpacity (:stroke-opacity shape nil)

View file

@ -2,65 +2,18 @@
;; 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-2019 Andrey Antukh <niwi@niwi.nz>
;; 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.shapes.circle
(:require
[rumext.alpha :as mf]
[uxbox.util.geom.shapes :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.ui.shapes.common :as common]
[uxbox.util.interop :as itr]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
;; --- Circle Wrapper for workspace
(declare circle-shape)
(mf/defc circle-wrapper
{::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)]
[:g.shape {:class (when selected? "selected")
:on-mouse-down on-mouse-down
: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 [x y width height]} (geom/selection-rect-shape 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)
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Circle Shape
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.object :as obj]))
(mf/defc circle-shape
{::mf/wrap-props false}
@ -75,13 +28,14 @@
ry (/ height 2)
props (-> (attrs/extract-style-attrs shape)
(itr/obj-assign!
(obj/merge!
#js {:cx cx
:cy cy
:rx rx
:ry ry
:transform transform
:id (str "shape-" id)}))]
[:& shape-custom-stroke {:shape shape
:base-props props
:elem-name "ellipse"}]))

View file

@ -8,11 +8,12 @@
(:require
[rumext.alpha :as mf]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.interop :as itr]))
[uxbox.util.object :as obj]))
; The SVG standard does not implement yet the 'stroke-alignment' attribute, to define the position
; of the stroke relative to the stroke axis (inner, center, outer). Here we implement a patch
; to be able to draw the stroke in the three cases. See discussion at:
; The SVG standard does not implement yet the 'stroke-alignment'
; attribute, to define the position of the stroke relative to the
; stroke axis (inner, center, outer). Here we implement a patch to be
; able to draw the stroke in the three cases. See discussion at:
; https://stackoverflow.com/questions/7241393/can-you-control-how-an-svgs-stroke-width-is-drawn
(mf/defc shape-custom-stroke
{::mf/wrap-props false}
@ -25,17 +26,17 @@
stroke-position (:stroke-alignment shape :center)]
(cond
; Center alignment (or no stroke): the default in SVG
;; Center alignment (or no stroke): the default in SVG
(or (= stroke-style :none) (= stroke-position :center))
[:> elem-name base-props]
; Inner alignment: display the shape with double width stroke, and clip the result
; with the original shape without stroke.
;; Inner alignment: display the shape with double width stroke,
;; and clip the result with the original shape without stroke.
(= stroke-position :inner)
(let [clip-id (str "clip-" id)
clip-props (-> (itr/obj-assign! #js {} base-props)
(itr/obj-assign! #js {:stroke nil
clip-props (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke nil
:strokeWidth nil
:strokeOpacity nil
:strokeDasharray nil
@ -43,48 +44,50 @@
:fillOpacity 1}))
stroke-width (.-strokeWidth base-props)
shape-props (-> (itr/obj-assign! #js {} base-props)
(itr/obj-assign! #js {:strokeWidth (* stroke-width 2)
shape-props (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:strokeWidth (* stroke-width 2)
:clipPath (str "url('#" clip-id "')")}))]
[:*
[:> "clipPath" #js {:id clip-id}
[:> elem-name clip-props]]
[:> elem-name shape-props]])
; Outer alingmnent: display the shape in two layers. One without stroke (only fill),
; and another one only with stroke at double width (transparent fill) and passed
; through a mask that shows the whole shape, but hides the original shape without stroke
;; Outer alingmnent: display the shape in two layers. One
;; without stroke (only fill), and another one only with stroke
;; at double width (transparent fill) and passed through a mask
;; that shows the whole shape, but hides the original shape
;; without stroke
(= stroke-position :outer)
(let [mask-id (str "mask-" id)
stroke-width (.-strokeWidth base-props)
mask-props1 (-> (itr/obj-assign! #js {} base-props)
(itr/obj-assign! #js {:stroke "white"
stroke-width (.-strokeWidth ^js base-props)
mask-props1 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke "white"
:strokeWidth (* stroke-width 2)
:strokeOpacity 1
:strokeDasharray nil
:fill "white"
:fillOpacity 1}))
mask-props2 (-> (itr/obj-assign! #js {} base-props)
(itr/obj-assign! #js {:stroke nil
mask-props2 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke nil
:strokeWidth nil
:strokeOpacity nil
:strokeDasharray nil
:fill "black"
:fillOpacity 1}))
shape-props1 (-> (itr/obj-assign! #js {} base-props)
(itr/obj-assign! #js {:stroke nil
shape-props1 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke nil
:strokeWidth nil
:strokeOpacity nil
:strokeDasharray nil}))
shape-props2 (-> (itr/obj-assign! #js {} base-props)
(itr/obj-assign! #js {:strokeWidth (* stroke-width 2)
shape-props2 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:strokeWidth (* stroke-width 2)
:fill "none"
:fillOpacity 0
:mask (str "url('#" mask-id "')")}))]
[:*
[:> "mask" #js {:id mask-id}
[:mask {:id mask-id}
[:> elem-name mask-props1]
[:> elem-name mask-props2]]
[:> elem-name shape-props1]

View file

@ -9,127 +9,14 @@
(ns uxbox.main.ui.shapes.frame
(:require
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.main.constants :as c]
[uxbox.main.data.workspace :as dw]
[uxbox.util.geom.shapes :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.ui.shapes.common :as common]
[uxbox.util.dom :as dom]
[uxbox.util.timers :as ts]
[uxbox.util.interop :as itr]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]))
(declare frame-wrapper)
[uxbox.util.geom.shapes :as geom]
[uxbox.util.object :as obj]))
(def frame-default-props {:fill-color "#ffffff"})
(declare frame-shape)
(declare translate-to-frame)
;; ---- Frame Wrapper for workspace
(defn frame-wrapper-memo-equals?
[np op]
(let [n-shape (aget np "shape")
o-shape (aget op "shape")
n-objs (aget np "objects")
o-objs (aget op "objects")
ids (:shapes n-shape)]
(and (identical? n-shape o-shape)
(loop [id (first ids)
ids (rest ids)]
(if (nil? id)
true
(if (identical? (get n-objs id)
(get o-objs id))
(recur (first ids) (rest ids))
false))))))
(defn frame-wrapper
[shape-wrapper]
(let [frame-shape (frame-shape shape-wrapper)]
(mf/fnc frame-wrapper
{::mf/wrap [#(mf/memo' % frame-wrapper-memo-equals?)
#(mf/deferred % ts/schedule-on-idle)]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
objects (unchecked-get props "objects")
selected-iref (mf/use-memo (mf/deps (:id shape))
#(refs/make-selected (:id shape)))
selected? (mf/deref selected-iref)
zoom (mf/deref refs/selected-zoom)
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))
{:keys [x y width height]} shape
inv-zoom (/ 1 zoom)
childs (mapv #(get objects %) (:shapes shape))
ds-modifier (get-in shape [:modifiers :displacement])
label-pos (cond-> (gpt/point x (- y 10))
(gmt/matrix? ds-modifier) (gpt/transform ds-modifier))
on-double-click
(mf/use-callback
(mf/deps (:id shape))
(fn [event]
(dom/prevent-default event)
(st/emit! dw/deselect-all
(dw/select-shape (:id shape)))))]
(when-not (:hidden shape)
[:g {:class (when selected? "selected")
:on-context-menu on-context-menu
:on-double-click on-double-click
:on-mouse-down on-mouse-down}
[:text {:x 0
:y 0
:width width
:height 20
:class "workspace-frame-label"
;; Ensure that the label has always the same font
;; size, regardless of zoom
;; https://css-tricks.com/transforms-on-svg-elements/
:transform (str
"scale(" inv-zoom ", " inv-zoom ") "
"translate(" (* zoom (:x label-pos)) ", "
(* zoom (:y label-pos)) ")")
;; User may also select the frame with single click in the label
:on-click on-double-click}
(:name shape)]
[:& frame-shape
{: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
@ -141,7 +28,7 @@
props (-> (merge frame-default-props shape)
(attrs/extract-style-attrs)
(itr/obj-assign!
(obj/merge!
#js {:x 0
:y 0
:id (str "shape-" id)

View file

@ -9,73 +9,10 @@
(ns uxbox.main.ui.shapes.group
(:require
[cuerdas.core :as str]
[rumext.alpha :as mf]
[uxbox.util.geom.shapes :as geom]
[uxbox.main.refs :as refs]
[uxbox.util.dom :as dom]
[uxbox.util.interop :as itr]
[uxbox.util.debug :refer [debug?]]
[uxbox.main.ui.shapes.common :as common]
[uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.data.workspace :as dw]
[uxbox.main.store :as st]
[uxbox.main.streams :as ms]
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]))
(defn- equals?
[np op]
(let [n-shape (unchecked-get np "shape")
o-shape (unchecked-get op "shape")
n-frame (unchecked-get np "frame")
o-frame (unchecked-get op "frame")]
(and (= n-frame o-frame)
(= n-shape o-shape))))
(declare translate-to-frame)
(declare group-shape)
(defn group-wrapper
[shape-wrapper]
(let [group-shape (group-shape shape-wrapper)]
(mf/fnc group-wrapper
{::mf/wrap [#(mf/memo' % equals?)]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
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))
children-ref (mf/use-memo (mf/deps shape)
#(refs/objects-by-id (:shapes shape)))
children (mf/deref children-ref)
is-child-selected-ref (mf/use-memo (mf/deps (:id shape))
#(refs/is-child-selected? (:id shape)))
is-child-selected? (mf/deref is-child-selected-ref)
on-double-click
(mf/use-callback
(mf/deps (:id shape))
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(st/emit! (dw/select-inside-group (:id shape) @ms/mouse-position))))]
[:g.shape
{:on-mouse-down on-mouse-down
:on-context-menu on-context-menu
:on-double-click on-double-click}
[:& group-shape
{:frame frame
:shape shape
:children children
:is-child-selected? is-child-selected?}]]))))
[uxbox.util.debug :refer [debug?]]
[uxbox.util.geom.shapes :as geom]))
(defn group-shape
[shape-wrapper]

View file

@ -2,93 +2,41 @@
;; 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>
;; 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.shapes.icon
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[uxbox.util.geom.shapes :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.ui.shapes.common :as common]
[uxbox.util.interop :as itr]))
;; --- Icon Wrapper for workspace
(declare icon-shape)
(mf/defc icon-wrapper
{::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]} (geom/selection-rect-shape 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)
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Icon Shape
[uxbox.util.object :as obj]))
(mf/defc icon-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
(+ x (/ width 2))
(+ y (/ height 2))))
view-box (apply str (interpose " " (:view-box metadata)))
transform (geom/transform-matrix shape)
vbox (apply str (interpose " " (:view-box metadata)))
props (-> (attrs/extract-style-attrs shape)
(itr/obj-assign!
(obj/merge!
#js {:x x
:y y
:transform transform
:id (str "shape-" id)
:width width
:height height
:viewBox view-box
:viewBox vbox
:preserveAspectRatio "none"
:dangerouslySetInnerHTML #js {:__html content}}))]
[:g {:transform transform}
[:> "svg" props]]))
;; --- Icon SVG
(mf/defc icon-svg
[{:keys [shape] :as props}]
(let [{:keys [content id metadata]} shape

View file

@ -2,70 +2,17 @@
;; 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-2019 Andrey Antukh <niwi@niwi.nz>
;; 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.shapes.image
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[uxbox.main.data.images :as udi]
[uxbox.util.geom.shapes :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.ui.shapes.common :as common]
[uxbox.util.interop :as itr]
[uxbox.util.geom.matrix :as gmt]))
;; --- Image Wrapper for workspace
(declare image-shape)
(mf/defc image-wrapper
{::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)
#(common/on-mouse-down % shape))
on-context-menu (mf/use-callback
(mf/deps shape)
#(common/on-context-menu % shape))]
[:g.shape {:class (when selected? "selected")
:on-mouse-down on-mouse-down
: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]} (geom/selection-rect-shape 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)
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Image Shape
[uxbox.util.object :as obj]))
(mf/defc image-shape
{::mf/wrap-props false}
@ -79,7 +26,7 @@
(:uri metadata))
props (-> (attrs/extract-style-attrs shape)
(itr/obj-assign!
(obj/merge!
#js {:x x
:y y
:transform transform

View file

@ -2,74 +2,19 @@
;; 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-2019 Andrey Antukh <niwi@niwi.nz>
;; 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.shapes.path
(:require
[cuerdas.core :as str :include-macros true]
[cuerdas.core :as str]
[rumext.alpha :as mf]
[uxbox.main.data.workspace :as dw]
[uxbox.util.geom.shapes :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.ui.shapes.common :as common]
[uxbox.util.interop :as itr]
[uxbox.util.geom.matrix :as gmt]
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
;; --- Path Wrapper for workspace
(declare path-shape)
(mf/defc path-wrapper
{::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)
#(common/on-mouse-down % shape))
on-context-menu (mf/use-callback
(mf/deps shape)
#(common/on-context-menu % shape))
on-double-click (mf/use-callback
(mf/deps shape)
(fn [event]
(when selected?
(st/emit! (dw/start-edition-mode (:id shape))))))]
[:g.shape {:on-double-click on-double-click
:on-mouse-down on-mouse-down
: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/selection-rect-shape 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)
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.object :as obj]))
;; --- Path Shape
@ -103,7 +48,7 @@
transform (geom/transform-matrix shape)
pdata (render-path shape)
props (-> (attrs/extract-style-attrs shape)
(itr/obj-assign!
(obj/merge!
#js {:transform transform
:id (str "shape-" id)
:d pdata}))]

View file

@ -2,66 +2,18 @@
;; 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>
;; 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.shapes.rect
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]
[uxbox.util.geom.shapes :as geom]
[uxbox.main.refs :as refs]
[uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.ui.shapes.common :as common]
[uxbox.util.interop :as itr]
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]))
(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-context-menu (mf/use-callback
(mf/deps shape)
#(common/on-context-menu % shape))]
[:g.shape {:on-mouse-down on-mouse-down
: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]} (geom/selection-rect-shape 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)
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
;; --- Rect Shape
[uxbox.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.object :as obj]))
(mf/defc rect-shape
{::mf/wrap-props false}
@ -70,7 +22,7 @@
{:keys [id x y width height]} shape
transform (geom/transform-matrix shape)
props (-> (attrs/extract-style-attrs shape)
(itr/obj-assign!
(obj/merge!
#js {:x x
:y y
:transform transform

View file

@ -7,61 +7,5 @@
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.shapes.shape
(:require
[rumext.alpha :as mf]
[uxbox.main.ui.shapes.circle :as circle]
[uxbox.main.ui.shapes.common :as common]
[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]
[uxbox.main.ui.shapes.frame :as frame]
[uxbox.main.ui.shapes.bounding-box :refer [bounding-box]]
[uxbox.util.geom.shapes :as gsh]
[uxbox.main.refs :as refs]))
(ns uxbox.main.ui.shapes.shape)
(defn- shape-wrapper-memo-equals?
[np op]
(let [n-shape (unchecked-get np "shape")
o-shape (unchecked-get op "shape")
n-frame (unchecked-get np "frame")
o-frame (unchecked-get op "frame")]
;; (prn "shape-wrapper-memo-equals?" (identical? n-frame o-frame))
(if (= (:type n-shape) :group)
false
(and (identical? n-shape o-shape)
(identical? n-frame o-frame)))))
(declare group-wrapper)
(declare frame-wrapper)
(mf/defc shape-wrapper
{::mf/wrap [#(mf/memo' % shape-wrapper-memo-equals?)]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
opts #js {:shape (->> shape (gsh/transform-shape frame))
:frame frame}]
(when (and shape (not (:hidden shape)))
[:*
(case (:type shape)
:group [:> group-wrapper opts]
:curve [:> path/path-wrapper opts]
:text [:> text/text-wrapper opts]
:icon [:> icon/icon-wrapper opts]
:rect [:> rect/rect-wrapper opts]
:path [:> path/path-wrapper opts]
:image [:> image/image-wrapper opts]
:circle [:> circle/circle-wrapper opts]
;; Only used when drawing a new frame.
:frame [:> frame-wrapper {:shape shape}]
nil)
[:& bounding-box {:shape shape :frame frame}]])))
(def group-wrapper (group/group-wrapper shape-wrapper))
(def frame-wrapper (frame/frame-wrapper shape-wrapper))

View file

@ -6,98 +6,12 @@
(ns uxbox.main.ui.shapes.text
(:require
[cuerdas.core :as str]
[goog.events :as events]
[goog.object :as gobj]
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.main.data.workspace :as dw]
[uxbox.main.data.workspace.texts :as dwt]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.shapes.common :as common]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.fonts :as fonts]
[uxbox.util.color :as color]
[uxbox.util.dom :as dom]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.object :as obj]
[uxbox.util.geom.matrix :as gmt]
["slate" :as slate]
["slate-react" :as rslate])
(:import goog.events.EventType
goog.events.KeyCodes))
;; --- Events
(defn handle-mouse-down
[event {:keys [id group] :as shape}]
(if (and (not (:blocked shape))
(or @refs/selected-drawing-tool
@refs/selected-edition))
(dom/stop-propagation event)
(common/on-mouse-down event shape)))
;; --- Text Wrapper for workspace
(declare text-shape-html)
(declare text-shape-edit)
(declare text-shape)
(mf/defc text-wrapper
{::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)
selected? (and (contains? selected id)
(= (count selected) 1))
on-mouse-down #(handle-mouse-down % shape)
on-context-menu #(common/on-context-menu % shape)
on-double-click
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(when selected?
(st/emit! (dw/start-edition-mode (:id shape)))))]
[:g.shape {:on-double-click on-double-click
:on-mouse-down on-mouse-down
:on-context-menu on-context-menu}
(if edition?
[:& text-shape-edit {:shape shape}]
[:& 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]} (geom/selection-rect-shape 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)
:fill "#31EFB8"
:stroke "#31EFB8"
:strokeWidth 1
:fillOpacity 0.2}])]]))
[uxbox.util.geom.matrix :as gmt]))
;; --- Text Editor Rendering
@ -169,174 +83,6 @@
base))
(mf/defc editor-root-node
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
type (obj/get data "type")
style (generate-root-styles data)
attrs (obj/set! attrs "style" style)
attrs (obj/set! attrs "className" type)]
[:> :div attrs childs]))
(mf/defc editor-paragraph-set-node
{::mf/wrap-props false}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
type (obj/get data "type")
style #js {:display "inline-block"
:width "100%"}
attrs (obj/set! attrs "style" style)
attrs (obj/set! attrs "className" type)]
[:> :div attrs childs]))
(mf/defc editor-paragraph-node
{::mf/wrap-props false}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
style (generate-paragraph-styles data)
attrs (obj/set! attrs "style" style)]
[:> :p attrs childs]))
(mf/defc editor-text-node
{::mf/wrap-props false}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "leaf")
style (generate-text-styles data)
attrs (obj/set! attrs "style" style)]
[:> :span attrs childs]))
(defn- render-element
[props]
(mf/html
(let [element (obj/get props "element")]
(case (obj/get element "type")
"root" [:> editor-root-node props]
"paragraph-set" [:> editor-paragraph-set-node props]
"paragraph" [:> editor-paragraph-node props]
nil))))
(defn- render-text
[props]
(mf/html
[:> editor-text-node props]))
;; --- Text Shape Edit
(defn- initial-text
[text]
(clj->js
[{:type "root"
:children [{:type "paragraph-set"
:children [{:type "paragraph"
:children [{:text (or text "")}]}]}]}]))
(defn- parse-content
[content]
(cond
(string? content) (initial-text content)
(map? content) (clj->js [content])
:else (initial-text "")))
(mf/defc text-shape-edit
{::mf/wrap [mf/memo]}
[{:keys [shape] :as props}]
(let [{:keys [id x y width height content]} shape
state (mf/use-state #(parse-content content))
editor (mf/use-memo #(dwt/create-editor))
self-ref (mf/use-ref)
selecting-ref (mf/use-ref)
on-close
(fn []
(st/emit! dw/clear-edition-mode))
on-click
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [sidebar (dom/get-element "settings-bar")
cpicker (dom/get-element-by-class "colorpicker-tooltip")
self (mf/ref-val self-ref)
target (dom/get-target event)
selecting? (mf/ref-val selecting-ref)]
(when-not (or (.contains sidebar target)
(.contains self target)
(and cpicker (.contains cpicker target)))
(if selecting?
(mf/set-ref-val! selecting-ref false)
(on-close)))))
on-mouse-down
(fn [event]
(mf/set-ref-val! selecting-ref true))
on-mouse-up
(fn [event]
(mf/set-ref-val! selecting-ref false))
on-keyup
(fn [event]
(when (= (.-keyCode event) 27) ; ESC
(on-close)))
on-mount
(fn []
(let [lkey1 (events/listen js/document EventType.CLICK on-click)
lkey2 (events/listen js/document EventType.KEYUP on-keyup)]
(st/emit! (dwt/assign-editor id editor))
#(do
(st/emit! (dwt/assign-editor id nil))
(events/unlistenByKey lkey1)
(events/unlistenByKey lkey2))))
on-focus
(fn [event]
(dwt/editor-select-all! editor))
on-change
(mf/use-callback
(fn [val]
(let [content (js->clj val :keywordize-keys true)
content (first content)]
(st/emit! (dw/update-shape id {:content content}))
(reset! state val))))]
(mf/use-effect on-mount)
[:foreignObject {:transform (geom/transform-matrix shape)
:x x :y y :width width :height height :ref self-ref}
[:> rslate/Slate {:editor editor
:value @state
:on-change on-change}
[:> rslate/Editable
{:auto-focus "true"
:spell-check "false"
:on-focus on-focus
:class "rich-text"
:render-element render-element
:render-leaf render-text
:on-mouse-up on-mouse-up
:on-mouse-down on-mouse-down
:on-blur (fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
;; WARN: monky patch
(obj/set! slate/Transforms "deselect" (constantly nil)))
:placeholder "Type some text here..."}]]]))
;; --- Text Shape Wrapper
(defn- render-text-node
([node] (render-text-node 0 node))
([index {:keys [type text children] :as node}]

View file

@ -25,7 +25,7 @@
[uxbox.main.ui.messages :refer [messages]]
[uxbox.main.ui.viewer.header :refer [header]]
[uxbox.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
[uxbox.main.ui.viewer.frame-viewer :refer [frame-viewer-svg]]
[uxbox.main.ui.viewer.shapes :refer [frame-svg]]
[uxbox.util.data :refer [classnames]]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :as i18n :refer [t tr]])
@ -48,7 +48,7 @@
[:span (t locale "viewer.frame-not-found")]]
:else
[:& frame-viewer-svg {:frame frame
[:& frame-svg {:frame frame
:zoom zoom
:objects objects}])]))

View file

@ -1,104 +0,0 @@
;; 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

@ -0,0 +1,149 @@
;; 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.viewer.shapes
"The main container for a frame in viewer mode"
(:require
[rumext.alpha :as mf]
[uxbox.common.pages :as cp]
[uxbox.main.data.viewer :as dv]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.shapes.circle :as circle]
[uxbox.main.ui.shapes.frame :as frame]
[uxbox.main.ui.shapes.group :as group]
[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.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]
[uxbox.util.geom.shapes :as geom]))
;; TODO: reivisit show interactions ref
;; TODO: revisit refs/frames
;; --- Interaction actions (in viewer mode)
(defn on-mouse-down
[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)))
(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
[{:keys [shape] :as props}]
(let [childs (mapv #(get objects %) (:shapes shape))
shape (geom/transform-shape shape)]
[:& 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
[{:keys [shape frame] :as props}]
(let [children (mapv #(get objects %) (:shapes shape))]
[:& group-shape {:frame frame
:shape shape
:children children}]))))
(defn generic-wrapper-factory
[component]
(mf/fnc generic-wrapper
{::mf/wrap-props false}
[props]
(let [{:keys [x y width height]
:as shape} (->> (unchecked-get props "shape")
(geom/selection-rect-shape))
show-interactions? (unchecked-get props "show-interactions?")
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}]
(when (and (:interactions shape) show-interactions?)
[:rect {:x (- x 1)
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
:fill "#31EFB8"
:stroke "#31EFB8"
: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 shape-wrapper-factory
[objects]
(mf/fnc shape-wrapper
[{:keys [frame shape] :as props}]
(let [group-wrapper (mf/use-memo (mf/deps objects)
#(group-wrapper-factory objects))]
(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-wrapper {:shape shape :frame frame}]
nil))))))
(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 #(assoc-in %1 [%2 :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))
wrapper (mf/use-memo
(mf/deps objects)
#(frame-wrapper-factory 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"}
[:& wrapper {:shape frame :view-box vbox}]]))

View file

@ -16,7 +16,7 @@
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.streams :as ms]
[uxbox.main.ui.shapes :as shapes]
[uxbox.main.ui.workspace.shapes :as shapes]
[uxbox.util.math :as mth]
[uxbox.util.dom :as dom]
[uxbox.util.data :refer [seek]]

View file

@ -0,0 +1,77 @@
;; 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
"A workspace specific shapes wrappers."
(:require
[rumext.alpha :as mf]
[uxbox.main.ui.shapes.rect :as rect]
[uxbox.main.ui.shapes.circle :as circle]
[uxbox.main.ui.shapes.icon :as icon]
[uxbox.main.ui.shapes.image :as image]
;; Shapes that has some peculiarities are defined in its own
;; namespace under uxbox.ui.workspace.shapes.* prefix, all the
;; others are defined using a generic wrapper implemented in
;; common.
[uxbox.main.ui.workspace.shapes.bbox :as bbox]
[uxbox.main.ui.workspace.shapes.common :as common]
[uxbox.main.ui.workspace.shapes.frame :as frame]
[uxbox.main.ui.workspace.shapes.group :as group]
[uxbox.main.ui.workspace.shapes.path :as path]
[uxbox.main.ui.workspace.shapes.text :as text]
[uxbox.util.geom.shapes :as geom]))
(declare group-wrapper)
(declare frame-wrapper)
(def circle-wrapper (common/generic-wrapper-factory circle/circle-shape))
(def icon-wrapper (common/generic-wrapper-factory icon/icon-shape))
(def image-wrapper (common/generic-wrapper-factory image/image-shape))
(def rect-wrapper (common/generic-wrapper-factory rect/rect-shape))
(defn- shape-wrapper-memo-equals?
[np op]
(let [n-shape (unchecked-get np "shape")
o-shape (unchecked-get op "shape")
n-frame (unchecked-get np "frame")
o-frame (unchecked-get op "frame")]
;; (prn "shape-wrapper-memo-equals?" (identical? n-frame o-frame))
(if (= (:type n-shape) :group)
false
(and (identical? n-shape o-shape)
(identical? n-frame o-frame)))))
(mf/defc shape-wrapper
{::mf/wrap [#(mf/memo' % shape-wrapper-memo-equals?)]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
opts #js {:shape (->> shape (geom/transform-shape frame))
:frame frame}]
(when (and shape (not (:hidden shape)))
[:*
(case (:type shape)
:curve [:> path/path-wrapper opts]
:path [:> path/path-wrapper opts]
:text [:> text/text-wrapper opts]
:group [:> group-wrapper opts]
:icon [:> icon-wrapper opts]
:rect [:> rect-wrapper opts]
:image [:> image-wrapper opts]
:circle [:> circle-wrapper opts]
;; Only used when drawing a new frame.
:frame [:> frame-wrapper {:shape shape}]
nil)
[:& bbox/bounding-box {:shape shape :frame frame}]])))
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
(def frame-wrapper (frame/frame-wrapper-factory shape-wrapper))

View file

@ -4,7 +4,7 @@
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.shapes.bounding-box
(ns uxbox.main.ui.workspace.shapes.bbox
(:require
[cuerdas.core :as str]
[rumext.alpha :as mf]

View file

@ -2,32 +2,24 @@
;; 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-2019 Andrey Antukh <niwi@niwi.nz>
;; 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
;; TODO: we need to consider moving this under uxbox.ui.workspace
;; namespace because this is logic only related to workspace
;; manipulation. Staying here causes a lot of confusion and finding
;; this code is also very difficult.
(ns uxbox.main.ui.shapes.common
(ns uxbox.main.ui.workspace.shapes.common
(:require
[potok.core :as ptk]
[beicon.core :as rx]
[uxbox.common.data :as d]
[uxbox.common.spec :as us]
[rumext.alpha :as mf]
[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]
[uxbox.main.streams :as uws]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.dom :as dom]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]
[uxbox.util.dom :as dom]))
[uxbox.util.geom.shapes :as geom]))
;; --- Shape Movement (by mouse)
(defn on-mouse-down
(defn- on-mouse-down
[event {:keys [id type] :as shape}]
(let [selected @refs/selected-shapes
selected? (contains? selected id)
@ -66,25 +58,27 @@
(dom/stop-propagation event)
(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}))))
(defn generic-wrapper-factory
[component]
(mf/fnc generic-wrapper
{::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
on-mouse-down (mf/use-callback
(mf/deps shape)
#(on-mouse-down % shape))
on-context-menu (mf/use-callback
(mf/deps shape)
#(on-context-menu % shape))]
[:g.shape {:on-mouse-down on-mouse-down
:on-context-menu on-context-menu}
[:& component {: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

@ -0,0 +1,109 @@
;; 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.frame
(:require
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.main.constants :as c]
[uxbox.main.data.workspace :as dw]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.workspace.shapes.common :as common]
[uxbox.main.ui.shapes.frame :as frame]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.dom :as dom]
[uxbox.main.streams :as ms]
[uxbox.util.timers :as ts]))
(defn- frame-wrapper-factory-equals?
[np op]
(let [n-shape (aget np "shape")
o-shape (aget op "shape")
n-objs (aget np "objects")
o-objs (aget op "objects")
ids (:shapes n-shape)]
(and (identical? n-shape o-shape)
(loop [id (first ids)
ids (rest ids)]
(if (nil? id)
true
(if (identical? (get n-objs id)
(get o-objs id))
(recur (first ids) (rest ids))
false))))))
(defn frame-wrapper-factory
[shape-wrapper]
(let [frame-shape (frame/frame-shape shape-wrapper)]
(mf/fnc frame-wrapper
{::mf/wrap [#(mf/memo' % frame-wrapper-factory-equals?)
#(mf/deferred % ts/schedule-on-idle)]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
objects (unchecked-get props "objects")
selected-iref (mf/use-memo (mf/deps (:id shape))
#(refs/make-selected (:id shape)))
selected? (mf/deref selected-iref)
zoom (mf/deref refs/selected-zoom)
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))
{:keys [x y width height]} shape
inv-zoom (/ 1 zoom)
childs (mapv #(get objects %) (:shapes shape))
ds-modifier (get-in shape [:modifiers :displacement])
label-pos (cond-> (gpt/point x (- y 10))
(gmt/matrix? ds-modifier) (gpt/transform ds-modifier))
on-double-click
(mf/use-callback
(mf/deps (:id shape))
(fn [event]
(dom/prevent-default event)
(st/emit! dw/deselect-all
(dw/select-shape (:id shape)))))]
(when-not (:hidden shape)
[:g {:class (when selected? "selected")
:on-context-menu on-context-menu
:on-double-click on-double-click
:on-mouse-down on-mouse-down}
[:text {:x 0
:y 0
:width width
:height 20
:class "workspace-frame-label"
;; Ensure that the label has always the same font
;; size, regardless of zoom
;; https://css-tricks.com/transforms-on-svg-elements/
:transform (str
"scale(" inv-zoom ", " inv-zoom ") "
"translate(" (* zoom (:x label-pos)) ", "
(* zoom (:y label-pos)) ")")
;; User may also select the frame with single click in the label
:on-click on-double-click}
(:name shape)]
[:& frame-shape
{:shape (geom/transform-shape shape)
:childs childs}]])))))

View file

@ -0,0 +1,74 @@
;; 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.group
(:require
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.main.constants :as c]
[uxbox.main.data.workspace :as dw]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.workspace.shapes.common :as common]
[uxbox.main.ui.shapes.group :as group]
[uxbox.util.dom :as dom]
[uxbox.main.streams :as ms]
[uxbox.util.timers :as ts]))
(defn- group-wrapper-factory-equals?
[np op]
(let [n-shape (unchecked-get np "shape")
o-shape (unchecked-get op "shape")
n-frame (unchecked-get np "frame")
o-frame (unchecked-get op "frame")]
(and (= n-frame o-frame)
(= n-shape o-shape))))
(defn group-wrapper-factory
[shape-wrapper]
(let [group-shape (group/group-shape shape-wrapper)]
(mf/fnc group-wrapper
{::mf/wrap [#(mf/memo' % group-wrapper-factory-equals?)]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
frame (unchecked-get props "frame")
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))
children-ref (mf/use-memo (mf/deps shape)
#(refs/objects-by-id (:shapes shape)))
children (mf/deref children-ref)
is-child-selected-ref (mf/use-memo (mf/deps (:id shape))
#(refs/is-child-selected? (:id shape)))
is-child-selected? (mf/deref is-child-selected-ref)
on-double-click
(mf/use-callback
(mf/deps (:id shape))
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(st/emit! (dw/select-inside-group (:id shape) @ms/mouse-position))))]
[:g.shape
{:on-mouse-down on-mouse-down
:on-context-menu on-context-menu
:on-double-click on-double-click}
[:& group-shape
{:frame frame
:shape shape
:children children
:is-child-selected? is-child-selected?}]]))))

View file

@ -0,0 +1,51 @@
;; 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.path
(:require
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.main.constants :as c]
[uxbox.main.data.workspace :as dw]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.shapes.path :as path]
[uxbox.main.ui.workspace.shapes.common :as common]
[uxbox.util.dom :as dom]
[uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.interop :as itr]
[uxbox.main.streams :as ms]
[uxbox.util.timers :as ts]))
(mf/defc path-wrapper
{::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)
#(common/on-mouse-down % shape))
on-context-menu (mf/use-callback
(mf/deps shape)
#(common/on-context-menu % shape))
on-double-click (mf/use-callback
(mf/deps shape)
(fn [event]
(when selected?
(st/emit! (dw/start-edition-mode (:id shape))))))]
[:g.shape {:on-double-click on-double-click
:on-mouse-down on-mouse-down
:on-context-menu on-context-menu}
[:& path/path-shape {:shape shape :background? true}]]))

View file

@ -0,0 +1,311 @@
;; 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-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.workspace.shapes.text
(:require
[cuerdas.core :as str]
[goog.events :as events]
[goog.object :as gobj]
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.main.data.workspace :as dw]
[uxbox.main.data.workspace.texts :as dwt]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.workspace.shapes.common :as common]
[uxbox.main.ui.shapes.text :as text]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.fonts :as fonts]
[uxbox.util.color :as color]
[uxbox.util.dom :as dom]
[uxbox.util.geom.shapes :as geom]
[uxbox.util.object :as obj]
[uxbox.util.geom.matrix :as gmt]
["slate" :as slate]
["slate-react" :as rslate])
(:import
goog.events.EventType
goog.events.KeyCodes))
;; --- Events
(defn handle-mouse-down
[event {:keys [id group] :as shape}]
(if (and (not (:blocked shape))
(or @refs/selected-drawing-tool
@refs/selected-edition))
(dom/stop-propagation event)
(common/on-mouse-down event shape)))
;; --- Text Wrapper for workspace
(declare text-shape-edit)
(declare text-shape)
(mf/defc text-wrapper
{::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)
selected? (and (contains? selected id)
(= (count selected) 1))
on-mouse-down #(handle-mouse-down % shape)
on-context-menu #(common/on-context-menu % shape)
on-double-click
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(when selected?
(st/emit! (dw/start-edition-mode (:id shape)))))]
[:g.shape {:on-double-click on-double-click
:on-mouse-down on-mouse-down
:on-context-menu on-context-menu}
(if edition?
[:& text-shape-edit {:shape shape}]
[:& text/text-shape {:shape shape
:selected? selected?}])]))
;; --- Text Editor Rendering
(defn- generate-root-styles
[data]
(let [valign (obj/get data "vertical-align")
base #js {:height "100%"
:width "100%"
:display "flex"}]
(cond-> base
(= valign "top") (obj/set! "alignItems" "flex-start")
(= valign "center") (obj/set! "alignItems" "center")
(= valign "bottom") (obj/set! "alignItems" "flex-end"))))
(defn- generate-paragraph-styles
[data]
(let [base #js {:fontSize "14px"
:margin "inherit"
:lineHeight "1.2"}
lh (obj/get data "line-height")
ta (obj/get data "text-align")]
(cond-> base
ta (obj/set! "textAlign" ta)
lh (obj/set! "lineHeight" lh))))
(defn- generate-text-styles
[data]
(let [letter-spacing (obj/get data "letter-spacing")
text-decoration (obj/get data "text-decoration")
text-transform (obj/get data "text-transform")
font-id (obj/get data "font-id")
font-variant-id (obj/get data "font-variant-id")
font-family (obj/get data "font-family")
font-size (obj/get data "font-size")
fill (obj/get data "fill")
opacity (obj/get data "opacity")
fontsdb (deref fonts/fontsdb)
base #js {:textDecoration text-decoration
:color fill
:opacity opacity
:textTransform text-transform}]
(when (and (string? letter-spacing)
(pos? (alength letter-spacing)))
(obj/set! base "letterSpacing" (str letter-spacing "px")))
(when (and (string? font-size)
(pos? (alength font-size)))
(obj/set! base "fontSize" (str font-size "px")))
(when (and (string? font-id)
(pos? (alength font-id)))
(let [font (get fontsdb font-id)]
(fonts/ensure-loaded! font-id)
(let [font-family (or (:family font)
(obj/get data "fontFamily"))
font-variant (d/seek #(= font-variant-id (:id %))
(:variants font))
font-style (or (:style font-variant)
(obj/get data "fontStyle"))
font-weight (or (:weight font-variant)
(obj/get data "fontWeight"))]
(obj/set! base "fontFamily" font-family)
(obj/set! base "fontStyle" font-style)
(obj/set! base "fontWeight" font-weight))))
base))
(mf/defc editor-root-node
{::mf/wrap-props false
::mf/wrap [mf/memo]}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
type (obj/get data "type")
style (generate-root-styles data)
attrs (obj/set! attrs "style" style)
attrs (obj/set! attrs "className" type)]
[:> :div attrs childs]))
(mf/defc editor-paragraph-set-node
{::mf/wrap-props false}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
type (obj/get data "type")
style #js {:display "inline-block"
:width "100%"}
attrs (obj/set! attrs "style" style)
attrs (obj/set! attrs "className" type)]
[:> :div attrs childs]))
(mf/defc editor-paragraph-node
{::mf/wrap-props false}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "element")
style (generate-paragraph-styles data)
attrs (obj/set! attrs "style" style)]
[:> :p attrs childs]))
(mf/defc editor-text-node
{::mf/wrap-props false}
[props]
(let [attrs (obj/get props "attributes")
childs (obj/get props "children")
data (obj/get props "leaf")
style (generate-text-styles data)
attrs (obj/set! attrs "style" style)]
[:> :span attrs childs]))
(defn- render-element
[props]
(mf/html
(let [element (obj/get props "element")]
(case (obj/get element "type")
"root" [:> editor-root-node props]
"paragraph-set" [:> editor-paragraph-set-node props]
"paragraph" [:> editor-paragraph-node props]
nil))))
(defn- render-text
[props]
(mf/html
[:> editor-text-node props]))
;; --- Text Shape Edit
(defn- initial-text
[text]
(clj->js
[{:type "root"
:children [{:type "paragraph-set"
:children [{:type "paragraph"
:children [{:text (or text "")}]}]}]}]))
(defn- parse-content
[content]
(cond
(string? content) (initial-text content)
(map? content) (clj->js [content])
:else (initial-text "")))
(mf/defc text-shape-edit
{::mf/wrap [mf/memo]}
[{:keys [shape] :as props}]
(let [{:keys [id x y width height content]} shape
state (mf/use-state #(parse-content content))
editor (mf/use-memo #(dwt/create-editor))
self-ref (mf/use-ref)
selecting-ref (mf/use-ref)
on-close
(fn []
(st/emit! dw/clear-edition-mode))
on-click
(fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [sidebar (dom/get-element "settings-bar")
cpicker (dom/get-element-by-class "colorpicker-tooltip")
self (mf/ref-val self-ref)
target (dom/get-target event)
selecting? (mf/ref-val selecting-ref)]
(when-not (or (.contains sidebar target)
(.contains self target)
(and cpicker (.contains cpicker target)))
(if selecting?
(mf/set-ref-val! selecting-ref false)
(on-close)))))
on-mouse-down
(fn [event]
(mf/set-ref-val! selecting-ref true))
on-mouse-up
(fn [event]
(mf/set-ref-val! selecting-ref false))
on-keyup
(fn [event]
(when (= (.-keyCode event) 27) ; ESC
(on-close)))
on-mount
(fn []
(let [lkey1 (events/listen js/document EventType.CLICK on-click)
lkey2 (events/listen js/document EventType.KEYUP on-keyup)]
(st/emit! (dwt/assign-editor id editor))
#(do
(st/emit! (dwt/assign-editor id nil))
(events/unlistenByKey lkey1)
(events/unlistenByKey lkey2))))
on-focus
(fn [event]
(dwt/editor-select-all! editor))
on-change
(mf/use-callback
(fn [val]
(let [content (js->clj val :keywordize-keys true)
content (first content)]
(st/emit! (dw/update-shape id {:content content}))
(reset! state val))))]
(mf/use-effect on-mount)
[:foreignObject {:transform (geom/transform-matrix shape)
:x x :y y :width width :height height :ref self-ref}
[:> rslate/Slate {:editor editor
:value @state
:on-change on-change}
[:> rslate/Editable
{:auto-focus "true"
:spell-check "false"
:on-focus on-focus
:class "rich-text"
:render-element render-element
:render-leaf render-text
:on-mouse-up on-mouse-up
:on-mouse-down on-mouse-down
:on-blur (fn [event]
(dom/prevent-default event)
(dom/stop-propagation event)
;; WARN: monky patch
(obj/set! slate/Transforms "deselect" (constantly nil)))
:placeholder "Type some text here..."}]]]))

View file

@ -23,7 +23,7 @@
[uxbox.main.streams :as ms]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.hooks :as hooks]
[uxbox.main.ui.shapes :refer [shape-wrapper frame-wrapper]]
[uxbox.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]]
[uxbox.main.ui.workspace.drawarea :refer [draw-area start-drawing]]
[uxbox.main.ui.workspace.grid :refer [grid]]
[uxbox.main.ui.workspace.ruler :refer [ruler]]

View file

@ -0,0 +1,34 @@
;; 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.worker.thumbnails
(:require
[rumext.alpha :as mf]
[cljs.spec.alpha :as s]
[uxbox.common.exceptions :as ex]
[uxbox.common.spec :as us]
[uxbox.main.exports :as exports]
[uxbox.worker.impl :as impl]
["react-dom/server" :as rds]))
(mf/defc foobar
[{:keys [name]}]
[:span name])
(defmethod impl/handler :echo
[message]
{:result (rds/renderToString (mf/element foobar {:name "foobar"}))})
(defmethod impl/handler :thumbnails/generate
[{:keys [data] :as message}]
(let [elem (mf/element exports/page-svg #js {:data data
:width "290"
:height "150"})]
(rds/renderToStaticMarkup elem)))