From e73350e2bab0e9575a041b1efd33acfa71e69545 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 2 Apr 2020 09:32:28 +0200 Subject: [PATCH] :tada: Adds groups infrastructure --- common/uxbox/common/pages.cljc | 2 +- frontend/deps.edn | 2 +- frontend/src/uxbox/main/data/helpers.cljs | 20 +++ frontend/src/uxbox/main/data/workspace.cljs | 137 ++++++++++++++-- frontend/src/uxbox/main/exports.cljs | 25 ++- frontend/src/uxbox/main/geom.cljs | 30 ++-- frontend/src/uxbox/main/refs.cljs | 10 +- frontend/src/uxbox/main/store.cljs | 1 - .../src/uxbox/main/ui/components/error.cljs | 3 +- frontend/src/uxbox/main/ui/shapes.cljs | 7 +- frontend/src/uxbox/main/ui/shapes/frame.cljs | 154 ++++++++---------- frontend/src/uxbox/main/ui/shapes/group.cljs | 141 ++++++++-------- frontend/src/uxbox/main/ui/shapes/shape.cljs | 52 ++++++ .../main/ui/workspace/sidebar/layers.cljs | 48 ++++-- 14 files changed, 423 insertions(+), 209 deletions(-) create mode 100644 frontend/src/uxbox/main/data/helpers.cljs create mode 100644 frontend/src/uxbox/main/ui/shapes/shape.cljs diff --git a/common/uxbox/common/pages.cljc b/common/uxbox/common/pages.cljc index bab18595e7..69875531d2 100644 --- a/common/uxbox/common/pages.cljc +++ b/common/uxbox/common/pages.cljc @@ -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?) diff --git a/frontend/deps.edn b/frontend/deps.edn index 98c4fefe48..9169e295da 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -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]} } diff --git a/frontend/src/uxbox/main/data/helpers.cljs b/frontend/src/uxbox/main/data/helpers.cljs new file mode 100644 index 0000000000..d51a19a2ae --- /dev/null +++ b/frontend/src/uxbox/main/data/helpers.cljs @@ -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)) + []))) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 5b1a5e1ab3..b7ea5c8b47 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -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)) diff --git a/frontend/src/uxbox/main/exports.cljs b/frontend/src/uxbox/main/exports.cljs index b9012d4962..008c4661cf 100644 --- a/frontend/src/uxbox/main/exports.cljs +++ b/frontend/src/uxbox/main/exports.cljs @@ -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] diff --git a/frontend/src/uxbox/main/geom.cljs b/frontend/src/uxbox/main/geom.cljs index 2a35ca5c76..d6f71d42d1 100644 --- a/frontend/src/uxbox/main/geom.cljs +++ b/frontend/src/uxbox/main/geom.cljs @@ -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] diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index 68c0a07890..d3ac1e2777 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -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 diff --git a/frontend/src/uxbox/main/store.cljs b/frontend/src/uxbox/main/store.cljs index 2548c084ee..8905cc39d9 100644 --- a/frontend/src/uxbox/main/store.cljs +++ b/frontend/src/uxbox/main/store.cljs @@ -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))) diff --git a/frontend/src/uxbox/main/ui/components/error.cljs b/frontend/src/uxbox/main/ui/components/error.cljs index 8a14d4fec0..9821cafa2f 100644 --- a/frontend/src/uxbox/main/ui/components/error.cljs +++ b/frontend/src/uxbox/main/ui/components/error.cljs @@ -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]}] diff --git a/frontend/src/uxbox/main/ui/shapes.cljs b/frontend/src/uxbox/main/ui/shapes.cljs index 25890599f3..63fdd6a493 100644 --- a/frontend/src/uxbox/main/ui/shapes.cljs +++ b/frontend/src/uxbox/main/ui/shapes.cljs @@ -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)) diff --git a/frontend/src/uxbox/main/ui/shapes/frame.cljs b/frontend/src/uxbox/main/ui/shapes/frame.cljs index dc00173c25..165e7d21ca 100644 --- a/frontend/src/uxbox/main/ui/shapes/frame.cljs +++ b/frontend/src/uxbox/main/ui/shapes/frame.cljs @@ -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))) diff --git a/frontend/src/uxbox/main/ui/shapes/group.cljs b/frontend/src/uxbox/main/ui/shapes/group.cljs index e25a24d637..f5de3924fc 100644 --- a/frontend/src/uxbox/main/ui/shapes/group.cljs +++ b/frontend/src/uxbox/main/ui/shapes/group.cljs @@ -5,82 +5,91 @@ ;; Copyright (c) 2016-2019 Andrey Antukh (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))) diff --git a/frontend/src/uxbox/main/ui/shapes/shape.cljs b/frontend/src/uxbox/main/ui/shapes/shape.cljs new file mode 100644 index 0000000000..c049316413 --- /dev/null +++ b/frontend/src/uxbox/main/ui/shapes/shape.cljs @@ -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)) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs index 95c2a6c2a2..e7c505e363 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs @@ -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]}