mirror of
https://github.com/penpot/penpot.git
synced 2025-02-08 08:09:14 -05:00
🎉 Adds groups infrastructure
This commit is contained in:
parent
9d0450a4b5
commit
e73350e2ba
14 changed files with 423 additions and 209 deletions
|
@ -56,7 +56,7 @@
|
|||
(s/def ::stroke-style #{:none :solid :dotted :dashed :mixed})
|
||||
(s/def ::stroke-width number?)
|
||||
(s/def ::text-align #{"left" "right" "center" "justify"})
|
||||
(s/def ::type #{:rect :path :circle :image :text :canvas :curve :icon :frame})
|
||||
(s/def ::type #{:rect :path :circle :image :text :canvas :curve :icon :frame :group})
|
||||
(s/def ::x number?)
|
||||
(s/def ::y number?)
|
||||
(s/def ::cx number?)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
funcool/lentes {:mvn/version "1.4.0-SNAPSHOT"}
|
||||
funcool/potok {:mvn/version "2.8.0-SNAPSHOT"}
|
||||
funcool/promesa {:mvn/version "5.1.0"}
|
||||
funcool/rumext {:mvn/version "2020.03.24-1"
|
||||
funcool/rumext {:mvn/version "2020.04.01-3"
|
||||
:exclusions [cljsjs/react
|
||||
cljsjs/react-dom]}
|
||||
}
|
||||
|
|
20
frontend/src/uxbox/main/data/helpers.cljs
Normal file
20
frontend/src/uxbox/main/data/helpers.cljs
Normal file
|
@ -0,0 +1,20 @@
|
|||
;; 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.data.helpers)
|
||||
|
||||
(defn get-children
|
||||
"Retrieve all children ids recursively for a given shape"
|
||||
[shape-id objects]
|
||||
(let [shapes (get-in objects [shape-id :shapes])]
|
||||
(if shapes
|
||||
(concat
|
||||
shapes
|
||||
(mapcat get-children shapes))
|
||||
[])))
|
|
@ -23,6 +23,7 @@
|
|||
[uxbox.main.constants :as c]
|
||||
[uxbox.main.data.icons :as udi]
|
||||
[uxbox.main.data.dashboard :as dd]
|
||||
[uxbox.main.data.helpers :as helpers]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.repo :as rp]
|
||||
|
@ -1512,13 +1513,32 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (::page-id state)
|
||||
rfn (fn [state id]
|
||||
(update-in state [:workspace-data page-id :objects id]
|
||||
(fn [shape]
|
||||
(let [mfr (:resize-modifier shape (gmt/matrix))]
|
||||
(-> (dissoc shape :resize-modifier)
|
||||
(geom/transform mfr))))))]
|
||||
(reduce rfn state ids)))
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
|
||||
;; Updates the displacement data for a single shape
|
||||
materialize-shape
|
||||
(fn [state id mtx]
|
||||
(update-in
|
||||
state
|
||||
[:workspace-data page-id :objects id]
|
||||
#(-> %
|
||||
(dissoc :resize-modifier)
|
||||
(geom/transform mtx))))
|
||||
|
||||
;; Applies materialize-shape over shape children
|
||||
materialize-children
|
||||
(fn [state id mtx]
|
||||
(reduce #(materialize-shape %1 %2 mtx) state (helpers/get-children id objects)))
|
||||
|
||||
;; For each shape makes permanent the displacemnt
|
||||
update-shapes
|
||||
(fn [state id]
|
||||
(let [shape (get objects id)
|
||||
mtx (:resize-modifier shape (gmt/matrix))]
|
||||
(-> state
|
||||
(materialize-shape id mtx)
|
||||
(materialize-children id mtx))))]
|
||||
(reduce update-shapes state ids)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -1552,13 +1572,33 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [page-id (::page-id state)
|
||||
rfn (fn [state id]
|
||||
(update-in state [:workspace-data page-id :objects id]
|
||||
(fn [shape]
|
||||
(let [mtx (:displacement-modifier shape (gmt/matrix))]
|
||||
(-> (dissoc shape :displacement-modifier)
|
||||
(geom/transform mtx))))))]
|
||||
(reduce rfn state ids)))
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
|
||||
;; Updates the displacement data for a single shape
|
||||
materialize-shape
|
||||
(fn [state id mtx]
|
||||
(update-in
|
||||
state
|
||||
[:workspace-data page-id :objects id]
|
||||
#(-> %
|
||||
(dissoc :displacement-modifier)
|
||||
(geom/transform mtx))))
|
||||
|
||||
;; Applies materialize-shape over shape children
|
||||
materialize-children
|
||||
(fn [state id mtx]
|
||||
(reduce #(materialize-shape %1 %2 mtx) state (helpers/get-children id objects)))
|
||||
|
||||
;; For each shape makes permanent the resize
|
||||
update-shapes
|
||||
(fn [state id]
|
||||
(let [shape (get objects id)
|
||||
mtx (:displacement-modifier shape (gmt/matrix))]
|
||||
(-> state
|
||||
(materialize-shape id mtx)
|
||||
(materialize-children id mtx))))]
|
||||
|
||||
(reduce update-shapes state ids)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -2123,6 +2163,73 @@
|
|||
(assoc-in state [:projects (:project-id page) :pages] pages)))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; GROUPS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn get-parent [object-id objects]
|
||||
(let [include-object
|
||||
(fn [object]
|
||||
(and (:shapes object)
|
||||
(some #(= object-id %) (:shapes object))))]
|
||||
(first (filter include-object objects))))
|
||||
|
||||
(defn group-shape [id frame-id selected selection-rect]
|
||||
{:id id
|
||||
:type :group
|
||||
:name (name (gensym "Group-"))
|
||||
:shapes (vec selected)
|
||||
:frame-id frame-id
|
||||
:x (:x selection-rect)
|
||||
:y (:y selection-rect)
|
||||
:width (:width selection-rect)
|
||||
:height (:height selection-rect)})
|
||||
|
||||
(defn create-group []
|
||||
(let [id (uuid/next)]
|
||||
(ptk/reify ::create-group
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [selected (get-in state [:workspace-local :selected])]
|
||||
(if (and selected (-> selected count (> 1)))
|
||||
(let [page-id (get-in state [:workspace-page :id])
|
||||
objects (get-in state [:workspace-data page-id :objects])
|
||||
parent (get-parent (first selected) (vals objects))
|
||||
selected-objects (map (partial get objects) selected)
|
||||
selection-rect (geom/selection-rect selected-objects)
|
||||
new-shape (group-shape id (-> selected-objects first :frame-id) selected selection-rect)
|
||||
objects-removed (-> objects
|
||||
#_(apply dissoc $ selected)
|
||||
(assoc (:id new-shape) new-shape)
|
||||
(update-in [(:id parent) :shapes]
|
||||
(fn [shapes] (filter #(not (selected %)) shapes)))
|
||||
(update-in [(:id parent) :shapes] conj (:id new-shape)))]
|
||||
(-> state
|
||||
(assoc-in [:workspace-data page-id :objects] objects-removed )
|
||||
(assoc-in [:workspace-local :selected] #{(:id new-shape)})))
|
||||
state)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [obj (get-in state [:workspace-data (::page-id state) :objects id])
|
||||
frame-id (:frame-id obj)
|
||||
frame (get-in state [:workspace-data (::page-id state) :objects frame-id])]
|
||||
(rx/of (commit-changes [{:type :add-obj
|
||||
:id id
|
||||
:frame-id (:frame-id obj)
|
||||
:obj obj}
|
||||
{:type :mod-obj
|
||||
:id frame-id
|
||||
:operations [{:type :set
|
||||
:attr :shapes
|
||||
:val (:shapes frame)}]}]
|
||||
[{:type :del-obj :id id}
|
||||
{:type :mod-obj
|
||||
:id frame-id
|
||||
:operations [{:type :set
|
||||
:attr :shapes
|
||||
:val (into (:shapes frame) (:shapes obj))}]}])))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Shortcuts
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
@ -2143,6 +2250,8 @@
|
|||
"ctrl+t" #(rx/of (select-for-drawing :text))
|
||||
"ctrl+c" #(rx/of copy-selected)
|
||||
"ctrl+v" #(rx/of paste)
|
||||
"ctrl+g" #(rx/of (create-group))
|
||||
;; "ctrl+shift+g" #(rx/of remove-group)
|
||||
"esc" #(rx/of :interrupt deselect-all)
|
||||
"delete" #(rx/of delete-selected)
|
||||
"ctrl+up" #(rx/of (vertical-order-selected :up))
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
[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.text :as text]
|
||||
[uxbox.main.ui.shapes.group :as group]))
|
||||
|
||||
(mf/defc background
|
||||
[]
|
||||
|
@ -37,13 +38,21 @@
|
|||
{:width (if (mth/nan? width) 100 width)
|
||||
:height (if (mth/nan? height) 100 height)}))
|
||||
|
||||
(declare frame-shape)
|
||||
(declare group-shape)
|
||||
|
||||
(mf/defc frame-wrapper
|
||||
[{:keys [shape objects] :as props}]
|
||||
(let [childs (mapv #(get objects %) (:shapes shape))]
|
||||
[:& frame/frame-shape {:shape shape :childs childs}]))
|
||||
[:& frame-shape {:shape shape :childs childs}]))
|
||||
|
||||
(mf/defc group-wrapper
|
||||
[{:keys [shape-wrapper shape objects] :as props}]
|
||||
(let [children (mapv #(get objects %) (:shapes shape))]
|
||||
[:& group-shape {:shape shape :children children}]))
|
||||
|
||||
(mf/defc shape-wrapper
|
||||
[{:keys [shape] :as props}]
|
||||
[{:keys [shape objects] :as props}]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(case (:type shape)
|
||||
:frame [:& rect/rect-shape {:shape shape}]
|
||||
|
@ -53,7 +62,12 @@
|
|||
:rect [:& rect/rect-shape {:shape shape}]
|
||||
:path [:& path/path-shape {:shape shape}]
|
||||
:image [:& image/image-shape {:shape shape}]
|
||||
:circle [:& circle/circle-shape {:shape shape}])))
|
||||
:circle [:& circle/circle-shape {:shape shape}]
|
||||
:group [:& (group/group-shape shape-wrapper) {:shape shape :shape-wrapper shape-wrapper :objects objects}]
|
||||
nil)))
|
||||
|
||||
(def group-shape (group/group-shape shape-wrapper))
|
||||
(def frame-shape (frame/frame-shape shape-wrapper))
|
||||
|
||||
(mf/defc page-svg
|
||||
[{:keys [data] :as props}]
|
||||
|
@ -73,7 +87,8 @@
|
|||
:key (:id item)
|
||||
:objects objects}]
|
||||
[:& shape-wrapper {:shape item
|
||||
:key (:id item)}]))]))
|
||||
:key (:id item)
|
||||
:objects objects}]))]))
|
||||
|
||||
;; (defn- render-html
|
||||
;; [component]
|
||||
|
|
|
@ -32,7 +32,8 @@
|
|||
:curve (move-path shape dpoint)
|
||||
:path (move-path shape dpoint)
|
||||
:circle (move-circle shape dpoint)
|
||||
nil))
|
||||
:group (move-rect shape dpoint)
|
||||
shape))
|
||||
|
||||
(defn- move-rect
|
||||
"A specialized function for relative movement
|
||||
|
@ -73,7 +74,9 @@
|
|||
:frame (absolute-move-rect shape position)
|
||||
:image (absolute-move-rect shape position)
|
||||
:rect (absolute-move-rect shape position)
|
||||
:circle (absolute-move-circle shape position)))
|
||||
:group (absolute-move-rect shape position)
|
||||
:circle (absolute-move-circle shape position)
|
||||
shape))
|
||||
|
||||
(defn- absolute-move-rect
|
||||
"A specialized function for absolute moviment
|
||||
|
@ -493,6 +496,7 @@
|
|||
[objects shape]
|
||||
(case (:type shape)
|
||||
:rect (resolve-rect-shape objects shape)
|
||||
:group (resolve-rect-shape objects shape)
|
||||
:frame (resolve-rect-shape objects shape)))
|
||||
|
||||
(defn- resolve-rect-shape
|
||||
|
@ -511,15 +515,19 @@
|
|||
(defn transform
|
||||
"Apply the matrix transformation to shape."
|
||||
[{:keys [type] :as shape} xfmt]
|
||||
(case type
|
||||
:frame (transform-rect shape xfmt)
|
||||
:rect (transform-rect shape xfmt)
|
||||
:icon (transform-rect shape xfmt)
|
||||
:text (transform-rect shape xfmt)
|
||||
:image (transform-rect shape xfmt)
|
||||
:path (transform-path shape xfmt)
|
||||
:curve (transform-path shape xfmt)
|
||||
:circle (transform-circle shape xfmt)))
|
||||
(if (gmt/matrix? xfmt)
|
||||
(case type
|
||||
:frame (transform-rect shape xfmt)
|
||||
:group (transform-rect shape xfmt)
|
||||
:rect (transform-rect shape xfmt)
|
||||
:icon (transform-rect shape xfmt)
|
||||
:text (transform-rect shape xfmt)
|
||||
:image (transform-rect shape xfmt)
|
||||
:path (transform-path shape xfmt)
|
||||
:curve (transform-path shape xfmt)
|
||||
:circle (transform-circle shape xfmt)
|
||||
shape)
|
||||
shape))
|
||||
|
||||
(defn- transform-rect
|
||||
[{:keys [x y width height] :as shape} mx]
|
||||
|
|
|
@ -47,9 +47,13 @@
|
|||
(l/derive st/state)))
|
||||
|
||||
(def workspace-data
|
||||
(-> (l/lens (fn [state]
|
||||
(let [page-id (get-in state [:workspace-page :id])]
|
||||
(get-in state [:workspace-data page-id]))))
|
||||
(-> (l/lens #(let [page-id (get-in % [:workspace-page :id])]
|
||||
(get-in % [:workspace-data page-id])))
|
||||
(l/derive st/state)))
|
||||
|
||||
(def objects
|
||||
(-> (l/lens #(let [page-id (get-in % [:workspace-page :id])]
|
||||
(get-in % [:workspace-data page-id :objects])))
|
||||
(l/derive st/state)))
|
||||
|
||||
(def selected-shapes
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
(rx/filter (fn [s] (deref *debug*)) $)
|
||||
(rx/subscribe $ (fn [event]
|
||||
(println "[stream]: " (repr-event event)))))))
|
||||
|
||||
(def auth-ref
|
||||
(-> (l/key :auth)
|
||||
(l/derive state)))
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
(:require
|
||||
[beicon.core :as rx]
|
||||
[goog.object :as gobj]
|
||||
[rumext.alpha :as mf]))
|
||||
[rumext.alpha :as mf]
|
||||
[cljsjs.react]))
|
||||
|
||||
(defn wrap-catch
|
||||
[component {:keys [fallback on-error]}]
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
[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.frame :as frame]
|
||||
[uxbox.main.ui.shapes.shape :as shape]))
|
||||
|
||||
(def shape-wrapper frame/shape-wrapper)
|
||||
(def frame-wrapper frame/frame-wrapper)
|
||||
(def shape-wrapper shape/shape-wrapper)
|
||||
(def frame-wrapper (frame/frame-wrapper shape-wrapper))
|
||||
|
|
|
@ -18,13 +18,7 @@
|
|||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[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.util.dom :as dom]
|
||||
[uxbox.util.interop :as itr]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
|
@ -32,39 +26,14 @@
|
|||
|
||||
(declare frame-wrapper)
|
||||
|
||||
|
||||
(defn wrap-memo-shape
|
||||
([component]
|
||||
(js/React.memo
|
||||
component
|
||||
(fn [np op]
|
||||
(let [n-shape (unchecked-get np "shape")
|
||||
o-shape (unchecked-get op "shape")]
|
||||
(= n-shape o-shape))))))
|
||||
|
||||
(mf/defc shape-wrapper
|
||||
{::mf/wrap [wrap-memo-shape]}
|
||||
[{:keys [shape] :as props}]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(case (:type shape)
|
||||
:frame [:& frame-wrapper {:shape shape :childs []}]
|
||||
:curve [:& path/path-wrapper {:shape shape}]
|
||||
:text [:& text/text-wrapper {:shape shape}]
|
||||
:icon [:& icon/icon-wrapper {:shape shape}]
|
||||
:rect [:& rect/rect-wrapper {:shape shape}]
|
||||
:path [:& path/path-wrapper {:shape shape}]
|
||||
:image [:& image/image-wrapper {:shape shape}]
|
||||
:circle [:& circle/circle-wrapper {:shape shape}])))
|
||||
|
||||
(def frame-default-props
|
||||
{:fill-color "#ffffff"})
|
||||
(def frame-default-props {:fill-color "#ffffff"})
|
||||
|
||||
(declare frame-shape)
|
||||
(declare translate-to-frame)
|
||||
|
||||
(defn wrap-memo-frame
|
||||
([component]
|
||||
(js/React.memo
|
||||
(mf/memo'
|
||||
component
|
||||
(fn [np op]
|
||||
(let [n-shape (aget np "shape")
|
||||
|
@ -84,76 +53,83 @@
|
|||
false)))))))))
|
||||
|
||||
|
||||
(mf/defc frame-wrapper
|
||||
{::mf/wrap [wrap-memo-frame]}
|
||||
[{:keys [shape objects] :as props}]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [selected-iref (-> (mf/deps (:id shape))
|
||||
(mf/use-memo #(refs/make-selected (:id shape))))
|
||||
selected? (mf/deref selected-iref)
|
||||
on-mouse-down #(common/on-mouse-down % shape)
|
||||
on-context-menu #(common/on-context-menu % shape)
|
||||
shape (merge frame-default-props shape)
|
||||
{:keys [x y width height]} shape
|
||||
(defn frame-wrapper [shape-wrapper]
|
||||
(mf/fnc frame-wrapper
|
||||
{::mf/wrap [wrap-memo-frame]}
|
||||
[{:keys [shape objects] :as props}]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [selected-iref (-> (mf/deps (:id shape))
|
||||
(mf/use-memo #(refs/make-selected (:id shape))))
|
||||
selected? (mf/deref selected-iref)
|
||||
on-mouse-down #(common/on-mouse-down % shape)
|
||||
on-context-menu #(common/on-context-menu % shape)
|
||||
shape (merge frame-default-props shape)
|
||||
{:keys [x y width height]} shape
|
||||
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
|
||||
ds-modifier (:displacement-modifier shape)
|
||||
label-pos (cond-> (gpt/point x (- y 10))
|
||||
(gmt/matrix? ds-modifier) (gpt/transform ds-modifier))
|
||||
ds-modifier (:displacement-modifier shape)
|
||||
label-pos (cond-> (gpt/point x (- y 10))
|
||||
(gmt/matrix? ds-modifier) (gpt/transform ds-modifier))
|
||||
|
||||
on-double-click
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! dw/deselect-all
|
||||
(dw/select-shape (:id 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 (:x label-pos)
|
||||
:y (:y label-pos)
|
||||
:width width
|
||||
:height 20
|
||||
:class-name "workspace-frame-label"
|
||||
:on-click on-double-click} ; user may also select with single click in the label
|
||||
(:name shape)]
|
||||
[:& frame-shape {:shape shape :childs childs}]])))
|
||||
on-double-click
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! dw/deselect-all
|
||||
(dw/select-shape (:id 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 (:x label-pos)
|
||||
:y (:y label-pos)
|
||||
:width width
|
||||
:height 20
|
||||
:class-name "workspace-frame-label"
|
||||
:on-click on-double-click} ; user may also select with single click in the label
|
||||
(:name shape)]
|
||||
[:& (frame-shape shape-wrapper) {:shape shape
|
||||
:childs childs}]]))))
|
||||
|
||||
(mf/defc frame-shape
|
||||
[{:keys [shape childs] :as props}]
|
||||
(let [rotation (:rotation shape)
|
||||
ds-modifier (:displacement-modifier shape)
|
||||
rz-modifier (:resize-modifier shape)
|
||||
(defn frame-shape [shape-wrapper]
|
||||
(mf/fnc frame-shape
|
||||
[{:keys [shape childs] :as props}]
|
||||
(let [rotation (:rotation shape)
|
||||
ds-modifier (:displacement-modifier shape)
|
||||
rz-modifier (:resize-modifier shape)
|
||||
|
||||
shape (cond-> shape
|
||||
(gmt/matrix? rz-modifier) (geom/transform rz-modifier)
|
||||
(gmt/matrix? ds-modifier) (geom/transform ds-modifier))
|
||||
shape (cond-> shape
|
||||
(gmt/matrix? rz-modifier) (geom/transform rz-modifier)
|
||||
(gmt/matrix? ds-modifier) (geom/transform ds-modifier))
|
||||
|
||||
{:keys [id x y width height]} shape
|
||||
{:keys [id x y width height]} shape
|
||||
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(itr/obj-assign!
|
||||
#js {:x 0
|
||||
:y 0
|
||||
:id (str "shape-" id)
|
||||
:width width
|
||||
:height height}))
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(itr/obj-assign!
|
||||
#js {:x 0
|
||||
:y 0
|
||||
:id (str "shape-" id)
|
||||
:width width
|
||||
:height height}))]
|
||||
|
||||
translate #(translate-to-frame % ds-modifier (gpt/point (- x) (- y)))]
|
||||
[:svg {:x x :y y :width width :height height}
|
||||
[:> "rect" props]
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:shape (translate item) :key (:id item)}])]))
|
||||
[:svg {:x x :y y :width width :height height}
|
||||
[:> "rect" props]
|
||||
(for [item childs]
|
||||
[:& shape-wrapper {:shape (translate-to-frame item shape) :key (:id item)}])])))
|
||||
|
||||
(defn- translate-to-frame
|
||||
[shape frame-ds-modifier pt]
|
||||
(let [rz-modifier (:resize-modifier shape)
|
||||
[shape frame]
|
||||
(let [pt (gpt/point (- (:x frame)) (- (:y frame)))
|
||||
frame-ds-modifier (:displacement-modifier frame)
|
||||
rz-modifier (:resize-modifier shape)
|
||||
shape (cond-> shape
|
||||
(gmt/matrix? frame-ds-modifier)
|
||||
(geom/transform frame-ds-modifier)
|
||||
|
||||
(gmt/matrix? rz-modifier)
|
||||
(and (= (:type shape) :group) (gmt/matrix? rz-modifier))
|
||||
(geom/transform rz-modifier)
|
||||
|
||||
(and (not= (:type shape) :group) (gmt/matrix? rz-modifier))
|
||||
(-> (geom/transform rz-modifier)
|
||||
(dissoc :resize-modifier)))]
|
||||
(geom/move shape pt)))
|
||||
|
|
|
@ -5,82 +5,91 @@
|
|||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.shapes.group
|
||||
#_(:require
|
||||
[lentes.core :as l]
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.geom :as geom]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.ui.shapes.circle :as circle]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.interop :as itr]
|
||||
[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.util.data :refer [classnames]]
|
||||
[uxbox.util.geom.matrix :as gmt]))
|
||||
[uxbox.main.ui.shapes.attrs :as attrs]))
|
||||
|
||||
;; --- Helpers
|
||||
(declare translate-to-frame)
|
||||
(declare group-shape)
|
||||
|
||||
;; (declare group-component)
|
||||
(defn group-wrapper [shape-wrapper]
|
||||
(mf/fnc group-wrapper
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
on-mouse-down #(common/on-mouse-down % shape)
|
||||
on-context-menu #(common/on-context-menu % shape)
|
||||
objects (-> refs/objects mf/deref)
|
||||
children (mapv #(get objects %) (:shapes shape))
|
||||
frame (get objects (:frame-id shape))]
|
||||
[:g.shape {:on-mouse-down on-mouse-down
|
||||
:on-context-menu on-context-menu}
|
||||
[:& (group-shape shape-wrapper) {:shape shape
|
||||
:shape-wrapper shape-wrapper
|
||||
:children children
|
||||
:frame frame }]])))
|
||||
|
||||
;; (defn- focus-shape
|
||||
;; [id]
|
||||
;; (-> (l/in [:shapes id])
|
||||
;; (l/derive st/state)))
|
||||
(defn group-shape [shape-wrapper]
|
||||
(mf/fnc group-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
children (unchecked-get props "children")
|
||||
frame (unchecked-get props "frame")
|
||||
|
||||
;; (defn render-component
|
||||
;; [shape]
|
||||
;; (case (:type shape)
|
||||
;; :group (group-component shape)
|
||||
;; :text (text/text-component shape)
|
||||
;; :icon (icon/icon-component shape)
|
||||
;; :rect (rect/rect-component shape)
|
||||
;; :path (path/path-component shape)
|
||||
;; :image (image/image-component shape)
|
||||
;; :circle (circle/circle-component shape)))
|
||||
ds-modifier (:displacement-modifier shape)
|
||||
rz-modifier (:resize-modifier shape)
|
||||
|
||||
;; (mx/defc component-container
|
||||
;; {:mixins [mx/reactive mx/static]}
|
||||
;; [id]
|
||||
;; (when-let [shape (mx/react (focus-shape id))]
|
||||
;; (when-not (:hidden shape)
|
||||
;; (render-component shape))))
|
||||
shape (cond-> shape
|
||||
(and (= "root" (:name frame)) (gmt/matrix? rz-modifier)) (geom/transform rz-modifier)
|
||||
(gmt/matrix? rz-modifier) (geom/transform ds-modifier))
|
||||
|
||||
;; ;; --- Group Component
|
||||
{:keys [id x y width height rotation]} shape
|
||||
|
||||
;; (declare group-shape)
|
||||
transform (when (and rotation (pos? rotation))
|
||||
(str/format "rotate(%s %s %s)"
|
||||
rotation
|
||||
(+ x (/ width 2))
|
||||
(+ y (/ height 2))))]
|
||||
[:g
|
||||
(for [item (reverse children)]
|
||||
[:& shape-wrapper {:shape (-> item
|
||||
(geom/transform rz-modifier)
|
||||
(assoc :displacement-modifier ds-modifier)
|
||||
(translate-to-frame frame))
|
||||
:key (:id item)}])
|
||||
|
||||
[:rect {:x x
|
||||
:y y
|
||||
:fill "red"
|
||||
:opacity 0.8
|
||||
:transform transform
|
||||
:id (str "group-" id)
|
||||
:width width
|
||||
:height height}]])))
|
||||
|
||||
;; (mx/defc group-component
|
||||
;; {:mixins [mx/static mx/reactive]}
|
||||
;; [{:keys [id x y width height group] :as shape}]
|
||||
;; (let [modifiers (mx/react (refs/selected-modifiers id))
|
||||
;; selected (mx/react refs/selected-shapes)
|
||||
;; selected? (contains? selected id)
|
||||
;; on-mouse-down #(common/on-mouse-down % shape selected)
|
||||
;; shape (assoc shape :modifiers modifiers)]
|
||||
;; [:g.shape.group-shape
|
||||
;; {:class (when selected? "selected")
|
||||
;; :on-mouse-down on-mouse-down}
|
||||
;; (group-shape shape component-container)]))
|
||||
(defn- translate-to-frame
|
||||
[shape frame]
|
||||
(let [pt (gpt/point (- (:x frame)) (- (:y frame)))
|
||||
frame-ds-modifier (:displacement-modifier frame)
|
||||
rz-modifier (:resize-modifier shape)
|
||||
shape (cond-> shape
|
||||
(gmt/matrix? frame-ds-modifier)
|
||||
(geom/transform frame-ds-modifier)
|
||||
|
||||
;; ;; --- Group Shape
|
||||
|
||||
;; (mx/defc group-shape
|
||||
;; {:mixins [mx/static mx/reactive]}
|
||||
;; [{:keys [id items modifiers] :as shape} factory]
|
||||
;; (let [{:keys [resize displacement]} modifiers
|
||||
|
||||
;; xfmt (cond-> (gmt/matrix)
|
||||
;; resize (gmt/multiply resize)
|
||||
;; displacement (gmt/multiply displacement))
|
||||
|
||||
;; moving? (boolean displacement)]
|
||||
;; [:g {:id (str "shape-" id)
|
||||
;; :class (classnames :move-cursor moving?)
|
||||
;; :transform (str xfmt)}
|
||||
;; (for [item (reverse items)]
|
||||
;; (-> (factory item)
|
||||
;; (mx/with-key (str item))))]))
|
||||
(and (= (:type shape) :group) (gmt/matrix? rz-modifier))
|
||||
(geom/transform rz-modifier)
|
||||
|
||||
(gmt/matrix? rz-modifier)
|
||||
(-> (geom/transform rz-modifier)
|
||||
(dissoc :resize-modifier)))]
|
||||
(geom/move shape pt)))
|
||||
|
||||
|
|
52
frontend/src/uxbox/main/ui/shapes/shape.cljs
Normal file
52
frontend/src/uxbox/main/ui/shapes/shape.cljs
Normal file
|
@ -0,0 +1,52 @@
|
|||
;; 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.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]))
|
||||
|
||||
(defn wrap-memo-shape
|
||||
([component]
|
||||
(mf/memo'
|
||||
component
|
||||
(fn [np op]
|
||||
(let [n-shape (unchecked-get np "shape")
|
||||
o-shape (unchecked-get op "shape")]
|
||||
(= n-shape o-shape))))))
|
||||
|
||||
(declare group-wrapper)
|
||||
(declare frame-wrapper)
|
||||
|
||||
(mf/defc shape-wrapper
|
||||
{::mf/wrap [wrap-memo-shape]}
|
||||
[{:keys [shape] :as props}]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(case (:type shape)
|
||||
:group [:& group-wrapper {:shape shape}]
|
||||
:curve [:& path/path-wrapper {:shape shape}]
|
||||
:text [:& text/text-wrapper {:shape shape}]
|
||||
:icon [:& icon/icon-wrapper {:shape shape}]
|
||||
:rect [:& rect/rect-wrapper {:shape shape}]
|
||||
:path [:& path/path-wrapper {:shape shape}]
|
||||
:image [:& image/image-wrapper {:shape shape}]
|
||||
:circle [:& circle/circle-wrapper {:shape shape}]
|
||||
:frame [:& frame-wrapper {:shape shape}]
|
||||
nil)))
|
||||
|
||||
(def group-wrapper (group/group-wrapper shape-wrapper))
|
||||
(def frame-wrapper (frame/frame-wrapper shape-wrapper))
|
|
@ -41,6 +41,7 @@
|
|||
:rect i/box
|
||||
:curve i/curve
|
||||
:text i/text
|
||||
:group i/folder
|
||||
nil))
|
||||
|
||||
;; --- Layer Name
|
||||
|
@ -82,8 +83,16 @@
|
|||
|
||||
(mf/defc layer-item
|
||||
{:wrap [mf/wrap-memo]}
|
||||
[{:keys [index item selected] :as props}]
|
||||
[{:keys [index item selected objects] :as props}]
|
||||
(let [selected? (contains? selected (:id item))
|
||||
local (mf/use-state {:collapsed false})
|
||||
collapsed? (:collapsed @local)
|
||||
|
||||
toggle-collapse
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(swap! local update :collapsed not))
|
||||
|
||||
toggle-blocking
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
|
@ -151,13 +160,30 @@
|
|||
:on-double-click #(dom/stop-propagation %)}
|
||||
[:& element-icon {:shape item}]
|
||||
[:& layer-name {:shape item}]
|
||||
|
||||
[:div.element-actions
|
||||
[:div.toggle-element {:class (when (:hidden item) "selected")
|
||||
:on-click toggle-visibility}
|
||||
i/eye]
|
||||
[:div.block-element {:class (when (:blocked item) "selected")
|
||||
:on-click toggle-blocking}
|
||||
i/lock]]]]))
|
||||
i/lock]]
|
||||
|
||||
(when (:shapes item)
|
||||
[:span.toggle-content
|
||||
{:on-click toggle-collapse
|
||||
:class (when-not collapsed? "inverse")}
|
||||
i/arrow-slide])]
|
||||
(when (and (:shapes item) (not collapsed?))
|
||||
[:ul.element-children
|
||||
(for [[index id] (d/enumerate (:shapes item))]
|
||||
(let [item (get objects id)]
|
||||
[:& layer-item
|
||||
{:item item
|
||||
:selected selected
|
||||
:index index
|
||||
:objects objects
|
||||
:key (:id item)}]))])]))
|
||||
|
||||
(mf/defc layer-frame-item
|
||||
{:wrap [#(mf/wrap-memo % =)]}
|
||||
|
@ -252,18 +278,12 @@
|
|||
[:ul
|
||||
(for [[index id] (d/enumerate (reverse (:shapes item)))]
|
||||
(let [item (get objects id)]
|
||||
(if (= (:type item) :frame)
|
||||
[:& layer-frame-item
|
||||
{:item item
|
||||
:key (:id item)
|
||||
:selected selected
|
||||
:objects objects
|
||||
:index index}]
|
||||
[:& layer-item
|
||||
{:item item
|
||||
:selected selected
|
||||
:index index
|
||||
:key (:id item)}])))])]))
|
||||
[:& layer-item
|
||||
{:item item
|
||||
:selected selected
|
||||
:index index
|
||||
:objects objects
|
||||
:key (:id item)}]))])]))
|
||||
|
||||
(mf/defc layers-tree
|
||||
{::mf/wrap [mf/wrap-memo]}
|
||||
|
|
Loading…
Add table
Reference in a new issue