0
Fork 0
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:
alonso.torres 2020-04-02 09:32:28 +02:00 committed by Andrey Antukh
parent 9d0450a4b5
commit e73350e2ba
14 changed files with 423 additions and 209 deletions

View file

@ -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?)

View file

@ -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]}
}

View 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))
[])))

View file

@ -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))

View file

@ -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]

View file

@ -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]

View file

@ -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

View file

@ -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)))

View file

@ -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]}]

View file

@ -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))

View file

@ -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)))

View file

@ -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)))

View 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))

View file

@ -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]}