mirror of
https://github.com/penpot/penpot.git
synced 2025-06-13 03:41:38 +02:00
♻️ Refactor shapes structure.
This commit is contained in:
parent
0cf0413ac4
commit
9d827d4b30
26 changed files with 983 additions and 1042 deletions
|
@ -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}]]))
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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"}]))
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}))]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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}]
|
||||
|
|
|
@ -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}])]))
|
||||
|
||||
|
|
|
@ -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}]]))
|
||||
|
149
frontend/src/uxbox/main/ui/viewer/shapes.cljs
Normal file
149
frontend/src/uxbox/main/ui/viewer/shapes.cljs
Normal 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}]]))
|
||||
|
|
@ -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]]
|
||||
|
|
77
frontend/src/uxbox/main/ui/workspace/shapes.cljs
Normal file
77
frontend/src/uxbox/main/ui/workspace/shapes.cljs
Normal 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))
|
|
@ -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]
|
|
@ -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)))
|
||||
|
109
frontend/src/uxbox/main/ui/workspace/shapes/frame.cljs
Normal file
109
frontend/src/uxbox/main/ui/workspace/shapes/frame.cljs
Normal 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}]])))))
|
||||
|
||||
|
74
frontend/src/uxbox/main/ui/workspace/shapes/group.cljs
Normal file
74
frontend/src/uxbox/main/ui/workspace/shapes/group.cljs
Normal 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?}]]))))
|
||||
|
51
frontend/src/uxbox/main/ui/workspace/shapes/path.cljs
Normal file
51
frontend/src/uxbox/main/ui/workspace/shapes/path.cljs
Normal 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}]]))
|
||||
|
311
frontend/src/uxbox/main/ui/workspace/shapes/text.cljs
Normal file
311
frontend/src/uxbox/main/ui/workspace/shapes/text.cljs
Normal 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..."}]]]))
|
|
@ -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]]
|
||||
|
|
34
frontend/src/uxbox/worker/thumbnails.cljs
Normal file
34
frontend/src/uxbox/worker/thumbnails.cljs
Normal 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)))
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue