From ba373573e06e24e8e2668e74172e8c8a882c01f0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sun, 8 Mar 2020 12:46:09 +0100 Subject: [PATCH] :recycle: Initial refactor of page data structure (wip). Still work in progress but is a necessary step for a future (re)introduction of groups. --- backend/src/uxbox/fixtures.clj | 8 +- backend/tests/user.clj | 77 +++ common/uxbox/common/data.cljc | 21 - common/uxbox/common/pages.cljc | 334 +++++---- common/uxbox/common/spec.cljc | 15 + docs/99-Collaborative-Edition.md | 63 +- frontend/deps.edn | 6 +- frontend/src/uxbox/main/constants.cljs | 4 +- frontend/src/uxbox/main/data/workspace.cljs | 633 +++++++++--------- frontend/src/uxbox/main/exports.cljs | 8 +- frontend/src/uxbox/main/geom.cljs | 33 +- frontend/src/uxbox/main/refs.cljs | 4 +- frontend/src/uxbox/main/ui/shapes.cljs | 6 +- frontend/src/uxbox/main/ui/shapes/attrs.cljs | 25 +- frontend/src/uxbox/main/ui/shapes/common.cljs | 12 +- .../ui/shapes/{canvas.cljs => frame.cljs} | 72 +- frontend/src/uxbox/main/ui/shapes/rect.cljs | 42 +- frontend/src/uxbox/main/ui/workspace.cljs | 10 +- .../src/uxbox/main/ui/workspace/drawarea.cljs | 6 +- .../src/uxbox/main/ui/workspace/header.cljs | 6 +- .../uxbox/main/ui/workspace/selection.cljs | 12 +- .../main/ui/workspace/sidebar/layers.cljs | 174 +++-- .../main/ui/workspace/sidebar/options.cljs | 6 +- .../options/{canvas.cljs => frame.cljs} | 6 +- .../src/uxbox/main/ui/workspace/viewport.cljs | 281 ++++---- frontend/src/uxbox/util/interop.cljs | 4 + frontend/src/uxbox/util/perf.cljc | 25 +- frontend/src/uxbox/view/ui/viewer.cljs | 4 +- .../ui/viewer/{canvas.cljs => frame.cljs} | 6 +- 29 files changed, 1116 insertions(+), 787 deletions(-) rename frontend/src/uxbox/main/ui/shapes/{canvas.cljs => frame.cljs} (68%) rename frontend/src/uxbox/main/ui/workspace/sidebar/options/{canvas.cljs => frame.cljs} (95%) rename frontend/src/uxbox/view/ui/viewer/{canvas.cljs => frame.cljs} (93%) diff --git a/backend/src/uxbox/fixtures.clj b/backend/src/uxbox/fixtures.clj index 5c832d7dd..ae9f82a66 100644 --- a/backend/src/uxbox/fixtures.clj +++ b/backend/src/uxbox/fixtures.clj @@ -12,6 +12,7 @@ [mount.core :as mount] [promesa.core :as p] [uxbox.config :as cfg] + [uxbox.common.pages :as cp] [uxbox.common.data :as d] [uxbox.core] [uxbox.db :as db] @@ -149,12 +150,7 @@ create-page (fn [conn owner-id project-id file-id index] (p/let [id (mk-uuid "page" project-id file-id index) - data {:version 1 - :shapes [] - :canvas [] - :options {} - :shapes-by-id {}} - + data cp/default-page-data name (str "page " index) version 0 ordering index diff --git a/backend/tests/user.clj b/backend/tests/user.clj index a263c8a68..7a6b1be0c 100644 --- a/backend/tests/user.clj +++ b/backend/tests/user.clj @@ -86,3 +86,80 @@ '(promesa.core/let)]}}}}) (kondo/print!)))) +(comment + {:version 1 + :options {} + :shapes [:id1 :id2] + :canvas [:id3] + :shapes-by-id {:id1 {:canvas :id3} :id2 {} :id3 {}}}) + + +(comment + {:version 2 + :options {} + + :objects + {:root + {:type :frame + :shapes [:sid0 :frame-0]} + + :frame0 + {:type :frame + :parent :root + :shapes [:sid1 :sid2]} + + :sid0 + {:type :rect + :parent :root} + + :sid1 + {:type :rect + :parent :frame0} + + :sid2 + {:type :group + :shapes [:sid3 :sid4] + :parent :frame0} + + :sid3 + {:type :elipse + :parent :sid2} + + :sid4 + {:type :elipse + :parent :sid2}}}) + +(comment + {:version 3 + :options {} + + :rmap + {:id1 :root-frame + :id2 :root-frame + :id3 :frame-id-1 + :id4 :frame-id-2 + :id5 :frame-id-2 + :id6 :frame-id-2} + + :frames + {:root-frame + {:type :frame + :shapes [:id1 :id2] + :objects + {:id1 {:type :rect} + :id2 {:type :elipse}}} + + :frame-id-1 + {:type :frame + :shapes [:id3] + :objects + {:id3 {:type :path}}} + + :frame-id-2 + {:type :frame + :shapes [:id4] + :objects + {:id4 {:type :group + :shapes [:id5 :id6]} + :id5 {:type :path :parent :id4} + :id6 {:type :elipse :parent :id4}}}}}) diff --git a/common/uxbox/common/data.cljc b/common/uxbox/common/data.cljc index ceaa4d409..e80e1c6a8 100644 --- a/common/uxbox/common/data.cljc +++ b/common/uxbox/common/data.cljc @@ -57,27 +57,6 @@ not-found)) not-found coll))) -(defn diff-maps - [ma mb] - (let [ma-keys (set (keys ma)) - mb-keys (set (keys mb)) - added (set/difference mb-keys ma-keys) - removed (set/difference ma-keys mb-keys) - both (set/intersection ma-keys mb-keys)] - (concat - (mapv #(vector :set % (get mb %)) added) - (mapv #(vector :set % nil) removed) - (loop [k (first both) - r (rest both) - rs []] - (if k - (let [vma (get ma k) - vmb (get mb k)] - (if (= vma vmb) - (recur (first r) (rest r) rs) - (recur (first r) (rest r) (conj rs [:set k vmb])))) - rs))))) - (defn index-by "Return a indexed map of the collection keyed by the result of executing the getter over each element of the collection." diff --git a/common/uxbox/common/pages.cljc b/common/uxbox/common/pages.cljc index 5454bc84f..6c4ff6d3d 100644 --- a/common/uxbox/common/pages.cljc +++ b/common/uxbox/common/pages.cljc @@ -1,9 +1,18 @@ +;; 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) 2019-2020 Andrey Antukh (ns uxbox.common.pages "A common (clj/cljs) functions and specs for pages." (:require - [uxbox.common.spec :as us] [clojure.spec.alpha :as s] - [uxbox.common.data :as d])) + [uxbox.common.data :as d] + [uxbox.common.exceptions :as ex] + [uxbox.common.spec :as us])) ;; --- Specs @@ -46,7 +55,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}) +(s/def ::type #{:rect :path :circle :image :text :canvas :curve :icon :frame}) (s/def ::x number?) (s/def ::y number?) (s/def ::cx number?) @@ -92,162 +101,243 @@ (s/def ::shapes (s/coll-of uuid? :kind vector?)) (s/def ::canvas (s/coll-of uuid? :kind vector?)) -(s/def ::shapes-by-id +(s/def ::objects (s/map-of uuid? ::shape)) (s/def ::data - (s/keys :req-un [::shapes - ::canvas - ::options - ::shapes-by-id])) + (s/keys :req-un [::options + ::version + ::objects])) -;; Changes related -(s/def ::operation (s/tuple #{:set} keyword? any?)) +(s/def ::attr keyword?) +(s/def ::val any?) +(s/def ::parent-id uuid?) +(s/def ::frame-id uuid?) -(s/def ::operations - (s/coll-of ::operation :kind vector?)) +(defmulti operation-spec-impl :type) + +(defmethod operation-spec-impl :set [_] + (s/keys :req-un [::attr ::val])) + +(defmethod operation-spec-impl :mov [_] + (s/keys :req-un [::id ::index])) + +(s/def ::operation (s/multi-spec operation-spec-impl :type)) +(s/def ::operations (s/coll-of ::operation)) (defmulti change-spec-impl :type) -(defmethod change-spec-impl :add-shape [_] - (s/keys :req-un [::shape ::id ::session-id] - :opt-un [::index])) +(defmethod change-spec-impl :add-obj [_] + (s/keys :req-un [::id ::frame-id ::obj] + :opt-un [::session-id])) -(defmethod change-spec-impl :add-canvas [_] - (s/keys :req-un [::shape ::id ::session-id] - :opt-un [::index])) +(defmethod change-spec-impl :mod-obj [_] + (s/keys :req-un [::id ::operations] + :opt-un [::session-id])) -(defmethod change-spec-impl :mod-shape [_] - (s/keys :req-un [::id ::operations ::session-id])) +(defmethod change-spec-impl :del-obj [_] + (s/keys :req-un [::id] + :opt-un [::session-id])) -(defmethod change-spec-impl :mov-shape [_] - (s/keys :req-un [::id ::index ::session-id])) +(defmethod change-spec-impl :mov-obj [_] + (s/keys :req-un [::id ::frame-id] + :opt-un [::session-id])) -(defmethod change-spec-impl :mod-opts [_] - (s/keys :req-un [::operations ::session-id])) +;; (defmethod change-spec-impl :mod-shape [_] +;; (s/keys :req-un [::id ::operations ::session-id])) -(defmethod change-spec-impl :del-shape [_] - (s/keys :req-un [::id ::session-id])) +;; (defmethod change-spec-impl :mov-shape [_] +;; (s/keys :req-un [::id ::index ::session-id])) -(defmethod change-spec-impl :del-canvas [_] - (s/keys :req-un [::id ::session-id])) +;; (defmethod change-spec-impl :mod-opts [_] +;; (s/keys :req-un [::operations ::session-id])) + +;; (defmethod change-spec-impl :del-shape [_] +;; (s/keys :req-un [::id ::session-id])) + +;; (defmethod change-spec-impl :del-canvas [_] +;; (s/keys :req-un [::id ::session-id])) (s/def ::change (s/multi-spec change-spec-impl :type)) (s/def ::changes (s/coll-of ::change)) +(def root #uuid "00000000-0000-0000-0000-000000000000") + (def default-page-data "A reference value of the empty page data." - {:version 1 - :shapes [] - :canvas [] + {:version 3 :options {} - :shapes-by-id {}}) + :objects + {root + {:id root + :type :frame + :name "root" + :shapes []}}}) ;; --- Changes Processing Impl -(declare process-change) -(declare process-mod-shape) -(declare process-mod-opts) -(declare process-mov-shape) -(declare process-add-shape) -(declare process-add-canvas) -(declare process-del-shape) -(declare process-del-canvas) +(defmulti process-change + (fn [data change] (:type change))) (defn process-changes [data items] (->> (us/verify ::changes items) - (reduce process-change data))) + (reduce #(or (process-change %1 %2) %1) data))) -(defn- process-change - [data {:keys [type] :as change}] - (case type - :add-shape (process-add-shape data change) - :add-canvas (process-add-canvas data change) - :mod-shape (process-mod-shape data change) - :mov-shape (process-mov-shape data change) - :del-shape (process-del-shape data change) - :del-canvas (process-del-canvas data change) - :mod-opts (process-mod-opts data change))) +(defmethod process-change :add-obj + [data {:keys [id obj frame-id index] :as change}] + (us/assert! (contains? (:objects data) frame-id) "process-change/add-obj") + (let [obj (assoc obj + :frame-id frame-id + :id id)] + (-> data + (update :objects assoc id obj) + (update-in [:objects frame-id :shapes] + (fn [shapes] + (cond + (some #{id} shapes) + shapes -(defn- process-add-shape - [data {:keys [id index shape] :as change}] - (-> data - (update :shapes (fn [shapes] - (cond - (some #{id} shapes) - shapes + (nil? index) + (conj shapes id) - (nil? index) - (conj shapes id) + :else + (let [[before after] (split-at index shapes)] + (d/concat [] before [id] after)))))))) - :else - (let [[before after] (split-at index shapes)] - (d/concat [] before [id] after))))) - (update :shapes-by-id assoc id shape))) +(defn- process-obj-operation + [shape op] + (case (:type op) + :set + (let [attr (:attr op) + val (:val op)] + (if (nil? val) + (dissoc shape attr) + (assoc shape attr val))) -(defn- process-add-canvas - [data {:keys [id shape index] :as change}] - (-> data - (update :canvas (fn [shapes] - (cond - (some #{id} shapes) - shapes + (ex/raise :type :not-implemented + :hint "TODO"))) - (nil? index) - (conj shapes id) - - :else - (let [[before after] (split-at index shapes)] - (d/concat [] before [id] after))))) - - (update :shapes-by-id assoc id shape))) - -(defn- process-mod-shape +(defmethod process-change :mod-obj [data {:keys [id operations] :as change}] - (if (get-in data [:shapes-by-id id]) - (update-in data [:shapes-by-id id] - #(reduce (fn [shape [_ att val]] - (if (nil? val) - (dissoc shape att) - (assoc shape att val))) - % operations)) - data)) + (us/assert! (contains? (:objects data) id) "process-change/mod-obj") + (update-in data [:objects id] + #(reduce process-obj-operation % operations))) -(defn- process-mod-opts - [data {:keys [operations]}] - (update data :options - #(reduce (fn [options [_ att val]] - (if (nil? val) - (dissoc options att) - (assoc options att val))) - % operations))) +(defmethod process-change :mov-obj + [data {:keys [id frame-id] :as change}] + (us/assert! (contains? (:objects data) frame-id)) + (let [frame-id' (get-in data [:objects id :frame-id])] + (when (not= frame-id frame-id') + (-> data + (update-in [:objects frame-id' :shapes] (fn [s] (filterv #(not= % id) s))) + (update-in [:objects id] assoc :frame-id frame-id) + (update-in [:objects frame-id :shapes] conj id))))) -(defn- process-mov-shape - [data {:keys [id index]}] - (let [shapes (:shapes data) - current-index (d/index-of shapes id) - shapes' (into [] (remove #(= % id) shapes))] - (cond - (= index current-index) - data - - (nil? current-index) - (assoc data :shapes (d/concat [id] shapes')) - - :else - (let [[before after] (split-at index shapes')] - (assoc data :shapes (d/concat [] before [id] after)))))) - -(defn- process-del-shape +(defmethod process-change :del-obj [data {:keys [id] :as change}] - (-> data - (update :shapes (fn [s] (filterv #(not= % id) s))) - (update :shapes-by-id dissoc id))) + (when-let [{:keys [frame-id] :as obj} (get-in data [:objects id])] + (-> data + (update :objects dissoc id) + (update-in [:objects frame-id :shapes] + (fn [s] (filterv #(not= % id) s)))))) -(defn- process-del-canvas - [data {:keys [id] :as change}] - (-> data - (update :canvas (fn [s] (filterv #(not= % id) s))) - (update :shapes-by-id dissoc id))) +;; (defn- process-change +;; [data {:keys [type] :as change}] +;; (case type +;; :add-obj (process-add-obj data change) +;; :mod-obj (process-mod-obj data change) + +;; ;; :add-shape (process-add-shape data change) +;; ;; :add-canvas (process-add-canvas data change) +;; ;; :mod-shape (process-mod-shape data change) +;; ;; :mov-shape (process-mov-shape data change) +;; ;; :del-shape (process-del-shape data change) +;; ;; :del-canvas (process-del-canvas data change) +;; ;; :mod-opts (process-mod-opts data change) +;; )) + +;; (defn- process-add-obj + + +;; (defn- process-add-shape +;; [data {:keys [id index shape] :as change}] +;; (-> data +;; (update :shapes (fn [shapes] +;; (cond +;; (some #{id} shapes) +;; shapes + +;; (nil? index) +;; (conj shapes id) + +;; :else +;; (let [[before after] (split-at index shapes)] +;; (d/concat [] before [id] after))))) +;; (update :shapes-by-id assoc id shape))) + +;; (defn- process-add-canvas +;; [data {:keys [id shape index] :as change}] +;; (-> data +;; (update :canvas (fn [shapes] +;; (cond +;; (some #{id} shapes) +;; shapes + +;; (nil? index) +;; (conj shapes id) + +;; :else +;; (let [[before after] (split-at index shapes)] +;; (d/concat [] before [id] after))))) + +;; (update :shapes-by-id assoc id shape))) + +;; (defn- process-mod-shape +;; [data {:keys [id operations] :as change}] +;; (if (get-in data [:shapes-by-id id]) +;; (update-in data [:shapes-by-id id] +;; #(reduce (fn [shape [_ att val]] +;; (if (nil? val) +;; (dissoc shape att) +;; (assoc shape att val))) +;; % operations)) +;; data)) + +;; (defn- process-mod-opts +;; [data {:keys [operations]}] +;; (update data :options +;; #(reduce (fn [options [_ att val]] +;; (if (nil? val) +;; (dissoc options att) +;; (assoc options att val))) +;; % operations))) + +;; (defn- process-mov-shape +;; [data {:keys [id index]}] +;; (let [shapes (:shapes data) +;; current-index (d/index-of shapes id) +;; shapes' (into [] (remove #(= % id) shapes))] +;; (cond +;; (= index current-index) +;; data + +;; (nil? current-index) +;; (assoc data :shapes (d/concat [id] shapes')) + +;; :else +;; (let [[before after] (split-at index shapes')] +;; (assoc data :shapes (d/concat [] before [id] after)))))) + +;; (defn- process-del-shape +;; [data {:keys [id] :as change}] +;; (-> data +;; (update :shapes (fn [s] (filterv #(not= % id) s))) +;; (update :shapes-by-id dissoc id))) + +;; (defn- process-del-canvas +;; [data {:keys [id] :as change}] +;; (-> data +;; (update :canvas (fn [s] (filterv #(not= % id) s))) +;; (update :shapes-by-id dissoc id))) diff --git a/common/uxbox/common/spec.cljc b/common/uxbox/common/spec.cljc index 55ab98ade..5c8eb712a 100644 --- a/common/uxbox/common/spec.cljc +++ b/common/uxbox/common/spec.cljc @@ -110,6 +110,21 @@ ;; --- Macros +(defmacro assert! + "Evaluates expr and throws an exception if it does not evaluate to + logical true." + ([x] + (when *assert* + `(when-not ~x + (throw (ex/error :type :assertion-error + :hint (str "Assert failed: " (pr-str '~x))))))) + ([x message] + (when *assert* + `(when-not ~x + (throw (ex/error :type :assertion-error + :hint (str "Assert failed: " (pr-str '~x)) + :message ~message)))))) + (defn spec-assert [spec x] (s/assert* spec x)) diff --git a/docs/99-Collaborative-Edition.md b/docs/99-Collaborative-Edition.md index d99535313..839e8ee0d 100644 --- a/docs/99-Collaborative-Edition.md +++ b/docs/99-Collaborative-Edition.md @@ -9,35 +9,64 @@ and persistence protocol. This is a page data structure: ``` -{:version 1 +{:version 2 :options {} - :shapes [, ...] - :canvas [, ...] - :shapes-by-id { , ...}} + + :rmap + {:id1 :default + :id2 :default + :id3 :id1} + + :objects + {:root + {:type :root + :shapes [:id1 :id2]} + + :id1 + {:type :canvas + :shapes [:id3]} + + :id2 {:type :rect} + :id3 {:type :circle}}} ``` + This is a potential list of persistent ops: ``` -;; Generic (Shapes & Canvas) -[:mod-shape [:set ], ...] +{:type :mod-opts + :operations [, ...] -;; Example: -;; [:mod-shape 1 [:set :x 2] [:set :y 3]] +{:type :add-obj + :id + :parent + :obj } -;; Specific -[:add-shape ] -[:add-canvas ] +{:type :mod-obj + :id + :operations [, ...]} -[:del-shape ] -[:del-canvas ] +{:type :mov-obj + :id + :dest } -[:mov-canvas :after ] ;; null implies at first position -[:mov-shape :after ] - -[:mod-opts [:set ], [:del nil], ...] +{:type :del-obj + :id } ``` +This is a potential list of operations: + +``` +{:type :set + :attr + :val } + +{:type :mov + :id + :index } +``` + + ## Ephemeral communication (Websocket protocol) diff --git a/frontend/deps.edn b/frontend/deps.edn index 9ae2502ad..8031f6335 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -3,9 +3,9 @@ org.clojure/clojure {:mvn/version "1.10.1"} com.cognitect/transit-cljs {:mvn/version "0.8.256"} - cljsjs/react {:mvn/version "16.12.0-1"} - cljsjs/react-dom {:mvn/version "16.12.0-1"} - cljsjs/react-dom-server {:mvn/version "16.12.0-1"} + cljsjs/react {:mvn/version "16.13.0-0"} + cljsjs/react-dom {:mvn/version "16.13.0-0"} + cljsjs/react-dom-server {:mvn/version "16.13.0-0"} environ/environ {:mvn/version "1.1.0"} metosin/reitit-core {:mvn/version "0.3.10"} diff --git a/frontend/src/uxbox/main/constants.cljs b/frontend/src/uxbox/main/constants.cljs index ae50ba8ad..c541dd58e 100644 --- a/frontend/src/uxbox/main/constants.cljs +++ b/frontend/src/uxbox/main/constants.cljs @@ -9,8 +9,8 @@ (def viewport-width 4000) (def viewport-height 4000) -(def canvas-start-x 1200) -(def canvas-start-y 1200) +(def frame-start-x 1200) +(def frame-start-y 1200) (def grid-x-axis 10) (def grid-y-axis 10) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 4f119dae2..68fc7bf5b 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -6,6 +6,7 @@ (ns uxbox.main.data.workspace (:require + [clojure.set :as set] [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk] @@ -166,7 +167,7 @@ (ptk/reify ::handle-page-change ptk/WatchEvent (watch [_ state stream] - (let [page-id' (get-in state [:workspace-page :id])] + #_(let [page-id' (get-in state [:workspace-page :id])] (when (= page-id page-id') (rx/of (shapes-changes-commited msg))))))) @@ -228,7 +229,7 @@ (when-not (or (empty? undo) (= index -1)) (let [changes (get-in undo [index :undo-changes])] (rx/of (materialize-undo changes (dec index)) - (commit-changes changes [] false)))))))) + (commit-changes changes [] {:save-undo? false})))))))) (def redo (ptk/reify ::redo @@ -241,7 +242,7 @@ (when-not (or (empty? undo) (= index (dec (count undo)))) (let [changes (get-in undo [(inc index) :redo-changes])] (rx/of (materialize-undo changes (inc index)) - (commit-changes changes [] false)))))))) + (commit-changes changes [] {:save-undo? false})))))))) (def reinitialize-undo (ptk/reify ::reset-undo @@ -335,6 +336,7 @@ (ptk/reify ::initialize-page ptk/UpdateEvent (update [_ state] + ;; (prn "initialize-page" page-id) (let [page (get-in state [:pages page-id]) data (get-in state [:pages-data page-id]) local (get-in state [:workspace-cache page-id] workspace-default)] @@ -348,11 +350,13 @@ (let [stoper (rx/filter #(or (ptk/type? ::finalize %) (ptk/type? ::initialize-page %)) stream)] - (->> stream - (rx/filter #(satisfies? IBatchedChange %)) - (rx/debounce 200) - (rx/map (constantly diff-and-commit-changes)) - (rx/take-until stoper)))))) + (rx/concat + (->> stream + (rx/filter #(satisfies? IBatchedChange %)) + (rx/debounce 200) + (rx/map (constantly diff-and-commit-changes)) + (rx/take-until stoper)) + #_(rx/of diff-and-commit-changes)))))) (defn finalize [file-id page-id] @@ -361,35 +365,60 @@ (ptk/reify ::finalize ptk/UpdateEvent (update [_ state] + ;; (prn "finalize-page" page-id) (let [local (:workspace-local state)] (assoc-in state [:workspace-cache page-id] local))))) +(defn- generate-operations + [ma mb] + (let [ma-keys (set (keys ma)) + mb-keys (set (keys mb)) + added (set/difference mb-keys ma-keys) + removed (set/difference ma-keys mb-keys) + both (set/intersection ma-keys mb-keys)] + (d/concat + (mapv #(array-map :type :set :attr % :val (get mb %)) added) + (mapv #(array-map :type :set :attr % :val nil) removed) + (loop [k (first both) + r (rest both) + rs []] + (if k + (let [vma (get ma k) + vmb (get mb k)] + (if (= vma vmb) + (recur (first r) (rest r) rs) + (recur (first r) (rest r) (conj rs {:type :set + :attr k + :val vmb})))) + rs))))) + (defn- generate-changes - [session-id prev curr] - (let [diff (d/diff-maps prev curr)] - (loop [scs (rest diff) - sc (first diff) - res []] - (if (nil? sc) - res - (let [[_ id shape] sc] - (recur (rest scs) - (first scs) - (conj res {:type :mod-shape - :session-id session-id - :operations (d/diff-maps (get prev id) shape) - :id id}))))))) + [prev curr] + (letfn [(impl-diff [res id] + (let [prev-obj (get-in prev [:objects id]) + curr-obj (get-in curr [:objects id]) + ops (generate-operations (dissoc prev-obj :shapes :frame-id) + (dissoc curr-obj :shapes :frame-id))] + (if (empty? ops) + res + (conj res {:type :mod-obj + :operations ops + :id id}))))] + (reduce impl-diff [] (set/union (set (keys (:objects prev))) + (set (keys (:objects curr))))))) (def diff-and-commit-changes (ptk/reify ::diff-and-commit-changes ptk/WatchEvent (watch [_ state stream] (let [pid (get-in state [:workspace-page :id]) - curr (get-in state [:workspace-data :shapes-by-id]) - prev (get-in state [:pages-data pid :shapes-by-id]) - session-id (:session-id state) - changes (generate-changes session-id prev curr) - undo-changes (generate-changes session-id curr prev)] + curr (get-in state [:workspace-data]) + prev (get-in state [:pages-data pid]) + + changes (generate-changes prev curr) + undo-changes (generate-changes curr prev)] + ;; (prn "diff-and-commit-changes1" changes) + ;; (prn "diff-and-commit-changes2" undo-changes) (when-not (empty? changes) (rx/of (commit-changes changes undo-changes))))))) @@ -878,7 +907,7 @@ in the current workspace page." [state] (let [data (:workspace-data state)] - (into #{} (map :name) (vals (:shapes-by-id data))))) + (into #{} (map :name) (vals (:objects data))))) (defn impl-generate-unique-name "A unique name generator based on the current workspace page." @@ -891,17 +920,14 @@ candidate))))) (defn impl-assoc-shape - [state {:keys [id] :as data}] + [state {:keys [id frame-id] :as data}] (let [name (impl-generate-unique-name state (:name data)) shape (assoc data :name name)] - (as-> state $ - (if (= :canvas (:type shape)) - (update-in $ [:workspace-data :canvas] conj id) - (update-in $ [:workspace-data :shapes] conj id)) - (assoc-in $ [:workspace-data :shapes-by-id id] shape)))) + (-> state + (update-in [:workspace-data :objects frame-id :shapes] conj id) + (update-in [:workspace-data :objects] assoc id shape)))) (declare select-shape) -(declare recalculate-shape-canvas-relation) (def shape-default-attrs {:stroke-color "#000000" @@ -909,60 +935,74 @@ :fill-color "#000000" :fill-opacity 1}) +(defn- calculate-frame-overlap + [data shape] + (let [objects (:objects data) + rshp (geom/shape->rect-shape shape) + + xfmt (comp + (filter #(= :frame (:type %))) + (filter #(not= (:id shape) (:id %))) + (filter #(not= uuid/zero (:id %))) + (filter #(geom/overlaps? % rshp))) + + frame (->> (vals objects) + (sequence xfmt) + (first))] + + (or (:id frame) uuid/zero))) + (defn add-shape - [data] - (us/verify ::shape-attrs data) + [attrs] + (us/verify ::shape-attrs attrs) (let [id (uuid/next)] (ptk/reify ::add-shape ptk/UpdateEvent (update [_ state] - (let [shape (-> (geom/setup-proportions data) + (let [data (:workspace-data state) + shape (-> (geom/setup-proportions attrs) (assoc :id id)) - shape (merge shape-default-attrs shape) - shape (recalculate-shape-canvas-relation state shape)] + frame-id (calculate-frame-overlap data shape) + shape (merge shape-default-attrs shape {:frame-id frame-id})] (impl-assoc-shape state shape))) ptk/WatchEvent (watch [_ state stream] - (let [shape (get-in state [:workspace-data :shapes-by-id id]) - sid (:session-id state)] - (rx/of (commit-changes [{:type :add-shape - :session-id sid - :shape shape - :id id}] - [{:type :del-shape - :session-id sid - :id id}]) - (select-shape id))))))) + (let [obj (get-in state [:workspace-data :objects id])] + (rx/of (commit-changes [{:type :add-obj + :id id + :frame-id (:frame-id obj) + :obj obj}] + [{:type :del-obj + :id id}]))))))) -(def canvas-default-attrs +(def frame-default-attrs {:stroke-color "#000000" :stroke-opacity 1 + :frame-id uuid/zero :fill-color "#ffffff" :fill-opacity 1}) -(defn add-canvas +(defn add-frame [data] (us/verify ::shape-attrs data) (let [id (uuid/next)] - (ptk/reify ::add-canvas + (ptk/reify ::add-frame ptk/UpdateEvent (update [_ state] (let [shape (-> (geom/setup-proportions data) (assoc :id id)) - shape (merge canvas-default-attrs shape)] + shape (merge frame-default-attrs shape)] (impl-assoc-shape state shape))) ptk/WatchEvent (watch [_ state stream] - (let [shape (get-in state [:workspace-data :shapes-by-id id]) - sid (:session-id state)] - (rx/of (commit-changes [{:type :add-canvas - :session-id sid - :shape shape - :id id}] - [{:type :del-canvas - :session-id sid + (let [obj (get-in state [:workspace-data :objects id])] + (rx/of (commit-changes [{:type :add-obj + :id id + :frame-id (:frame-id obj) + :obj obj}] + [{:type :del-obj :id id}]))))))) @@ -971,51 +1011,47 @@ (defn duplicate-shapes [shapes] (ptk/reify ::duplicate-shapes - ptk/UpdateEvent - (update [_ state] - (reduce (fn [state {:keys [id] :as shape}] - (-> state - (assoc-in [:workspace-data :shapes-by-id id] shape) - (update-in [:workspace-data :shapes] (fnil conj []) id))) - state - shapes)) - ptk/WatchEvent (watch [_ state stream] - (let [rchanges (mapv (fn [shape] - {:type :add-shape - :id (:id shape) - :shape shape - :session-id (:session-id state)}) + (prn "duplicate-shapes" shapes) + (let [objects (get-in state [:workspace-data :objects]) + rchanges (mapv (fn [id] + (let [obj (get objects id) + obj (assoc obj :id (uuid/next))] + {:type :add-obj + :id (:id obj) + :frame-id (:frame-id obj) + :obj obj + :session-id (:session-id state)})) shapes) - uchanges (mapv (fn [shape] - {:type :del-shape - :id (:id shape) + uchanges (mapv (fn [rch] + {:type :del-obj + :id (:id rch) :session-id (:session-id state)}) - shapes)] - (rx/of (commit-changes rchanges uchanges)))))) + rchanges)] + (rx/of (commit-changes rchanges uchanges {:commit-local? true})))))) -(defn duplicate-canvas - [{:keys [id] :as canvas} prev-id] - (ptk/reify ::duplicate-canvas +(defn duplicate-frame + [{:keys [id] :as frame} prev-id] + (ptk/reify ::duplicate-frame ptk/UpdateEvent (update [_ state] (-> state - (assoc-in [:workspace-data :shapes-by-id id] canvas) - (update-in [:workspace-data :canvas] (fnil conj []) id))) + (assoc-in [:workspace-data :objects id] frame) + (update-in [:workspace-data :frame] (fnil conj []) id))) ptk/WatchEvent (watch [_ state stream] - (let [shapes (->> (vals (get-in state [:workspace-data :shapes-by-id])) - (filter #(= (:canvas %) prev-id)) + (let [shapes (->> (vals (get-in state [:workspace-data :objects])) + (filter #(= (:frame %) prev-id)) (map #(assoc % :id (uuid/next))) - (map #(assoc % :canvas id))) + (map #(assoc % :frame id))) - rchange {:type :add-canvas + rchange {:type :add-frame :id id - :shape canvas + :shape frame :session-id (:session-id state)} - uchange {:type :del-canvas + uchange {:type :del-frame :id id :session-id (:session-id state)}] (rx/of (duplicate-shapes shapes) @@ -1027,18 +1063,19 @@ ptk/WatchEvent (watch [_ state stream] (let [selected (get-in state [:workspace-local :selected]) - dup #(-> (get-in state [:workspace-data :shapes-by-id %]) + dup #(-> (get-in state [:workspace-data :objects %]) (assoc :id (uuid/next))) shapes (map dup selected) - shape? #(not= (:type %) :canvas)] + shape? #(not= (:type %) :frame)] (cond (and (= (count shapes) 1) - (= (:type (first shapes)) :canvas)) - (rx/of (duplicate-canvas (first shapes) (first selected))) + (= (:type (first shapes)) :frame)) + (rx/of (duplicate-frame (first shapes) (first selected))) (and (pos? (count shapes)) (every? shape? shapes)) - (rx/of (duplicate-shapes shapes)) + ;; (rx/of (duplicate-shapes shapes)) + (rx/of (duplicate-shapes selected)) :else (rx/empty)))))) @@ -1067,12 +1104,13 @@ (update [_ state] (update state :workspace-local #(-> % (assoc :selected #{}) - (dissoc :selected-canvas)))))) + (dissoc :selected-frame)))))) + ;; --- Select Shapes (By selrect) (defn- impl-try-match-shape - [selrect acc {:keys [type id items] :as shape}] + [selrect acc {:keys [type id] :as shape}] (cond (geom/contained-in? shape selrect) (conj acc id) @@ -1087,15 +1125,14 @@ [state selrect] (let [data (:workspace-data state) match (partial impl-try-match-shape selrect) - shapes (:shapes data) - xf (comp (map #(get-in data [:shapes-by-id %])) - (remove :hidden) + xf (comp (remove :hidden) (remove :blocked) - (remove #(= :canvas (:type %))) + (remove #(= :frame (:type %))) + (remove #(= uuid/zero (:id %))) (map geom/shape->rect-shape) (map geom/resolve-rotation) (map geom/shape->rect-shape))] - (transduce xf match #{} shapes))) + (transduce xf match #{} (vals (:objects data))))) (def select-shapes-by-current-selrect (ptk/reify ::select-shapes-by-current-selrect @@ -1115,7 +1152,7 @@ IBatchedChange ptk/UpdateEvent (update [_ state] - (update-in state [:workspace-data :shapes-by-id id] merge attrs)))) + (update-in state [:workspace-data :objects id] merge attrs)))) ;; --- Update Page Options @@ -1182,7 +1219,7 @@ (watch [_ state stream] (let [selected (get-in state [:workspace-local :selected]) options (get-in state [:workspace-data :options]) - shapes (map #(get-in state [:workspace-data :shapes-by-id %]) selected) + shapes (map #(get-in state [:workspace-data :objects %]) selected) shape (geom/shapes->rect-shape shapes) displacement (if align? (get-displacement-with-grid shape direction options) @@ -1195,10 +1232,13 @@ (defn- impl-dissoc-shape "Given a shape id, removes it from the state." [state id] - (-> state - (update-in [:workspace-data :canvas] (fn [v] (filterv #(not= % id) v))) - (update-in [:workspace-data :shapes] (fn [v] (filterv #(not= % id) v))) - (update-in [:workspace-data :shapes-by-id] dissoc id))) + (let [data (:workspace-data state) + fid (get-in data [:rmap id]) + shp (get-in data [:frames fid :objects id]) + data (-> data + (update-in [:frames fid :shapes] (fn [s] (filterv #(not= % id) s))) + (update-in [:frames fid :objects] dissoc id))] + (assoc data :workspace-data data))) (defn- impl-purge-shapes [ids] @@ -1210,102 +1250,50 @@ (defn- delete-shapes [ids] (us/assert ::set-of-uuid ids) - (ptk/reify ::delete-canvas + (ptk/reify ::delete-shapes ptk/WatchEvent (watch [_ state stream] - (let [shapes-map (get-in state [:workspace-data :shapes-by-id]) + (let [objects (get-in state [:workspace-data :objects]) session-id (:session-id state) - shapes (->> (get-in state [:workspace-data :shapes]) - (map #(get shapes-map %)) - (d/enumerate) - (map (fn [[i s]] (assoc s ::index i))) - (filter #(contains? ids (:id %)))) + rchanges (mapv #(array-map :type :del-obj :id %) ids) + uchanges (mapv (fn [id] + (let [obj (get objects id) + frm (get objects (:frame-id obj)) + idx (d/index-of (:shapes frm) id)] + {:type :add-obj + :id id + :frame-id (:id frm) + :index idx + :obj obj})) + ids)] + (rx/of (commit-changes rchanges uchanges {:commit-local? true})))))) - rchanges (mapv (fn [item] - {:type :del-shape - :id (:id item) - :session-id session-id}) - shapes) - uchanges (mapv (fn [item] - {:type :add-shape - :id (:id item) - :shape (dissoc item ::index) - :session-id session-id - :index (::index item)}) - shapes)] - - (rx/of (impl-purge-shapes (map :id shapes)) - (commit-changes rchanges uchanges)))))) - -;; NOTE: this event has "repeated" logic; we want reuse the -;; `delete-shape` event here because we need to perform an atomic -;; operation that the user can undo in one step. - -(defn- delete-canvas +(defn- delete-frame [id] (ptk/reify ::delete-shapes ptk/WatchEvent (watch [_ state stream] - (let [shapes-map (get-in state [:workspace-data :shapes-by-id]) - session-id (:session-id state) - - shapes (->> (get-in state [:workspace-data :shapes]) - (map #(get shapes-map %)) - (d/enumerate) - (map (fn [[i s]] (assoc s ::index i))) - (filter (fn [s] (= (:canvas s) id)))) - - canvas (->> (get-in state [:workspace-data :canvas]) - (map #(get shapes-map %)) - (d/enumerate) - (map (fn [[i s]] (assoc s ::index i))) - (filter (fn [s] (= (:id s) id)))) - - rchanges1 (mapv (fn [item] - {:type :del-shape - :id (:id item) - :session-id session-id}) - shapes) - uchanges1 (mapv (fn [item] - {:type :add-shape - :id (:id item) - :shape (dissoc item ::index) - :session-id session-id - :index (::index item)}) - shapes) - rchanges2 (mapv (fn [item] - {:type :del-canvas - :id (:id item) - :session-id session-id}) - canvas) - uchanges2 (mapv (fn [item] - {:type :add-canvas - :id (:id item) - :shape (dissoc item ::index) - :session-id session-id - :index (::index item)}) - canvas)] - - (rx/of (impl-purge-shapes (d/concat [] (map :id shapes) (map :id canvas))) - (commit-changes (d/concat [] rchanges1 rchanges2) - (d/concat [] uchanges1 uchanges2))))))) + (let [objects (get-in state [:workspace-data :objects]) + obj (get objects id) + ids (d/concat #{} (:shapes obj) [(:id obj)])] + (rx/of (delete-shapes ids)))))) (def delete-selected "Deselect all and remove all selected shapes." (ptk/reify ::delete-selected ptk/WatchEvent (watch [_ state stream] - (let [lookup #(get-in state [:workspace-data :shapes-by-id %]) + (let [lookup #(get-in state [:workspace-data :objects %]) selected (get-in state [:workspace-local :selected]) shapes (map lookup selected) - shape? #(not= (:type %) :canvas)] + shape? #(not= (:type %) :frame)] (cond (and (= (count shapes) 1) - (= (:type (first shapes)) :canvas)) - (rx/of (delete-canvas (first selected))) + (= (:type (first shapes)) :frame)) + (rx/of (delete-frame (first selected))) (and (pos? (count shapes)) (every? shape? shapes)) @@ -1323,7 +1311,7 @@ (ptk/reify ::rename-shape ptk/WatchEvent (watch [_ state stream] - (let [shape (get-in state [:workspace-data :shapes-by-id id]) + (let [shape (get-in state [:workspace-data :objects id]) session-id (:session-id state) change {:type :mod-shape :id id @@ -1347,9 +1335,9 @@ ptk/UpdateEvent (update [_ state] (let [id (first (get-in state [:workspace-local :selected])) - type (get-in state [:workspace-data :shapes-by-id id :type])] + type (get-in state [:workspace-data :objects id :type])] ;; NOTE: multiple selection ordering not supported - (if (and id (not= type :canvas)) + (if (and id (not= type :frame)) (impl-order-shape state id loc) state))))) @@ -1376,11 +1364,12 @@ (ptk/reify ::change-shape-order ptk/UpdateEvent (update [_ state] - (let [shapes (get-in state [:workspace-data :shapes]) - shapes (into [] (remove #(= % id)) shapes) - [before after] (split-at index shapes) - shapes (d/concat [] before [id] after)] - (assoc-in state [:workspace-data :shapes] shapes))))) + (let [obj (get-in state [:workspace-data :objects id]) + frm (get-in state [:workspace-data :objects (:frame-id obj)]) + shp (remove #(= % id) (:shapes frm)) + [b a] (split-at index shp) + shp (d/concat [] b [id] a)] + (assoc-in state [:workspace-data :objects (:id frm) :shapes] shp))))) (defn commit-shape-order-change [id] @@ -1405,20 +1394,20 @@ :index prev-index}] (rx/of (commit-changes [change] [uchange])))))) -;; --- Change Canvas Order (D&D Ordering) +;; --- Change Frame Order (D&D Ordering) -(defn change-canvas-order +(defn change-frame-order [{:keys [id index] :as params}] (us/verify ::us/uuid id) (us/verify ::us/number index) - (ptk/reify ::change-canvas-order + (ptk/reify ::change-frame-order ptk/UpdateEvent (update [_ state] - (let [shapes (get-in state [:workspace-data :canvas]) + (let [shapes (get-in state [:workspace-data :frame]) shapes (into [] (remove #(= % id)) shapes) [before after] (split-at index shapes) shapes (vec (concat before [id] after))] - (assoc-in state [:workspace-data :canvas] shapes))))) + (assoc-in state [:workspace-data :frame] shapes))))) ;; --- Shape / Selection Alignment @@ -1429,7 +1418,7 @@ (ptk/reify ::initialize-shapes-align-in-bulk ptk/WatchEvent (watch [_ state stream] - (let [shapes-by-id (get-in state [:workspace-data :shapes-by-id]) + #_(let [shapes-by-id (get-in state [:workspace-data :objects]) shapes (mapv #(get shapes-by-id %) ids) sshape (geom/shapes->rect-shape shapes) point (gpt/point (:x1 sshape) @@ -1440,43 +1429,72 @@ ;; --- Temportal displacement for Shape / Selection -(defn- recalculate-shape-canvas-relation - [state shape] - (let [shape' (geom/shape->rect-shape shape) - xfmt (comp (map #(get-in state [:workspace-data :shapes-by-id %])) - (map geom/shape->rect-shape) - (filter #(geom/overlaps? % shape')) - (map :id)) - - id (->> (get-in state [:workspace-data :canvas]) - (into [] xfmt) - (first))] - (assoc shape :canvas id))) +(defn- rehash-shape-frame-relationship + [ids] + (letfn [(impl-diff [state] + (loop [id (first ids) + ids (rest ids) + rch [] + uch []] + (if (nil? id) + [rch uch] + (let [dta (:workspace-data state) + obj (get-in dta [:objects id]) + fid (calculate-frame-overlap dta obj)] + (if (not= fid (:frame-id obj)) + (recur (first ids) + (rest ids) + (conj rch {:type :mov-obj + :id id + :frame-id fid}) + (conj uch {:type :mov-obj + :id id + :frame-id (:frame-id obj)})) + (recur (first ids) + (rest ids) + rch + uch))))))] + (ptk/reify ::rehash-shape-frame-relationship + ptk/WatchEvent + (watch [_ state stream] + (let [[rch uch] (impl-diff state)] + ;; (prn "rehash-shape-frame-relationship" rch) + ;; (prn "rehash-shape-frame-relationship" uch) + (when-not (empty? rch) + (rx/of (commit-changes rch uch {:commit-local? true})))))))) (defn assoc-resize-modifier-in-bulk [ids xfmt] + ;; (prn "assoc-resize-modifier-in-bulk" ids) (us/verify ::set-of-uuid ids) (us/verify gmt/matrix? xfmt) (ptk/reify ::assoc-resize-modifier-in-bulk ptk/UpdateEvent (update [_ state] - (reduce #(assoc-in %1 [:workspace-data :shapes-by-id %2 :resize-modifier] xfmt) state ids)))) + (reduce #(assoc-in %1 [:workspace-data :objects %2 :resize-modifier] xfmt) state ids)))) (defn materialize-resize-modifier-in-bulk [ids] (letfn [(process-shape [state id] - (let [shape (get-in state [:workspace-data :shapes-by-id id]) - modifier (:resize-modifier shape (gmt/matrix)) - - shape (-> (dissoc shape :resize-modifier) - (geom/transform modifier)) - shape (recalculate-shape-canvas-relation state shape)] - (assoc-in state [:workspace-data :shapes-by-id id] shape)))] + (update-in state [:workspace-data :objects id] + (fn [shape] + (let [mfr (:resize-modifier shape (gmt/matrix))] + (-> (dissoc shape :resize-modifier) + (geom/transform mfr))))))] (ptk/reify ::materialize-resize-modifier-in-bulk - IBatchedChange ptk/UpdateEvent (update [_ state] - (reduce process-shape state ids))))) + + ;; (prn "materialize-resize-modifier-in-bulk$update" ids) + + (reduce process-shape state ids)) + + ptk/WatchEvent + (watch [_ state stream] + ;; (prn "materialize-resize-modifier-in-bulk$watch" ids) + (rx/of diff-and-commit-changes + (rehash-shape-frame-relationship ids)))))) + (defn apply-displacement-in-bulk "Apply the same displacement delta to all shapes identified by the @@ -1485,11 +1503,12 @@ (us/verify ::set-of-uuid ids) (us/verify gpt/point? delta) (letfn [(process-shape [state id] - (let [shape (get-in state [:workspace-data :shapes-by-id id]) - prev (:displacement-modifier shape (gmt/matrix)) - curr (gmt/translate prev delta)] + (let [objects (get-in state [:workspace-data :objects]) + shape (get objects id) + prev (:displacement-modifier shape (gmt/matrix)) + curr (gmt/translate prev delta)] (->> (assoc shape :displacement-modifier curr) - (assoc-in state [:workspace-data :shapes-by-id id]))))] + (assoc-in state [:workspace-data :objects id]))))] (ptk/reify ::apply-displacement-in-bulk ptk/UpdateEvent (update [_ state] @@ -1498,75 +1517,81 @@ (defn materialize-displacement-in-bulk [ids] (letfn [(process-shape [state id] - (let [shape (get-in state [:workspace-data :shapes-by-id id]) - modifier (:displacement-modifier shape (gmt/matrix)) + (update-in state [:workspace-data :objects id] + (fn [shape] + (let [mtx (:displacement-modifier shape (gmt/matrix))] + (-> (dissoc shape :displacement-modifier) + (geom/transform mtx))))))] - shape (-> (dissoc shape :displacement-modifier) - (geom/transform modifier)) - shape (recalculate-shape-canvas-relation state shape)] - (assoc-in state [:workspace-data :shapes-by-id id] shape)))] (ptk/reify ::materialize-displacement-in-bulk - IBatchedChange ptk/UpdateEvent (update [_ state] - (reduce process-shape state ids))))) + (reduce process-shape state ids)) + + ptk/WatchEvent + (watch [_ state stream] + (rx/of diff-and-commit-changes + (rehash-shape-frame-relationship ids)))))) -(defn apply-canvas-displacement +(defn apply-frame-displacement "Apply the same displacement delta to all shapes identified by the set if ids." [id delta] (us/verify ::us/uuid id) (us/verify gpt/point? delta) - (ptk/reify ::apply-canvas-displacement + (ptk/reify ::apply-frame-displacement ptk/UpdateEvent (update [_ state] - (let [shape (get-in state [:workspace-data :shapes-by-id id]) + (let [shape (get-in state [:workspace-data :objects id]) prev-xfmt (:displacement-modifier shape (gmt/matrix)) xfmt (gmt/translate prev-xfmt delta)] (->> (assoc shape :displacement-modifier xfmt) - (assoc-in state [:workspace-data :shapes-by-id id])))))) + (assoc-in state [:workspace-data :objects id])))))) -(defn materialize-canvas-displacement +(defn materialize-frame-displacement [id] (us/verify ::us/uuid id) - (ptk/reify ::materialize-canvas-displacement + (ptk/reify ::materialize-frame-displacement IBatchedChange ptk/UpdateEvent (update [_ state] - (let [data (:workspace-data state) - shapes-map (:shapes-by-id data) + ;; (prn "materialize-frame-displacement") + (let [objects (get-in state [:workspace-data :objects]) + frame (get objects id) + xmt (or (:displacement-modifier frame) (gmt/matrix)) - canvas (get shapes-map id) + frame (-> frame + (dissoc :displacement-modifier) + (geom/transform xmt)) - xfmt (or (:displacement-modifier canvas) (gmt/matrix)) + shapes (->> (:shapes frame) + (map #(get objects %)) + (map #(geom/transform % xmt)) + (d/index-by :id)) - canvas (-> canvas - (dissoc :displacement-modifier) - (geom/transform xfmt)) + shapes (assoc shapes (:id frame) frame)] - shapes (->> (:shapes data []) - (map #(get shapes-map %)) - (filter #(= (:canvas %) id)) - (map #(geom/transform % xfmt))) - - shapes (d/index-by :id shapes) - shapes (assoc shapes (:id canvas) canvas)] - - (update-in state [:workspace-data :shapes-by-id] merge shapes))))) + (update-in state [:workspace-data :objects] merge shapes))))) (defn commit-changes - ([changes undo-changes] (commit-changes changes undo-changes true)) - ([changes undo-changes save-undo?] + ([changes undo-changes] (commit-changes changes undo-changes {})) + ([changes undo-changes {:keys [save-undo? + commit-local?] + :or {save-undo? true + commit-local? false} + :as opts}] (us/verify ::cp/changes changes) (us/verify ::cp/changes undo-changes) + (ptk/reify ::commit-changes ptk/UpdateEvent (update [_ state] (let [pid (get-in state [:workspace-page :id]) - data (get-in state [:pages-data pid])] - (update-in state [:pages-data pid] cp/process-changes changes))) + state (update-in state [:pages-data pid] cp/process-changes changes)] + (cond-> state + commit-local? (update-in [:workspace-data] cp/process-changes changes)))) ptk/WatchEvent (watch [_ state stream] @@ -1575,6 +1600,7 @@ params {:id (:id page) :revn (:revn page) :changes (vec changes)}] + (rx/concat (when (and save-undo? (not= uidx ::not-found)) (rx/of (reset-undo uidx))) @@ -1588,23 +1614,23 @@ (rx/map shapes-changes-commited)))))))) -(defn- check-page-integrity - [data] - (let [items (d/concat (:shapes data) - (:canvas data))] - (loop [id (first items) - ids (rest items)] - (let [content (get-in data [:shapes-by-id id])] - (cond - (nil? id) - nil - (nil? content) - (ex/raise :type :validation - :code :shape-integrity - :context {:id id}) - - :else - (recur (first ids) (rest ids))))))) +;; (defn- check-page-integrity +;; [data] +;; (let [items (d/concat (:shapes data) +;; (:frame data))] +;; (loop [id (first items) +;; ids (rest items)] +;; (let [content (get-in data [:objects id])] +;; (cond +;; (nil? id) +;; nil +;; (nil? content) +;; (ex/raise :type :validation +;; :code :shape-integrity +;; :context {:id id}) +;; +;; :else +;; (recur (first ids) (rest ids))))))) (s/def ::shapes-changes-commited (s/keys :req-un [::page-id ::revn ::cp/changes])) @@ -1615,6 +1641,9 @@ (ptk/reify ::shapes-changes-commited ptk/UpdateEvent (update [_ state] + ;; (prn "shapes-changes-commited$update" + ;; (get-in state [:workspace-page :id]) + ;; page-id) (-> state (assoc-in [:workspace-page :revn] revn) (assoc-in [:pages page-id :revn] revn) @@ -1623,7 +1652,7 @@ ptk/EffectEvent (effect [_ state stream] - (when *assert* + #_(when *assert* (check-page-integrity (:workspace-data state)))))) ;; --- Start shape "edition mode" @@ -1688,7 +1717,7 @@ IBatchedChange ptk/UpdateEvent (update [_ state] - (update-in state [:workspace-data :shapes-by-id id] geom/resize-rect attr value)))) + (update-in state [:workspace-data :objects id] geom/resize-rect attr value)))) (defn update-circle-dimensions [id attr value] @@ -1699,7 +1728,7 @@ IBatchedChange ptk/UpdateEvent (update [_ state] - (update-in state [:workspace-data :shapes-by-id id] geom/resize-rect attr value)))) + (update-in state [:workspace-data :objects id] geom/resize-rect attr value)))) ;; --- Shape Proportions @@ -1708,11 +1737,11 @@ (ptk/reify ::toggle-shape-proportion-lock ptk/UpdateEvent (update [_ state] - (let [shape (get-in state [:workspace-data :shapes-by-id id])] + (let [shape (get-in state [:workspace-data :objects id])] (if (:proportion-lock shape) - (assoc-in state [:workspace-data :shapes-by-id id :proportion-lock] false) + (assoc-in state [:workspace-data :objects id :proportion-lock] false) (->> (geom/assign-proportions (assoc shape :proportion-lock true)) - (assoc-in state [:workspace-data :shapes-by-id id]))))))) + (assoc-in state [:workspace-data :objects id]))))))) ;; --- Update Shape Position @@ -1728,7 +1757,7 @@ (ptk/reify ::update-position ptk/UpdateEvent (update [_ state] - (update-in state [:workspace-data :shapes-by-id id] + (update-in state [:workspace-data :objects id] geom/absolute-move position)))) ;; --- Path Modifications @@ -1742,7 +1771,7 @@ (ptk/reify ::update-path ptk/UpdateEvent (update [_ state] - (update-in state [:workspace-data :shapes-by-id id :segments index] gpt/add delta)))) + (update-in state [:workspace-data :objects id :segments index] gpt/add delta)))) ;; --- Initial Path Point Alignment @@ -1750,7 +1779,7 @@ (deftype InitialPathPointAlign [id index] ptk/WatchEvent (watch [_ state s] - (let [shape (get-in state [:workspace-data :shapes-by-id id]) + (let [shape (get-in state [:workspace-data :objects id]) point (get-in shape [:segments index])] (->> (uwrk/align-point point) (rx/map #(update-path id index %)))))) @@ -1786,7 +1815,7 @@ (update [_ state] (impl-update-shape-hidden state id false)))) -(defn hide-canvas +(defn hide-frame [id] (us/verify ::us/uuid id) (ptk/reify ::hide-shape @@ -1794,12 +1823,12 @@ ptk/UpdateEvent (update [_ state] (let [hide #(impl-update-shape-hidden %1 %2 true) - ids (->> (vals (get-in state [:workspace-data :shapes-by-id])) - (filter #(= (:canvas %) id)) + ids (->> (vals (get-in state [:workspace-data :objects])) + (filter #(= (:frame %) id)) (map :id))] (reduce hide state (cons id ids)))))) -(defn show-canvas +(defn show-frame [id] (us/verify ::us/uuid id) (ptk/reify ::hide-shape @@ -1807,14 +1836,14 @@ ptk/UpdateEvent (update [_ state] (let [show #(impl-update-shape-hidden %1 %2 false) - ids (->> (vals (get-in state [:workspace-data :shapes-by-id])) - (filter #(= (:canvas %) id)) + ids (->> (vals (get-in state [:workspace-data :objects])) + (filter #(= (:frame %) id)) (map :id))] (reduce show state (cons id ids)))))) (defn- impl-update-shape-hidden [state id hidden?] - (assoc-in state [:workspace-data :shapes-by-id id :hidden] hidden?)) + (assoc-in state [:workspace-data :objects id :hidden] hidden?)) ;; --- Shape Blocking @@ -1838,29 +1867,29 @@ (defn- impl-update-shape-blocked [state id hidden?] - (let [type (get-in state [:workspace-data :shapes-by-id id :type]) - state (update-in state [:workspace-data :shapes-by-id id] assoc :blocked hidden?)] + (let [type (get-in state [:workspace-data :objects id :type]) + state (update-in state [:workspace-data :objects id] assoc :blocked hidden?)] (cond-> state - (= type :canvas) - (update-in [:workspace-data :shapes-by-id] + (= type :frame) + (update-in [:workspace-data :objects] (fn [shapes] - (reduce-kv (fn [shapes key {:keys [canvas] :as val}] + (reduce-kv (fn [shapes key {:keys [frame] :as val}] (cond-> shapes - (= id canvas) (update key assoc :blocked hidden?))) + (= id frame) (update key assoc :blocked hidden?))) shapes shapes)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Canvas Interactions +;; Frame Interactions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn select-canvas +(defn select-frame [id] (us/verify ::us/uuid id) - (ptk/reify ::select-canvas + (ptk/reify ::select-frame ptk/UpdateEvent (update [_ state] - (update state :workspace-local assoc :selected-canvas id)))) + (update state :workspace-local assoc :selected-frame id)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Navigation diff --git a/frontend/src/uxbox/main/exports.cljs b/frontend/src/uxbox/main/exports.cljs index 381465a72..c52690ef9 100644 --- a/frontend/src/uxbox/main/exports.cljs +++ b/frontend/src/uxbox/main/exports.cljs @@ -11,7 +11,7 @@ [rumext.alpha :as mf] [uxbox.util.math :as mth] [uxbox.main.geom :as geom] - [uxbox.main.ui.shapes.canvas :as canvas] + [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] @@ -40,7 +40,7 @@ [{:keys [shape] :as props}] (when (and shape (not (:hidden shape))) (case (:type shape) - :canvas [:& rect/rect-shape {:shape shape}] + :frame [:& rect/rect-shape {:shape shape}] :curve [:& path/path-shape {:shape shape}] :text [:& text/text-shape {:shape shape}] :icon [:& icon/icon-shape {:shape shape}] @@ -53,7 +53,7 @@ [{:keys [data] :as props}] (let [shapes-by-id (:shapes-by-id data) shapes (map #(get shapes-by-id %) (:shapes data [])) - canvas (map #(get shapes-by-id %) (:canvas data [])) + frame (map #(get shapes-by-id %) (:frame data [])) dim (calculate-dimensions data)] [:svg {:view-box (str "0 0 " (:width dim 0) " " (:height dim 0)) :version "1.1" @@ -61,7 +61,7 @@ :xmlns "http://www.w3.org/2000/svg"} (background) [:* - (for [item canvas] + (for [item frame] [:& shape-wrapper {:shape item :key (:id item)}]) (for [item shapes] [:& shape-wrapper {:shape item :key (:id item)}])]])) diff --git a/frontend/src/uxbox/main/geom.cljs b/frontend/src/uxbox/main/geom.cljs index f838e7ba5..5939f3a13 100644 --- a/frontend/src/uxbox/main/geom.cljs +++ b/frontend/src/uxbox/main/geom.cljs @@ -27,7 +27,7 @@ :icon (move-rect shape dpoint) :image (move-rect shape dpoint) :rect (move-rect shape dpoint) - :canvas (move-rect shape dpoint) + :frame (move-rect shape dpoint) :text (move-rect shape dpoint) :curve (move-path shape dpoint) :path (move-path shape dpoint) @@ -69,7 +69,7 @@ [shape position] (case (:type shape) :icon (absolute-move-rect shape position) - :canvas (absolute-move-rect shape position) + :frame (absolute-move-rect shape position) :image (absolute-move-rect shape position) :rect (absolute-move-rect shape position) :circle (absolute-move-circle shape position))) @@ -482,6 +482,25 @@ :width width :height height))) +;; --- Resolve Shape + +(declare resolve-rect-shape) +(declare translate-from-frame) +(declare translate-to-frame) + +(defn resolve-shape + [objects shape] + (case (:type shape) + :rect (resolve-rect-shape objects shape) + :frame (resolve-rect-shape objects shape))) + +(defn- resolve-rect-shape + [objects {:keys [parent] :as shape}] + (loop [pobj (get objects parent)] + (if (= :frame (:type pobj)) + (translate-from-frame shape pobj) + (recur (get objects (:parent pobj)))))) + ;; --- Transform Shape (declare transform-rect) @@ -492,7 +511,7 @@ "Apply the matrix transformation to shape." [{:keys [type] :as shape} xfmt] (case type - :canvas (transform-rect shape xfmt) + :frame (transform-rect shape xfmt) :rect (transform-rect shape xfmt) :icon (transform-rect shape xfmt) :text (transform-rect shape xfmt) @@ -599,6 +618,14 @@ :height (- maxy miny) :type :rect})) +(defn translate-to-frame + [shape {:keys [x y] :as frame}] + (move shape (gpt/point (- x) (- y)))) + +(defn translate-from-frame + [shape {:keys [x y] :as frame}] + (move shape (gpt/point (+ x) (+ y)))) + ;; --- Helpers (defn contained-in? diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index d2b4bf892..c9ea3b3d3 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -59,8 +59,8 @@ (-> (l/lens #(contains? % id)) (l/derive selected-shapes))) -(def selected-canvas - (-> (l/key :selected-canvas) +(def selected-frame + (-> (l/key :selected-frame) (l/derive workspace-local))) (def toolboxes diff --git a/frontend/src/uxbox/main/ui/shapes.cljs b/frontend/src/uxbox/main/ui/shapes.cljs index 8df0648c6..25890599f 100644 --- a/frontend/src/uxbox/main/ui/shapes.cljs +++ b/frontend/src/uxbox/main/ui/shapes.cljs @@ -13,7 +13,7 @@ [rumext.alpha :as mf] [uxbox.main.refs :as refs] [uxbox.main.store :as st] - [uxbox.main.ui.shapes.canvas :as canvas])) + [uxbox.main.ui.shapes.frame :as frame])) -(def shape-wrapper canvas/shape-wrapper) -(def canvas-wrapper canvas/canvas-wrapper) +(def shape-wrapper frame/shape-wrapper) +(def frame-wrapper frame/frame-wrapper) diff --git a/frontend/src/uxbox/main/ui/shapes/attrs.cljs b/frontend/src/uxbox/main/ui/shapes/attrs.cljs index feeacdcd2..61a7850d6 100644 --- a/frontend/src/uxbox/main/ui/shapes/attrs.cljs +++ b/frontend/src/uxbox/main/ui/shapes/attrs.cljs @@ -5,7 +5,9 @@ ;; Copyright (c) 2016-2017 Andrey Antukh (ns uxbox.main.ui.shapes.attrs - (:require [cuerdas.core :as str])) + (:require + [cuerdas.core :as str] + [uxbox.util.interop :as interop])) ;; (defn camel-case @@ -58,7 +60,8 @@ (case style :mixed "5,5,1,5" :dotted "5,5" - :dashed "10,10")) + :dashed "10,10" + nil)) (defn- transform-stroke-attrs [{:keys [stroke-style] :or {stroke-style :none} :as attrs}] @@ -75,3 +78,21 @@ (-> (select-keys shape shape-style-attrs) (transform-stroke-attrs) (process-attrs))) + + +;; TODO: migrate all the code to use this function and then, rename. + +(defn extract-style-attrs2 + [shape] + (let [stroke-style (:stroke-style shape :none) + attrs #js {:fill (:fill-color shape nil) + :opacity (:opacity shape nil) + :rx (:rx shape nil) + :ry (:ry shape nil)}] + (when (not= :none stroke-style) + (interop/obj-assign! attrs + #js {:stroke (:stroke-color shape nil) + :strokeWidth (:stroke-width shape nil) + :strokeOpacity (:stroke-opacity shape nil) + :strokeDasharray (stroke-type->dasharray stroke-style)})) + attrs)) diff --git a/frontend/src/uxbox/main/ui/shapes/common.cljs b/frontend/src/uxbox/main/ui/shapes/common.cljs index d9254fce5..fc7644af1 100644 --- a/frontend/src/uxbox/main/ui/shapes/common.cljs +++ b/frontend/src/uxbox/main/ui/shapes/common.cljs @@ -43,21 +43,21 @@ (rx/of (dw/materialize-displacement-in-bulk selected) ::dw/page-data-update)))))) -(def start-move-canvas +(def start-move-frame (ptk/reify ::start-move-selected ptk/WatchEvent (watch [_ state stream] (let [flags (get-in state [:workspace-local :flags]) selected (get-in state [:workspace-local :selected]) stoper (rx/filter uws/mouse-up? stream) - canvas-id (first selected) + frame-id (first selected) position @uws/mouse-position] (rx/concat (->> (uws/mouse-position-deltas position) - (rx/map #(dw/apply-canvas-displacement canvas-id %)) + (rx/map #(dw/apply-frame-displacement frame-id %)) (rx/take-until stoper)) - (rx/of (dw/materialize-canvas-displacement canvas-id))))))) + (rx/of (dw/materialize-frame-displacement frame-id))))))) (defn on-mouse-down ([event shape] (on-mouse-down event shape nil)) @@ -70,10 +70,10 @@ drawing? nil - (= type :canvas) + (= type :frame) (when selected? (dom/stop-propagation event) - (st/emit! start-move-canvas)) + (st/emit! start-move-frame)) (and (not selected?) (empty? selected)) (do diff --git a/frontend/src/uxbox/main/ui/shapes/canvas.cljs b/frontend/src/uxbox/main/ui/shapes/frame.cljs similarity index 68% rename from frontend/src/uxbox/main/ui/shapes/canvas.cljs rename to frontend/src/uxbox/main/ui/shapes/frame.cljs index f6c384f01..eeb820c94 100644 --- a/frontend/src/uxbox/main/ui/shapes/canvas.cljs +++ b/frontend/src/uxbox/main/ui/shapes/frame.cljs @@ -8,7 +8,7 @@ ;; Copyright (c) 2015-2020 Andrey Antukh ;; Copyright (c) 2015-2020 Juan de la Cruz -(ns uxbox.main.ui.shapes.canvas +(ns uxbox.main.ui.shapes.frame (:require [lentes.core :as l] [rumext.alpha :as mf] @@ -30,14 +30,24 @@ [uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.point :as gpt])) -(declare canvas-wrapper) +(declare frame-wrapper) + + +(defn wrap-memo-shape + ([component] + (js/React.memo + component + (fn [np op] + (let [n-shape (aget np "shape") + o-shape (aget op "shape")] + (= n-shape o-shape)))))) (mf/defc shape-wrapper - {:wrap [#(mf/wrap-memo % =)]} + {:wrap [wrap-memo-shape]} [{:keys [shape] :as props}] (when (and shape (not (:hidden shape))) (case (:type shape) - :canvas [:& canvas-wrapper {:shape shape :childs []}] + :frame [:& frame-wrapper {:shape shape :childs []}] :curve [:& path/path-wrapper {:shape shape}] :text [:& text/text-wrapper {:shape shape}] :icon [:& icon/icon-wrapper {:shape shape}] @@ -46,22 +56,48 @@ :image [:& image/image-wrapper {:shape shape}] :circle [:& circle/circle-wrapper {:shape shape}]))) -(def canvas-default-props +(def frame-default-props {:fill-color "#ffffff"}) -(declare canvas-shape) -(declare translate-to-canvas) +(declare frame-shape) +(declare translate-to-frame) -(mf/defc canvas-wrapper - {:wrap [#(mf/wrap-memo % =)]} - [{:keys [shape childs] :as props}] +(def kaka [1 2 3]) + +(defn wrap-memo-frame + ([component] + (js/React.memo + component + (fn [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))))))))) + + +(mf/defc frame-wrapper + {:wrap [wrap-memo-frame]} + [{:keys [shape objects] :as props}] (when (and shape (not (:hidden shape))) (let [selected-iref (mf/use-memo {:fn #(refs/make-selected (:id shape)) :deps (mf/deps (:id shape))}) selected? (mf/deref selected-iref) on-mouse-down #(common/on-mouse-down % shape) - shape (merge canvas-default-props shape) + shape (merge frame-default-props shape) + + childs (mapv #(get objects %) (:shapes shape)) on-double-click (fn [event] @@ -71,9 +107,9 @@ [:g {:class (when selected? "selected") :on-double-click on-double-click :on-mouse-down on-mouse-down} - [:& canvas-shape {:shape shape :childs childs}]]))) + [:& frame-shape {:shape shape :childs childs}]]))) -(mf/defc canvas-shape +(mf/defc frame-shape [{:keys [shape childs] :as props}] (let [rotation (:rotation shape) ds-modifier (:displacement-modifier shape) @@ -93,7 +129,7 @@ :height height )) - translate #(translate-to-canvas % ds-modifier (gpt/point (- x) (- y))) + translate #(translate-to-frame % ds-modifier (gpt/point (- x) (- y))) ] [:svg {:x x :y y :width width :height height} @@ -101,12 +137,12 @@ (for [item childs] [:& shape-wrapper {:shape (translate item) :key (:id item)}])])) -(defn- translate-to-canvas - [shape canvas-ds-modifier pt] +(defn- translate-to-frame + [shape frame-ds-modifier pt] (let [rz-modifier (:resize-modifier shape) shape (cond-> shape - (gmt/matrix? canvas-ds-modifier) - (geom/transform canvas-ds-modifier) + (gmt/matrix? frame-ds-modifier) + (geom/transform frame-ds-modifier) (gmt/matrix? rz-modifier) (-> (geom/transform rz-modifier) diff --git a/frontend/src/uxbox/main/ui/shapes/rect.cljs b/frontend/src/uxbox/main/ui/shapes/rect.cljs index 6aa881149..f30376b33 100644 --- a/frontend/src/uxbox/main/ui/shapes/rect.cljs +++ b/frontend/src/uxbox/main/ui/shapes/rect.cljs @@ -12,8 +12,7 @@ [uxbox.main.refs :as refs] [uxbox.main.ui.shapes.attrs :as attrs] [uxbox.main.ui.shapes.common :as common] - [uxbox.util.data :refer [classnames normalize-props]] - [uxbox.util.dom :as dom] + [uxbox.util.interop :as interop] [uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.point :as gpt])) @@ -21,23 +20,19 @@ (declare rect-shape) -(mf/defc rect-wrapper - {:wrap [#(mf/wrap-memo % =)]} - [{:keys [shape] :as props}] - (let [selected-iref (mf/use-memo - {:fn #(refs/make-selected (:id shape)) - :deps (mf/deps (:id shape))}) - selected? (mf/deref selected-iref) +(mf/defrc rect-wrapper + [props] + (let [shape (unchecked-get props "shape") on-mouse-down #(common/on-mouse-down % shape)] - [:g.shape {:class (when selected? "selected") - :on-mouse-down on-mouse-down} + [:g.shape {:on-mouse-down on-mouse-down} [:& rect-shape {:shape shape}]])) ;; --- Rect Shape -(mf/defc rect-shape - [{:keys [shape] :as props}] - (let [ds-modifier (:displacement-modifier shape) +(mf/defrc rect-shape + [props] + (let [shape (unchecked-get props "shape") + ds-modifier (:displacement-modifier shape) rz-modifier (:resize-modifier shape) shape (cond-> shape @@ -52,12 +47,13 @@ (+ x (/ width 2)) (+ y (/ height 2)))) - props (-> (attrs/extract-style-attrs shape) - (assoc :x x - :y y - :transform transform - :id (str "shape-" id) - :width width - :height height - ))] - [:& "rect" props])) + props (-> (attrs/extract-style-attrs2 shape) + (interop/obj-assign! + #js {:x x + :y y + :transform transform + :id (str "shape-" id) + :width width + :height height}))] + + [:> "rect" props])) diff --git a/frontend/src/uxbox/main/ui/workspace.cljs b/frontend/src/uxbox/main/ui/workspace.cljs index 8e0eabbca..ae5c9d111 100644 --- a/frontend/src/uxbox/main/ui/workspace.cljs +++ b/frontend/src/uxbox/main/ui/workspace.cljs @@ -45,10 +45,10 @@ (st/emit! (ms/->ScrollEvent (gpt/point left top))))) (defn- on-wheel - [event canvas] + [event frame] (when (kbd/ctrl? event) (let [prev-zoom @refs/selected-zoom - dom (mf/ref-node canvas) + dom (mf/ref-node frame) scroll-position (scroll/get-current-position-absolute dom) mouse-point @ms/mouse-position] (dom/prevent-default event) @@ -60,7 +60,7 @@ (mf/defc workspace-content [{:keys [page file flags] :as params}] - (let [canvas (mf/use-ref nil) + (let [frame (mf/use-ref nil) layout (mf/deref refs/workspace-layout) left-sidebar? (not (empty? (keep layout [:layers :sitemap :document-history]))) @@ -77,7 +77,7 @@ [:section.workspace-content {:class classes :on-scroll on-scroll - :on-wheel #(on-wheel % canvas)} + :on-wheel #(on-wheel % frame)} [:& history-dialog] @@ -87,7 +87,7 @@ [:& horizontal-rule] [:& vertical-rule]]) - [:section.workspace-viewport {:id "workspace-viewport" :ref canvas} + [:section.workspace-viewport {:id "workspace-viewport" :ref frame} [:& viewport {:page page}]]] ;; Aside diff --git a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs index 33a95c45f..c3f4beccc 100644 --- a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs +++ b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs @@ -52,7 +52,7 @@ :fill-color "#000000" :fill-opacity 0 :segments []} - {:type :canvas + {:type :frame :name "Canvas"} {:type :curve :name "Path" @@ -281,8 +281,8 @@ shape (dissoc shape ::initialized? :resize-modifier)] ;; Add & select the created shape to the workspace (rx/of dw/deselect-all - (if (= :canvas (:type shape)) - (dw/add-canvas shape) + (if (= :frame (:type shape)) + (dw/add-frame shape) (dw/add-shape shape)))))))))) (def close-drawing-path diff --git a/frontend/src/uxbox/main/ui/workspace/header.cljs b/frontend/src/uxbox/main/ui/workspace/header.cljs index a0948bbcd..5ce1a2083 100644 --- a/frontend/src/uxbox/main/ui/workspace/header.cljs +++ b/frontend/src/uxbox/main/ui/workspace/header.cljs @@ -94,9 +94,9 @@ [:div.workspace-options [:ul.options-btn [:li.tooltip.tooltip-bottom - {:alt (tr "workspace.header.canvas") - :class (when (= selected-drawtool :canvas) "selected") - :on-click (partial select-drawtool :canvas)} + {:alt (tr "workspace.header.frame") + :class (when (= selected-drawtool :frame) "selected") + :on-click (partial select-drawtool :frame)} i/artboard] [:li.tooltip.tooltip-bottom {:alt (tr "workspace.header.rect") diff --git a/frontend/src/uxbox/main/ui/workspace/selection.cljs b/frontend/src/uxbox/main/ui/workspace/selection.cljs index 06ed474f7..80c090715 100644 --- a/frontend/src/uxbox/main/ui/workspace/selection.cljs +++ b/frontend/src/uxbox/main/ui/workspace/selection.cljs @@ -133,7 +133,7 @@ :stroke-opacity "1"}}] (when (and (fn? on-rotate) - (not= :canvas (:type shape))) + (not= :frame (:type shape))) [:* [:path {:stroke "#31EFB8" :stroke-opacity "1" @@ -255,7 +255,7 @@ :on-resize on-resize}])) (mf/defc single-selection-handlers - [{:keys [shape zoom] :as props}] + [{:keys [shape zoom objects] :as props}] (let [on-resize #(do (dom/stop-propagation %2) (st/emit! (start-resize %1 #{(:id shape)} shape))) on-rotate #(do (dom/stop-propagation %) @@ -263,6 +263,7 @@ ds-modifier (:displacement-modifier shape) rz-modifier (:resize-modifier shape) + ;; shape (geom/resolve-shape objects shape) shape (cond-> (geom/shape->rect-shape shape) (gmt/matrix? rz-modifier) (geom/transform rz-modifier) (gmt/matrix? ds-modifier) (geom/transform ds-modifier))] @@ -274,11 +275,13 @@ (mf/defc selection-handlers [{:keys [selected edition zoom] :as props}] - (let [data (mf/deref refs/workspace-data) + (let [data (mf/deref refs/workspace-data) + objects (:objects data) + ;; We need remove posible nil values because on shape ;; deletion many shape will reamin selected and deleted ;; in the same time for small instant of time - shapes (->> (map #(get-in data [:shapes-by-id %]) selected) + shapes (->> (map #(get objects %) selected) (remove nil?)) num (count shapes) {:keys [id type] :as shape} (first shapes)] @@ -303,4 +306,5 @@ :else [:& single-selection-handlers {:shape shape + :objects objects :zoom zoom}]))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs index 67b0abd13..52c8d343d 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs @@ -21,6 +21,7 @@ [uxbox.main.ui.shapes.icon :as icon] [uxbox.main.ui.workspace.sortable :refer [use-sortable]] [uxbox.util.dom :as dom] + [uxbox.util.uuid :as uuid] [uxbox.util.i18n :as i18n :refer [t]])) (def ^:private shapes-iref @@ -76,34 +77,35 @@ {:on-double-click on-click} (:name shape "")]))) -;; --- Layer Item + +(def strip-attrs + #(select-keys % [:id :frame :name :type :hidden :blocked])) (mf/defc layer-item - {:wrap [#(mf/wrap-memo % =)]} - [{:keys [shape selected index] :as props}] - (let [selected? (contains? selected (:id shape)) - + {:wrap [mf/wrap-memo]} + [{:keys [index item selected] :as props}] + (let [selected? (contains? selected (:id item)) toggle-blocking (fn [event] (dom/stop-propagation event) - (if (:blocked shape) - (st/emit! (dw/unblock-shape (:id shape))) - (st/emit! (dw/block-shape (:id shape))))) + (if (:blocked item) + (st/emit! (dw/unblock-shape (:id item))) + (st/emit! (dw/block-shape (:id item))))) toggle-visibility (fn [event] (dom/stop-propagation event) - (if (:hidden shape) - (st/emit! (dw/show-shape (:id shape))) - (st/emit! (dw/hide-shape (:id shape))))) + (if (:hidden item) + (st/emit! (dw/show-shape (:id item))) + (st/emit! (dw/hide-shape (:id item))))) select-shape (fn [event] (dom/prevent-default event) - (let [id (:id shape)] + (let [id (:id item)] (cond - (or (:blocked shape) - (:hidden shape)) + (or (:blocked item) + (:hidden item)) nil (.-ctrlKey event) @@ -118,7 +120,7 @@ on-drop (fn [item monitor] - (st/emit! (dw/commit-shape-order-change (:shape-id item)))) + #_(st/emit! (dw/commit-shape-order-change (:shape-id item)))) on-hover (fn [item monitor] @@ -126,8 +128,8 @@ [dprops dnd-ref] (use-sortable {:type "layer-item" - :data {:shape-id (:id shape) - :page-id (:page shape) + :data {:shape-id (:id item) + :page-id (:page item) :index index} :on-hover on-hover :on-drop on-drop})] @@ -139,24 +141,22 @@ :on-click select-shape :on-double-click #(dom/stop-propagation %)} [:div.element-actions - [:div.toggle-element {:class (when-not (:hidden shape) "selected") + [:div.toggle-element {:class (when-not (:hidden item) "selected") :on-click toggle-visibility} i/eye] - [:div.block-element {:class (when (:blocked shape) "selected") + [:div.block-element {:class (when (:blocked item) "selected") :on-click toggle-blocking} i/lock]] - [:& element-icon {:shape shape}] - [:& layer-name {:shape shape}]]])) + [:& element-icon {:shape item}] + [:& layer-name {:shape item}]]])) -(mf/defc canvas-item +(mf/defc layer-frame-item {:wrap [#(mf/wrap-memo % =)]} - [{:keys [canvas shapes selected index] :as props}] - (let [selected? (contains? selected (:id canvas)) + [{:keys [item selected index objects] :as props}] + (let [selected? (contains? selected (:id item)) local (mf/use-state {:collapsed false}) collapsed? (:collapsed @local) - shapes (filter #(= (:canvas (second %)) (:id canvas)) shapes) - toggle-collapse (fn [event] (dom/stop-propagation event) @@ -165,24 +165,24 @@ toggle-blocking (fn [event] (dom/stop-propagation event) - (if (:blocked canvas) - (st/emit! (dw/unblock-shape (:id canvas))) - (st/emit! (dw/block-shape (:id canvas))))) + (if (:blocked item) + (st/emit! (dw/unblock-shape (:id item))) + (st/emit! (dw/block-shape (:id item))))) toggle-visibility (fn [event] (dom/stop-propagation event) - (if (:hidden canvas) - (st/emit! (dw/show-canvas (:id canvas))) - (st/emit! (dw/hide-canvas (:id canvas))))) + (if (:hidden item) + (st/emit! (dw/show-frame (:id item))) + (st/emit! (dw/hide-frame (:id item))))) select-shape (fn [event] (dom/prevent-default event) - (let [id (:id canvas)] + (let [id (:id item)] (cond - (or (:blocked canvas) - (:hidden canvas)) + (or (:blocked item) + (:hidden item)) nil (.-ctrlKey event) @@ -201,13 +201,13 @@ on-hover (fn [item monitor] - (st/emit! (dw/change-canvas-order {:id (:canvas-id item) + (st/emit! (dw/change-frame-order {:id (:frame-id item) :index index}))) [dprops dnd-ref] (use-sortable - {:type "canvas-item" - :data {:canvas-id (:id canvas) - :page-id (:page canvas) + {:type "frame-item" + :data {:frame-id (:id item) + :page-id (:page item) :index index} :on-hover on-hover :on-drop on-drop})] @@ -219,84 +219,68 @@ :on-click select-shape :on-double-click #(dom/stop-propagation %)} [:div.element-actions - [:div.toggle-element {:class (when-not (:hidden canvas) "selected") + [:div.toggle-element {:class (when-not (:hidden item) "selected") :on-click toggle-visibility} i/eye] - #_[:div.block-element {:class (when (:blocked canvas) "selected") + #_[:div.block-element {:class (when (:blocked item) "selected") :on-click toggle-blocking} i/lock]] [:div.element-icon i/folder] - [:& layer-name {:shape canvas}] + [:& layer-name {:shape item}] [:span.toggle-content {:on-click toggle-collapse :class (when-not collapsed? "inverse")} i/arrow-slide]] (when-not collapsed? [:ul - (for [[index shape] shapes] - [:& layer-item {:shape shape - :selected selected - :index index - :key (:id shape)}])])])) + (for [[index id] (d/enumerate (:shapes item))] + (let [item (get objects id)] + (if (= (:type item) :frame) + [:& layer-frame-item + {:item item + :key (:id item) + :objects objects + :index index}] + [:& layer-item + {:item item + :index index + :key (:id item)}])))])])) -;; --- Layers List - -(mf/defc layers-list - {:wrap [#(mf/wrap-memo % =)]} - [{:keys [shapes selected] :as props}] - [:ul.element-list - (for [[index shape] shapes] - [:& layer-item {:shape shape - :selected selected - :index index - :key (:id shape)}])]) - -(mf/defc canvas-list - {:wrap [#(mf/wrap-memo % =)]} - [{:keys [shapes canvas selected] :as props}] - [:ul.element-list - (for [[index item] canvas] - [:& canvas-item {:canvas item - :shapes shapes - :selected selected - :index index - :key (:id item)}])]) +(mf/defc layers-tree + {:wrap [mf/wrap-memo]} + [props] + (let [selected (mf/deref refs/selected-shapes) + data (mf/deref refs/workspace-data) + objects (:objects data) + root (get objects uuid/zero)] + [:ul.element-list + (for [[index id] (d/enumerate (:shapes root))] + (let [item (get objects id)] + (if (= (:type item) :frame) + [:& layer-frame-item + {:item item + :key (:id item) + :objects objects + :index index}] + [:& layer-item + {:item item + :index index + :key (:id item)}])))])) ;; --- Layers Toolbox +;; NOTE: we need to consider using something like react window for +;; only render visible items instead of all. + (mf/defc layers-toolbox {:wrap [mf/wrap-memo]} [{:keys [page] :as props}] (let [locale (i18n/use-locale) - on-click #(st/emit! (dw/toggle-layout-flag :layers)) - - selected (mf/deref refs/selected-shapes) - data (mf/deref refs/workspace-data) - - shapes-map (:shapes-by-id data) - strip #(select-keys % [:id :canvas :name :type :hidden :blocked]) - - canvas (->> (:canvas data) - (map #(get shapes-map %)) - (map strip) - (d/enumerate)) - - shapes (->> (:shapes data) - (map #(get shapes-map %)) - (map strip)) - - all-shapes (d/enumerate shapes) - unc-shapes (->> shapes - (filter #(nil? (:canvas %))) - (d/enumerate))] + on-click #(st/emit! (dw/toggle-layout-flag :layers))] [:div#layers.tool-window [:div.tool-window-bar [:div.tool-window-icon i/layers] [:span (t locale "workspace.sidebar.layers")] #_[:div.tool-window-close {:on-click on-click} i/close]] [:div.tool-window-content - [:& canvas-list {:canvas canvas - :shapes all-shapes - :selected selected}] - [:& layers-list {:shapes unc-shapes - :selected selected}]]])) + [:& layers-tree]]])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs index 84f046366..e2a120ff9 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options.cljs @@ -13,7 +13,7 @@ [uxbox.main.data.workspace :as udw] [uxbox.main.store :as st] [uxbox.main.refs :as refs] - [uxbox.main.ui.workspace.sidebar.options.canvas :as canvas] + [uxbox.main.ui.workspace.sidebar.options.frame :as frame] [uxbox.main.ui.workspace.sidebar.options.rect :as rect] [uxbox.main.ui.workspace.sidebar.options.icon :as icon] [uxbox.main.ui.workspace.sidebar.options.circle :as circle] @@ -29,7 +29,7 @@ [{:keys [shape] :as props}] [:div (case (:type shape) - :canvas [:& canvas/options {:shape shape}] + :frame [:& frame/options {:shape shape}] :text [:& text/options {:shape shape}] :rect [:& rect/options {:shape shape}] :icon [:& icon/options {:shape shape}] @@ -43,7 +43,7 @@ [{:keys [shape-id] :as props}] (let [shape-iref (mf/use-memo {:deps (mf/deps shape-id) - :fn #(-> (l/in [:workspace-data :shapes-by-id shape-id]) + :fn #(-> (l/in [:workspace-data :objects shape-id]) (l/derive st/state))}) shape (mf/deref shape-iref)] [:& shape-options {:shape shape}])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/canvas.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame.cljs similarity index 95% rename from frontend/src/uxbox/main/ui/workspace/sidebar/options/canvas.cljs rename to frontend/src/uxbox/main/ui/workspace/sidebar/options/frame.cljs index 20a578d8f..09036b314 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/canvas.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame.cljs @@ -8,7 +8,7 @@ ;; Copyright (c) 2015-2020 Andrey Antukh ;; Copyright (c) 2015-2020 Juan de la Cruz -(ns uxbox.main.ui.workspace.sidebar.options.canvas +(ns uxbox.main.ui.workspace.sidebar.options.frame (:require [rumext.alpha :as mf] [uxbox.common.data :as d] @@ -43,8 +43,8 @@ delta (if (= attr :x) (gpt/point (math/neg (- pval cval)) 0) (gpt/point 0 (math/neg (- pval cval))))] - (st/emit! (udw/apply-canvas-displacement (:id shape) delta) - (udw/materialize-canvas-displacement (:id shape))))) + (st/emit! (udw/apply-frame-displacement (:id shape) delta) + (udw/materialize-frame-displacement (:id shape))))) on-width-change #(on-size-change % :width) on-height-change #(on-size-change % :height) diff --git a/frontend/src/uxbox/main/ui/workspace/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index 35bef155c..72ad05bb4 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -22,13 +22,13 @@ [uxbox.main.ui.workspace.grid :refer [grid]] [uxbox.main.ui.workspace.ruler :refer [ruler]] [uxbox.main.ui.workspace.drawarea :refer [start-drawing]] - - [uxbox.main.ui.shapes :refer [shape-wrapper canvas-wrapper]] + [uxbox.main.ui.shapes :refer [shape-wrapper frame-wrapper]] [uxbox.main.ui.workspace.drawarea :refer [draw-area]] [uxbox.main.ui.workspace.selection :refer [selection-handlers]] - [uxbox.util.data :refer [parse-int]] + [uxbox.util.perf :as perf] [uxbox.util.components :refer [use-rxsub]] + [uxbox.util.uuid :as uuid] [uxbox.util.dom :as dom] [uxbox.util.geom.point :as gpt]) (:import goog.events.EventType)) @@ -142,22 +142,23 @@ (declare remote-user-cursors) -(mf/defc canvas-and-shapes +(mf/defc frame-and-shapes {:wrap [mf/wrap-memo]} [props] (let [data (mf/deref refs/workspace-data) - shapes-map (:shapes-by-id data) - shapes (->> (map #(get shapes-map %) (:shapes data [])) - (group-by :canvas)) - canvas (map #(get shapes-map %) (:canvas data []))] + objects (:objects data) + root (get objects uuid/zero) + shapes (->> (:shapes root) + (map #(get objects %)))] [:g.shapes - (for [item canvas] - [:& canvas-wrapper {:shape item - :key (:id item) - :childs (reverse (get shapes (:id item)))}]) - (for [item (reverse (get shapes nil))] - [:& shape-wrapper {:shape item - :key (:id item)}])])) + (for [item shapes] + (if (= (:type item) :frame) + [:& frame-wrapper {:shape item + :key (:id item) + :objects objects}] + [:& shape-wrapper {:shape item + :key (:id item)}]))])) + (mf/defc viewport [{:keys [page] :as props}] @@ -169,139 +170,157 @@ selected] :as local} (mf/deref refs/workspace-local) viewport-ref (mf/use-ref nil) - zoom (or zoom 1)] - (letfn [(on-mouse-down [event] - (dom/stop-propagation event) - (let [ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - opts {:shift? shift? - :ctrl? ctrl?}] - (st/emit! (ms/->MouseEvent :down ctrl? shift?))) - (when (not edition) - (if drawing-tool - (st/emit! (start-drawing drawing-tool)) - (st/emit! handle-selrect)))) + zoom (or zoom 1) - (on-context-menu [event] - (dom/prevent-default event) - (dom/stop-propagation event) - (let [ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - opts {:shift? shift? - :ctrl? ctrl?}] - (st/emit! (ms/->MouseEvent :context-menu ctrl? shift?)))) + on-mouse-down + (fn [event] + (dom/stop-propagation event) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:shift? shift? + :ctrl? ctrl?}] + (st/emit! (ms/->MouseEvent :down ctrl? shift?))) + (when (not edition) + (if drawing-tool + (st/emit! (start-drawing drawing-tool)) + (st/emit! handle-selrect)))) - (on-mouse-up [event] - (dom/stop-propagation event) - (let [ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - opts {:shift? shift? - :ctrl? ctrl?}] - (st/emit! (ms/->MouseEvent :up ctrl? shift?)))) + on-context-menu + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:shift? shift? + :ctrl? ctrl?}] + (st/emit! (ms/->MouseEvent :context-menu ctrl? shift?)))) - (on-click [event] - (dom/stop-propagation event) - (let [ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - opts {:shift? shift? - :ctrl? ctrl?}] - (st/emit! (ms/->MouseEvent :click ctrl? shift?)))) + on-mouse-up + (fn [event] + (dom/stop-propagation event) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:shift? shift? + :ctrl? ctrl?}] + (st/emit! (ms/->MouseEvent :up ctrl? shift?)))) - (on-double-click [event] - (dom/stop-propagation event) - (let [ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - opts {:shift? shift? - :ctrl? ctrl?}] - (st/emit! (ms/->MouseEvent :double-click ctrl? shift?)))) + on-click + (fn [event] + (dom/stop-propagation event) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:shift? shift? + :ctrl? ctrl?}] + (st/emit! (ms/->MouseEvent :click ctrl? shift?)))) - (translate-point-to-viewport [pt] - (let [viewport (mf/ref-node viewport-ref) - brect (.getBoundingClientRect viewport) - brect (gpt/point (parse-int (.-left brect)) - (parse-int (.-top brect)))] - (gpt/subtract pt brect))) + on-double-click + (fn [event] + (dom/stop-propagation event) + (let [ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:shift? shift? + :ctrl? ctrl?}] + (st/emit! (ms/->MouseEvent :double-click ctrl? shift?)))) - (on-key-down [event] - (let [bevent (.getBrowserEvent event) - key (.-keyCode event) - ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - opts {:key key - :shift? shift? - :ctrl? ctrl?}] - (when-not (.-repeat bevent) - (st/emit! (ms/->KeyboardEvent :down key ctrl? shift?)) - (when (kbd/space? event) - (st/emit! handle-viewport-positioning) - #_(st/emit! (dw/start-viewport-positioning)))))) + on-key-down + (fn [event] + (let [bevent (.getBrowserEvent event) + key (.-keyCode event) + ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:key key + :shift? shift? + :ctrl? ctrl?}] + (when-not (.-repeat bevent) + (st/emit! (ms/->KeyboardEvent :down key ctrl? shift?)) + (when (kbd/space? event) + (st/emit! handle-viewport-positioning) + #_(st/emit! (dw/start-viewport-positioning)))))) - (on-key-up [event] - (let [key (.-keyCode event) - ctrl? (kbd/ctrl? event) - shift? (kbd/shift? event) - opts {:key key - :shift? shift? - :ctrl? ctrl?}] - (when (kbd/space? event) - (st/emit! ::finish-positioning #_(dw/stop-viewport-positioning))) - (st/emit! (ms/->KeyboardEvent :up key ctrl? shift?)))) + on-key-up + (fn [event] + (let [key (.-keyCode event) + ctrl? (kbd/ctrl? event) + shift? (kbd/shift? event) + opts {:key key + :shift? shift? + :ctrl? ctrl?}] + (when (kbd/space? event) + (st/emit! ::finish-positioning #_(dw/stop-viewport-positioning))) + (st/emit! (ms/->KeyboardEvent :up key ctrl? shift?)))) - (on-mouse-move [event] - (let [pt (gpt/point (.-clientX event) - (.-clientY event)) - pt (translate-point-to-viewport pt)] - (st/emit! (ms/->PointerEvent :viewport pt - (kbd/ctrl? event) - (kbd/shift? event))))) + ;; translate-point-to-viewport + ;; (fn [pt] + ;; (let [viewport (mf/ref-node viewport-ref) + ;; brect (.getBoundingClientRect viewport) + ;; brect (gpt/point (parse-int (.-left brect)) + ;; (parse-int (.-top brect)))] + ;; (gpt/subtract pt brect))) - (on-mount [] - (let [key1 (events/listen js/document EventType.KEYDOWN on-key-down) - key2 (events/listen js/document EventType.KEYUP on-key-up)] - (fn [] - (events/unlistenByKey key1) - (events/unlistenByKey key2))))] - (mf/use-effect on-mount) - [:* - [:& coordinates {:zoom zoom}] - [:svg.viewport {:width (* c/viewport-width zoom) - :height (* c/viewport-height zoom) - :ref viewport-ref - :class (when drawing-tool "drawing") - :on-context-menu on-context-menu - :on-click on-click - :on-double-click on-double-click - :on-mouse-move on-mouse-move - :on-mouse-down on-mouse-down - :on-mouse-up on-mouse-up} - [:g.zoom {:transform (str "scale(" zoom ", " zoom ")")} - [:* - [:& canvas-and-shapes] + on-mouse-move + (fn [event] + ;; NOTE: offsetX and offsetY are marked as "experimental" on + ;; MDN site but seems like they are supported on all + ;; browsers so we can avoid translation opetation just using + ;; this attributes. + (let [;; pt (gpt/point (.-clientX event) + ;; (.-clientY event)) + ;; pt (translate-point-to-viewport pt) + pt (gpt/point (.-offsetX (.-nativeEvent event)) + (.-offsetY (.-nativeEvent event)))] + (st/emit! (ms/->PointerEvent :viewport pt + (kbd/ctrl? event) + (kbd/shift? event))))) - (when (seq selected) - [:& selection-handlers {:selected selected - :zoom zoom - :edition edition}]) + on-mount + (fn [] + (let [key1 (events/listen js/document EventType.KEYDOWN on-key-down) + key2 (events/listen js/document EventType.KEYUP on-key-up)] + (fn [] + (events/unlistenByKey key1) + (events/unlistenByKey key2))))] - (when-let [drawing-shape (:drawing local)] - [:& draw-area {:shape drawing-shape - :zoom zoom - :modifiers (:modifiers local)}])] + (mf/use-effect on-mount) + [:* + [:& coordinates {:zoom zoom}] + [:svg.viewport {:width (* c/viewport-width zoom) + :height (* c/viewport-height zoom) + :ref viewport-ref + :class (when drawing-tool "drawing") + :on-context-menu on-context-menu + :on-click on-click + :on-double-click on-double-click + :on-mouse-move on-mouse-move + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up} + [:g.zoom {:transform (str "scale(" zoom ", " zoom ")")} + ;; [:> js/React.Profiler + ;; {:id "foobar" + ;; :on-render (perf/react-on-profile)} + ;; [:& frame-and-shapes]] + [:& frame-and-shapes] - (if (contains? flags :grid) - [:& grid])] + (when (seq selected) + [:& selection-handlers {:selected selected + :zoom zoom + :edition edition}]) - (when tooltip - [:& cursor-tooltip {:zoom zoom :tooltip tooltip}]) + (when-let [drawing-shape (:drawing local)] + [:& draw-area {:shape drawing-shape + :zoom zoom + :modifiers (:modifiers local)}]) - (when (contains? flags :ruler) - [:& ruler {:zoom zoom :ruler (:ruler local)}]) + (if (contains? flags :grid) + [:& grid])] + (when tooltip + [:& cursor-tooltip {:zoom zoom :tooltip tooltip}]) - ;; -- METER CURSOR MULTIUSUARIO - [:& remote-user-cursors {:page page}] + (when (contains? flags :ruler) + [:& ruler {:zoom zoom :ruler (:ruler local)}]) - [:& selrect {:data (:selrect local)}]]]))) + [:& remote-user-cursors {:page page}] + [:& selrect {:data (:selrect local)}]]])) (mf/defc remote-user-cursor diff --git a/frontend/src/uxbox/util/interop.cljs b/frontend/src/uxbox/util/interop.cljs index ee6e7a2a5..ae76b74a6 100644 --- a/frontend/src/uxbox/util/interop.cljs +++ b/frontend/src/uxbox/util/interop.cljs @@ -11,3 +11,7 @@ "Convert an es6 iterable into cljs Seq." [v] (seq (js/Array.from v))) + +(defn obj-assign! + [obj1 obj2] + (js/Object.assign obj1 obj2)) diff --git a/frontend/src/uxbox/util/perf.cljc b/frontend/src/uxbox/util/perf.cljc index e03dbe6ff..207a32ca9 100644 --- a/frontend/src/uxbox/util/perf.cljc +++ b/frontend/src/uxbox/util/perf.cljc @@ -6,7 +6,8 @@ (ns uxbox.util.perf "Performance and debugging tools." - #?(:cljs (:require-macros [uxbox.util.perf]))) + #?(:cljs (:require-macros [uxbox.util.perf])) + #?(:cljs (:require [uxbox.util.math :as math]))) #?(:clj (defmacro with-measure @@ -17,3 +18,25 @@ time# (.toFixed (- end# start#) 2)] (println (str "[perf|" ~name "] => " time#)) res#))) + + +;; id, // the "id" prop of the Profiler tree that has just committed +;; phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered) +;; actualDuration, // time spent rendering the committed update +;; baseDuration, // estimated time to render the entire subtree without memoization +;; startTime, // when React began rendering this update +;; commitTime, // when React committed this update +;; interactions // the Set of interactions belonging to this update + +#?(:cljs + (defn react-on-profile + [] + (let [sum (volatile! 0) + ctr (volatile! 0)] + (fn [id phase adur, bdur, st, ct, itx] + (vswap! sum (fn [prev] (+ prev adur))) + (vswap! ctr inc) + (js/console.log (str "[profile:" id ":" phase "]") + "" + (str "time=" (math/precision adur 4)) + (str "avg=" (math/precision (/ @sum @ctr) 4))))))) diff --git a/frontend/src/uxbox/view/ui/viewer.cljs b/frontend/src/uxbox/view/ui/viewer.cljs index 57e395095..970c2367f 100644 --- a/frontend/src/uxbox/view/ui/viewer.cljs +++ b/frontend/src/uxbox/view/ui/viewer.cljs @@ -13,7 +13,7 @@ [uxbox.util.data :refer [seek]] [uxbox.view.data.viewer :as dv] [uxbox.view.store :as st] - [uxbox.view.ui.viewer.canvas :refer [canvas]] + [uxbox.view.ui.viewer.frame :refer [frame]] [uxbox.view.ui.viewer.nav :refer [nav]] [uxbox.view.ui.viewer.sitemap :refer [sitemap]] [lentes.core :as l])) @@ -45,4 +45,4 @@ :pages pages :selected id}]) [:& nav {:flags flags}] - [:& canvas {:page (seek #(= id (:id %)) pages)}]]))) + [:& frame {:page (seek #(= id (:id %)) pages)}]]))) diff --git a/frontend/src/uxbox/view/ui/viewer/canvas.cljs b/frontend/src/uxbox/view/ui/viewer/frame.cljs similarity index 93% rename from frontend/src/uxbox/view/ui/viewer/canvas.cljs rename to frontend/src/uxbox/view/ui/viewer/frame.cljs index 4f3409760..f08985734 100644 --- a/frontend/src/uxbox/view/ui/viewer/canvas.cljs +++ b/frontend/src/uxbox/view/ui/viewer/frame.cljs @@ -5,7 +5,7 @@ ;; Copyright (c) 2016-2017 Andrey Antukh ;; Copyright (c) 2016-2017 Juan de la Cruz -(ns uxbox.view.ui.viewer.canvas +(ns uxbox.view.ui.viewer.frame (:require [rumext.alpha :as mf] [uxbox.view.ui.viewer.shapes :as shapes])) @@ -25,12 +25,12 @@ (declare shape) -(mf/defc canvas +(mf/defc frame {:wrap [mf/wrap-memo]} [{:keys [page] :as props}] #_(let [{:keys [metadata id]} page {:keys [width height]} metadata] - [:div.view-canvas + [:div.view-frame [:svg.page-layout {:width width :height height} [:& background metadata]