From 3e00d67504f2ae8f92a3df9dd7df58d79362cf95 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 10 Jun 2020 13:14:33 +0200 Subject: [PATCH 1/9] :sparkles: Improve page data migrations. --- .../src/uxbox/services/mutations/pages.clj | 4 +- backend/src/uxbox/services/queries/pages.clj | 6 +- common/uxbox/common/pages.cljc | 2 +- ...{migrations.cljc => pages_migrations.cljc} | 59 ++++++++++++------- 4 files changed, 45 insertions(+), 26 deletions(-) rename common/uxbox/common/{migrations.cljc => pages_migrations.cljc} (54%) diff --git a/backend/src/uxbox/services/mutations/pages.clj b/backend/src/uxbox/services/mutations/pages.clj index 75fbdef38..67118fc99 100644 --- a/backend/src/uxbox/services/mutations/pages.clj +++ b/backend/src/uxbox/services/mutations/pages.clj @@ -13,7 +13,7 @@ [uxbox.common.data :as d] [uxbox.common.exceptions :as ex] [uxbox.common.pages :as cp] - [uxbox.common.migrations :as mg] + [uxbox.common.pages-migrations :as pmg] [uxbox.common.spec :as us] [uxbox.common.uuid :as uuid] [uxbox.config :as cfg] @@ -182,7 +182,7 @@ page (-> page (update :data blob/decode) - (update :data mg/migrate-data) + (update :data pmg/migrate-data) (update :data cp/process-changes changes) (update :data blob/encode) (update :revn inc) diff --git a/backend/src/uxbox/services/queries/pages.clj b/backend/src/uxbox/services/queries/pages.clj index 07ced67b7..fc85b2161 100644 --- a/backend/src/uxbox/services/queries/pages.clj +++ b/backend/src/uxbox/services/queries/pages.clj @@ -13,7 +13,7 @@ [promesa.core :as p] [uxbox.common.spec :as us] [uxbox.common.exceptions :as ex] - [uxbox.common.migrations :as mg] + [uxbox.common.pages-migrations :as pmg] [uxbox.db :as db] [uxbox.services.queries :as sq] [uxbox.services.queries.files :as files] @@ -40,7 +40,7 @@ (db/with-atomic [conn db/pool] (files/check-edition-permissions! conn profile-id file-id) (->> (retrieve-pages conn params) - (mapv #(update % :data mg/migrate-data))))) + (mapv #(update % :data pmg/migrate-data))))) (def ^:private sql:pages "select p.* @@ -67,7 +67,7 @@ (let [page (retrieve-page conn id)] (files/check-edition-permissions! conn profile-id (:file-id page)) (-> page - (update :data mg/migrate-data))))) + (update :data pmg/migrate-data))))) (def ^:private sql:page "select p.* from page as p where id=?") diff --git a/common/uxbox/common/pages.cljc b/common/uxbox/common/pages.cljc index 5d3b99802..6054169da 100644 --- a/common/uxbox/common/pages.cljc +++ b/common/uxbox/common/pages.cljc @@ -16,7 +16,7 @@ [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us])) -(def page-version 4) +(def page-version 5) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Page Data Structure Helpers diff --git a/common/uxbox/common/migrations.cljc b/common/uxbox/common/pages_migrations.cljc similarity index 54% rename from common/uxbox/common/migrations.cljc rename to common/uxbox/common/pages_migrations.cljc index 4cb445150..4fc0a6ca9 100644 --- a/common/uxbox/common/migrations.cljc +++ b/common/uxbox/common/pages_migrations.cljc @@ -1,41 +1,60 @@ -(ns uxbox.common.migrations +(ns uxbox.common.pages-migrations (:require [uxbox.common.pages :as p] [uxbox.common.geom.shapes :as gsh] [uxbox.common.geom.point :as gpt] [uxbox.common.geom.matrix :as gmt] + [uxbox.common.spec :as us] [uxbox.common.uuid :as uuid] [uxbox.common.data :as d])) (defmulti migrate :version) (defn migrate-data + ([data] + (if (= (:version data) p/page-version) + data + (reduce #(migrate-data %1 %2 (inc %2)) + data + (range (:version data 0) p/page-version)))) + ([data from-version to-version] (-> data (assoc :version to-version) - (migrate))) - - ([data] - (try - (reduce #(migrate-data %1 %2 (inc %2)) - data - (range (:version data 0) p/page-version)) - - ;; If an error is thrown, we log the error and return the data without migrations - #?(:clj (catch Exception e (.printStackTrace e) data) - :cljs (catch :default e (.error js/console e) data))))) + (migrate)))) ;; Default handler, noop (defmethod migrate :default [data] data) ;; -- MIGRATIONS -- -(defmethod migrate 4 [data] - ;; We changed the internal model of the shapes so they have their selection rect - ;; and the vertices - - (letfn [;; Creates a new property `points` that stores the transformed points inside the shape - ;; this will be used for the snaps and the selection rect +(defn- generate-child-parent-index + [objects] + (reduce-kv + (fn [index id obj] + (into index (map #(vector % id) (:shapes obj [])))) + {} objects)) + +(defmethod migrate 5 + [data] + (update data :objects + (fn [objects] + (let [index (generate-child-parent-index objects)] + (d/mapm + (fn [id obj] + (let [parent-id (get index id)] + (assoc obj :parent-id parent-id))) + objects))))) + +;; We changed the internal model of the shapes so they have their +;; selection rect and the vertices + +(defmethod migrate 4 + [data] + + (letfn [;; Creates a new property `points` that stores the + ;; transformed points inside the shape this will be used for + ;; the snaps and the selection rect (calculate-shape-points [objects] (->> objects (d/mapm @@ -44,7 +63,8 @@ shape (assoc shape :points (gsh/shape->points shape))))))) - ;; Creates a new property `selrect` that stores the selection rect for the shape + ;; Creates a new property `selrect` that stores the + ;; selection rect for the shape (calculate-shape-selrects [objects] (->> objects (d/mapm @@ -53,7 +73,6 @@ shape (assoc shape :selrect (gsh/points->selrect (:points shape))))))))] (-> data - ;; Adds vertices to shapes (update :objects calculate-shape-points) From 93a967ed8f90c4c5ff7b0e39569862ac4a11e3a9 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 12 Jun 2020 09:58:10 +0200 Subject: [PATCH 2/9] :lipstick: Remove unused operation on page update mutation. --- .../src/uxbox/services/mutations/pages.clj | 3 +- common/uxbox/common/pages_helpers.cljc | 108 ++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 common/uxbox/common/pages_helpers.cljc diff --git a/backend/src/uxbox/services/mutations/pages.clj b/backend/src/uxbox/services/mutations/pages.clj index 67118fc99..6ff29dd72 100644 --- a/backend/src/uxbox/services/mutations/pages.clj +++ b/backend/src/uxbox/services/mutations/pages.clj @@ -177,8 +177,7 @@ :context {:incoming-revn (:revn params) :stored-revn (:revn page)})) (let [sid (:session-id params) - changes (->> (:changes params) - (mapv #(assoc % :session-id sid))) + changes (:changes params) page (-> page (update :data blob/decode) diff --git a/common/uxbox/common/pages_helpers.cljc b/common/uxbox/common/pages_helpers.cljc new file mode 100644 index 000000000..1bcc63d3d --- /dev/null +++ b/common/uxbox/common/pages_helpers.cljc @@ -0,0 +1,108 @@ +;; 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.common.pages-helpers + (:require + [uxbox.common.data :as d] + [uxbox.common.uuid :as uuid])) + +(defn get-children + "Retrieve all children ids recursively for a given object" + [id objects] + (let [shapes (get-in objects [id :shapes])] + (if shapes + (d/concat shapes (mapcat #(get-children % objects) shapes)) + []))) + +(defn is-shape-grouped + "Checks if a shape is inside a group" + [shape-id objects] + (let [contains-shape-fn (fn [{:keys [shapes]}] ((set shapes) shape-id)) + shapes (remove #(= (:type %) :frame) (vals objects))] + (some contains-shape-fn shapes))) + +(defn get-parent + "Retrieve the id of the parent for the shape-id (if exists)" + [shape-id objects] + (let [obj (get objects shape-id)] + (:parent-id obj))) + +(defn get-parents + [shape-id objects] + (let [{:keys [parent-id] :as obj} (get objects shape-id)] + (when parent-id + (lazy-seq (cons parent-id (get-parents parent-id objects)))))) + +(defn generate-child-parent-index + [objects] + (reduce-kv + (fn [index id obj] + (assoc index id (:parent-id obj))) + {} objects)) + +(defn calculate-invalid-targets + [shape-id objects] + (let [result #{shape-id} + children (get-in objects [shape-id :shape]) + reduce-fn (fn [result child-id] + (into result (calculate-invalid-targets child-id objects)))] + (reduce reduce-fn result children))) + +(defn valid-frame-target + [shape-id parent-id objects] + (let [shape (get objects shape-id)] + (or (not= (:type shape) :frame) + (= parent-id uuid/zero)))) + +(defn insert-at-index + [shapes index ids] + (let [[before after] (split-at index shapes) + p? (set ids)] + (d/concat [] + (remove p? before) + ids + (remove p? after)))) + +(defn select-toplevel-shapes + ([objects] (select-toplevel-shapes objects nil)) + ([objects {:keys [include-frames?] :or {include-frames? false}}] + (let [lookup #(get objects %) + root (lookup uuid/zero) + childs (:shapes root)] + (loop [id (first childs) + ids (rest childs) + res []] + (if (nil? id) + res + (let [obj (lookup id) + typ (:type obj)] + (recur (first ids) + (rest ids) + (if (= :frame typ) + (if include-frames? + (d/concat res [obj] (map lookup (:shapes obj))) + (d/concat res (map lookup (:shapes obj)))) + (conj res obj))))))))) + +(defn select-frames + [objects] + (let [root (get objects uuid/zero) + loopfn (fn loopfn [ids] + (let [obj (get objects (first ids))] + (cond + (nil? obj) + nil + + (= :frame (:type obj)) + (lazy-seq (cons obj (loopfn (rest ids)))) + + :else + (lazy-seq (loopfn (rest ids))))))] + (loopfn (:shapes root)))) + From b68a076e576e07c24ead482bc94f06ff4c793d67 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 12 Jun 2020 10:32:25 +0200 Subject: [PATCH 3/9] :bug: Fix critical inconsistencies on group handling. --- .../tests/uxbox/tests/test_common_pages.clj | 167 +++++++++++--- common/uxbox/common/geom/shapes.cljc | 5 +- common/uxbox/common/pages.cljc | 212 +++++++----------- common/uxbox/common/pages_migrations.cljc | 8 +- frontend/src/uxbox/main/data/workspace.cljs | 81 ++++--- .../src/uxbox/main/data/workspace/common.cljs | 9 +- .../uxbox/main/data/workspace/drawing.cljs | 3 +- .../uxbox/main/data/workspace/selection.cljs | 17 +- .../uxbox/main/data/workspace/transforms.cljs | 7 +- frontend/src/uxbox/main/exports.cljs | 5 +- frontend/src/uxbox/main/refs.cljs | 13 +- frontend/src/uxbox/main/ui/viewer/shapes.cljs | 3 +- .../main/ui/workspace/sidebar/layers.cljs | 7 +- .../sidebar/options/interactions.cljs | 4 +- frontend/src/uxbox/worker/selection.cljs | 13 +- frontend/src/uxbox/worker/snaps.cljs | 5 +- 16 files changed, 323 insertions(+), 236 deletions(-) diff --git a/backend/tests/uxbox/tests/test_common_pages.clj b/backend/tests/uxbox/tests/test_common_pages.clj index 9eac86a80..5f48b90d9 100644 --- a/backend/tests/uxbox/tests/test_common_pages.clj +++ b/backend/tests/uxbox/tests/test_common_pages.clj @@ -77,16 +77,23 @@ (t/testing "Adds single object" (let [chg {:type :add-obj :id id-a + :parent-id uuid/zero :frame-id uuid/zero :obj {:id id-a :frame-id uuid/zero + :parent-id uuid/zero :type :rect :name "rect"}} res (cp/process-changes data [chg])] + ;; (clojure.pprint/pprint data) + ;; (clojure.pprint/pprint res) + (t/is (= 2 (count (:objects res)))) (t/is (= (:obj chg) (get-in res [:objects id-a]))) (t/is (= [id-a] (get-in res [:objects uuid/zero :shapes]))))) + + (t/testing "Adds several objects with different indexes" (let [data cp/default-page-data @@ -277,36 +284,77 @@ rect-c-id (uuid/custom 7) rect-d-id (uuid/custom 8) rect-e-id (uuid/custom 9) - data (-> cp/default-page-data - (assoc-in [cp/root :shapes] [frame-a-id]) - (assoc-in [:objects frame-a-id] - {:id frame-a-id :name "Frame a" :type :frame}) - (assoc-in [:objects frame-b-id] - {:id frame-b-id :name "Frame b" :type :frame}) - ;; Groups - (assoc-in [:objects group-a-id] - {:id group-a-id :name "Group A" :type :group :frame-id frame-a-id}) - (assoc-in [:objects group-b-id] - {:id group-b-id :name "Group B" :type :group :frame-id frame-a-id}) + data + (-> cp/default-page-data + (assoc-in [:objects uuid/zero :shapes] [frame-a-id frame-b-id]) + (assoc-in [:objects frame-a-id] + {:id frame-a-id + :parent-id uuid/zero + :frame-id uuid/zero + :name "Frame a" + :shapes [group-a-id group-b-id rect-e-id] + :type :frame}) + + (assoc-in [:objects frame-b-id] + {:id frame-b-id + :parent-id uuid/zero + :frame-id uuid/zero + :name "Frame b" + :shapes [] + :type :frame}) + + ;; Groups + (assoc-in [:objects group-a-id] + {:id group-a-id + :name "Group A" + :type :group + :parent-id frame-a-id + :frame-id frame-a-id + :shapes [rect-a-id rect-b-id rect-c-id]}) + (assoc-in [:objects group-b-id] + {:id group-b-id + :name "Group B" + :type :group + :parent-id frame-a-id + :frame-id frame-a-id + :shapes [rect-d-id]}) ;; Shapes - (assoc-in [:objects rect-a-id] - {:id rect-a-id :name "Rect A" :type :rect :frame-id frame-a-id}) - (assoc-in [:objects rect-b-id] - {:id rect-b-id :name "Rect B" :type :rect :frame-id frame-a-id}) - (assoc-in [:objects rect-c-id] - {:id rect-c-id :name "Rect C" :type :rect :frame-id frame-a-id}) - (assoc-in [:objects rect-d-id] - {:id rect-d-id :name "Rect D" :type :rect :frame-id frame-a-id}) - (assoc-in [:objects rect-e-id] - {:id rect-e-id :name "Rect E" :type :rect :frame-id frame-a-id}) + (assoc-in [:objects rect-a-id] + {:id rect-a-id + :name "Rect A" + :type :rect + :parent-id group-a-id + :frame-id frame-a-id}) - ;; Relationships - (assoc-in [:objects cp/root :shapes] [frame-a-id frame-b-id]) - (assoc-in [:objects frame-a-id :shapes] [group-a-id group-b-id rect-e-id]) - (assoc-in [:objects group-a-id :shapes] [rect-a-id rect-b-id rect-c-id]) - (assoc-in [:objects group-b-id :shapes] [rect-d-id]))] + (assoc-in [:objects rect-b-id] + {:id rect-b-id + :name "Rect B" + :type :rect + :parent-id group-a-id + :frame-id frame-a-id}) + + (assoc-in [:objects rect-c-id] + {:id rect-c-id + :name "Rect C" + :type :rect + :parent-id group-a-id + :frame-id frame-a-id}) + + (assoc-in [:objects rect-d-id] + {:id rect-d-id + :name "Rect D" + :parent-id group-b-id + :type :rect + :frame-id frame-a-id}) + + (assoc-in [:objects rect-e-id] + {:id rect-e-id + :name "Rect E" + :type :rect + :parent-id frame-a-id + :frame-id frame-a-id}))] (t/testing "Create new group an add objects from the same group" (let [new-group-id (uuid/next) @@ -322,6 +370,10 @@ :shapes [rect-b-id rect-c-id]}] res (cp/process-changes data changes)] + ;; (clojure.pprint/pprint data) + ;; (println "===============") + ;; (clojure.pprint/pprint res) + (t/is (= [group-a-id group-b-id rect-e-id new-group-id] (get-in res [:objects frame-a-id :shapes]))) (t/is (= [rect-b-id rect-c-id] @@ -396,10 +448,15 @@ (t/testing "Move elements to frame zero" (let [changes [{:type :mov-objects - :parent-id cp/root + :parent-id uuid/zero :shapes [group-a-id] :index 0}] res (cp/process-changes data changes)] + + ;; (pprint (get-in data [:objects uuid/zero])) + ;; (println "==========") + ;; (pprint (get-in res [:objects uuid/zero])) + (t/is (= [group-a-id frame-a-id frame-b-id] (get-in res [:objects cp/root :shapes]))))) @@ -408,7 +465,8 @@ :parent-id group-a-id :shapes [group-a-id]}] res (cp/process-changes data changes)] - (t/is (= data res)))))) + (t/is (= data res)))) + )) (t/deftest process-change-move-objects-regression @@ -653,4 +711,57 @@ )) +(t/deftest idenpotency-regression-1 + (let [data {:version 5 + :objects + {#uuid "00000000-0000-0000-0000-000000000000" + {:id #uuid "00000000-0000-0000-0000-000000000000", + :type :frame, + :name "root", + :shapes + [#uuid "f5d51910-ab23-11ea-ac38-e1abed64181a" + #uuid "f6a36590-ab23-11ea-ac38-e1abed64181a"]}, + #uuid "f5d51910-ab23-11ea-ac38-e1abed64181a" + {:name "Rect-1", + :type :rect, + :id #uuid "f5d51910-ab23-11ea-ac38-e1abed64181a", + :parent-id #uuid "00000000-0000-0000-0000-000000000000", + :frame-id #uuid "00000000-0000-0000-0000-000000000000"} + #uuid "f6a36590-ab23-11ea-ac38-e1abed64181a" + {:name "Rect-2", + :type :rect, + :id #uuid "f6a36590-ab23-11ea-ac38-e1abed64181a", + :parent-id #uuid "00000000-0000-0000-0000-000000000000", + :frame-id #uuid "00000000-0000-0000-0000-000000000000"}}} + chgs [{:type :add-obj, + :id #uuid "3375ec40-ab24-11ea-b512-b945e8edccf5", + :frame-id #uuid "00000000-0000-0000-0000-000000000000", + :index 0 + :obj {:name "Group-1", + :type :group, + :id #uuid "3375ec40-ab24-11ea-b512-b945e8edccf5", + :frame-id #uuid "00000000-0000-0000-0000-000000000000"}} + {:type :mov-objects, + :parent-id #uuid "3375ec40-ab24-11ea-b512-b945e8edccf5", + :shapes + [#uuid "f5d51910-ab23-11ea-ac38-e1abed64181a" + #uuid "f6a36590-ab23-11ea-ac38-e1abed64181a"]}] + + res1 (cp/process-changes data chgs) + res2 (cp/process-changes res1 chgs)] + + ;; (clojure.pprint/pprint data) + ;; (println "==============") + ;; (clojure.pprint/pprint res2) + + (t/is (= [#uuid "f5d51910-ab23-11ea-ac38-e1abed64181a" + #uuid "f6a36590-ab23-11ea-ac38-e1abed64181a"] + (get-in data [:objects uuid/zero :shapes]))) + (t/is (= [#uuid "3375ec40-ab24-11ea-b512-b945e8edccf5"] + (get-in res2 [:objects uuid/zero :shapes]))) + (t/is (= [#uuid "3375ec40-ab24-11ea-b512-b945e8edccf5"] + (get-in res1 [:objects uuid/zero :shapes]))) + )) + + diff --git a/common/uxbox/common/geom/shapes.cljc b/common/uxbox/common/geom/shapes.cljc index fb783daca..f67b65ce3 100644 --- a/common/uxbox/common/geom/shapes.cljc +++ b/common/uxbox/common/geom/shapes.cljc @@ -10,8 +10,8 @@ (ns uxbox.common.geom.shapes (:require [clojure.spec.alpha :as s] - [uxbox.common.pages :as cp] [uxbox.common.spec :as us] + [uxbox.common.pages-helpers :as cph] [uxbox.common.geom.matrix :as gmt] [uxbox.common.geom.point :as gpt] [uxbox.common.math :as mth] @@ -61,7 +61,7 @@ (defn recursive-move "Move the shape and all its recursive children." [shape dpoint objects] - (let [children-ids (cp/get-children (:id shape) objects) + (let [children-ids (cph/get-children (:id shape) objects) children (map #(get objects %) children-ids)] (map #(move % dpoint) (cons shape children)))) @@ -253,6 +253,7 @@ ;; -- Points (declare transform-shape-point) + (defn shape->points [shape] (let [points (case (:type shape) diff --git a/common/uxbox/common/pages.cljc b/common/uxbox/common/pages.cljc index 6054169da..5ed758798 100644 --- a/common/uxbox/common/pages.cljc +++ b/common/uxbox/common/pages.cljc @@ -11,119 +11,15 @@ "A common (clj/cljs) functions and specs for pages." (:require [clojure.spec.alpha :as s] - [uxbox.common.uuid :as uuid] [uxbox.common.data :as d] + [uxbox.common.pages-helpers :as cph] [uxbox.common.exceptions :as ex] - [uxbox.common.spec :as us])) + [uxbox.common.geom.shapes :as geom] + [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid])) (def page-version 5) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Page Data Structure Helpers -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn get-children - "Retrieve all children ids recursively for a given object" - [id objects] - (let [shapes (get-in objects [id :shapes])] - (if shapes - (d/concat shapes (mapcat #(get-children % objects) shapes)) - []))) - -(defn is-shape-grouped - "Checks if a shape is inside a group" - [shape-id objects] - (let [contains-shape-fn (fn [{:keys [shapes]}] ((set shapes) shape-id)) - shapes (remove #(= (:type %) :frame) (vals objects))] - (some contains-shape-fn shapes))) - -(defn get-parent - "Retrieve the id of the parent for the shape-id (if exists)" - [shape-id objects] - (let [check-parenthood - (fn [shape] - (when (and (:shapes shape) - ((set (:shapes shape)) shape-id)) - (:id shape)))] - (some check-parenthood (vals objects)))) - -(defn calculate-child-parent-map - [objects] - (let [red-fn - (fn [acc {:keys [id shapes]}] - ;; Insert every pair shape -> parent into accumulated value - (into acc (map #(vector % id) (or shapes []))))] - (reduce red-fn {} (vals objects)))) - -(defn get-all-parents - [shape-id objects] - (let [child->parent (calculate-child-parent-map objects) - rec-fn (fn [cur result] - (if-let [parent (child->parent cur)] - (recur parent (conj result parent)) - (vec (reverse result))))] - (rec-fn shape-id []))) - -(defn- calculate-invalid-targets - [shape-id objects] - (let [result #{shape-id} - children (get-in objects [shape-id :shape]) - reduce-fn (fn [result child-id] - (into result (calculate-invalid-targets child-id objects)))] - (reduce reduce-fn result children))) - -(defn- valid-frame-target - [shape-id parent-id objects] - (let [shape (get objects shape-id)] - (or (not= (:type shape) :frame) - (= parent-id uuid/zero)))) - -(defn- insert-at-index - [shapes index ids] - (let [[before after] (split-at index shapes) - p? (set ids)] - (d/concat [] - (remove p? before) - ids - (remove p? after)))) - -(defn select-toplevel-shapes - ([objects] (select-toplevel-shapes objects nil)) - ([objects {:keys [include-frames?] :or {include-frames? false}}] - (let [lookup #(get objects %) - root (lookup uuid/zero) - childs (:shapes root)] - (loop [id (first childs) - ids (rest childs) - res []] - (if (nil? id) - res - (let [obj (lookup id) - typ (:type obj)] - (recur (first ids) - (rest ids) - (if (= :frame typ) - (if include-frames? - (d/concat res [obj] (map lookup (:shapes obj))) - (d/concat res (map lookup (:shapes obj)))) - (conj res obj))))))))) - -(defn select-frames - [objects] - (let [root (get objects uuid/zero) - loopfn (fn loopfn [ids] - (let [obj (get objects (first ids))] - (cond - (nil? obj) - nil - - (= :frame (:type obj)) - (lazy-seq (cons obj (loopfn (rest ids)))) - - :else - (lazy-seq (loopfn (rest ids))))))] - (loopfn (:shapes root)))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Page Transformation Changes ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -258,6 +154,7 @@ ::options ::objects])) +(s/def ::ids (s/coll-of ::us/uuid)) (s/def ::attr keyword?) (s/def ::val any?) (s/def ::frame-id uuid?) @@ -297,6 +194,10 @@ (s/keys :req-un [::id] :opt-un [::session-id])) +(defmethod change-spec-impl :reg-obj [_] + (s/keys :req-un [::ids] + :opt-un [::session-id])) + (defmethod change-spec-impl :mov-objects [_] (s/keys :req-un [::parent-id ::shapes] :opt-un [::index])) @@ -402,8 +303,6 @@ (or (process-change %1 %2) %1)) data))) -(declare insert-at-index) - (defmethod process-change :set-option [data {:keys [option value]}] (let [path (if (seqable? option) option [option])] @@ -418,8 +317,9 @@ (when (and (contains? objects parent-id) (contains? objects frame-id)) (let [obj (assoc obj - :frame-id frame-id - :id id)] + :frame-id frame-id + :parent-id parent-id + :id id)] (-> data (update :objects assoc id obj) (update-in [:objects parent-id :shapes] @@ -428,7 +328,7 @@ (cond (some #{id} shapes) shapes (nil? index) (conj shapes id) - :else (insert-at-index shapes index [id])))))))))) + :else (cph/insert-at-index shapes index [id])))))))))) (defmethod process-change :mod-obj [data {:keys [id operations] :as change}] @@ -442,7 +342,7 @@ [data {:keys [id] :as change}] (when-let [{:keys [frame-id shapes] :as obj} (get-in data [:objects id])] (let [objects (:objects data) - parent-id (get-parent id objects) + parent-id (cph/get-parent id objects) parent (get objects parent-id) data (update data :objects dissoc id)] (cond-> data @@ -456,15 +356,44 @@ (seq shapes) ; Recursive delete all dependend objects (as-> $ (reduce #(or (process-change %1 {:type :del-obj :id %2}) %1) $ shapes)))))) +(defmethod process-change :reg-obj + [data {:keys [ids]}] + (let [objects (:objects data)] + (loop [ids ids data data] + (if (seq ids) + (let [item (get objects (first ids))] + (if (= :group (:type item)) + (recur + (rest ids) + (update-in data [:objects (:id item)] + (fn [{:keys [shapes] :as obj}] + (let [shapes (->> shapes + (map (partial get objects)) + (filter identity))] + (if (seq shapes) + (let [selrect (geom/selection-rect shapes)] + (as-> obj $ + (assoc $ + :x (:x selrect) + :y (:y selrect) + :width (:width selrect) + :height (:height selrect)) + (assoc $ :points (geom/shape->points $)) + (assoc $ :selrect (geom/points->selrect (:points $))))) + obj))))) + (recur (rest ids) data))) + data)))) + (defmethod process-change :mov-objects [data {:keys [parent-id shapes index] :as change}] - (let [child->parent (calculate-child-parent-map (:objects data)) + (let [ ;; Check if the move from shape-id -> parent-id is valid + is-valid-move (fn [shape-id] - (let [invalid-targets (calculate-invalid-targets shape-id (:objects data))] + (let [invalid-targets (cph/calculate-invalid-targets shape-id (:objects data))] (and (not (invalid-targets parent-id)) - (valid-frame-target shape-id parent-id (:objects data))))) + (cph/valid-frame-target shape-id parent-id (:objects data))))) valid? (every? is-valid-move shapes) @@ -473,7 +402,7 @@ (fn [prev-shapes] (let [prev-shapes (or prev-shapes [])] (if index - (insert-at-index prev-shapes index shapes) + (cph/insert-at-index prev-shapes index shapes) (reduce (fn [acc id] (if (some #{id} acc) acc @@ -485,23 +414,31 @@ (fn [id] (fn [coll] (filterv #(not= % id) coll))) - ;; Remove from the old :shapes the references that have been moved - remove-in-parent - (fn [data shape-id] - (let [parent-id' (get child->parent shape-id)] + cpindex + (reduce + (fn [index id] + (let [obj (get-in data [:objects id])] + (assoc index id (:parent-id obj)))) + {} (keys (:objects data))) + + remove-from-old-parent + (fn remove-from-old-parent [data shape-id] + (let [prev-parent-id (get cpindex shape-id)] ;; Do nothing if the parent id of the shape is the same as ;; the new destination target parent id. - (if (= parent-id' parent-id) + (if (= prev-parent-id parent-id) data - (let [parent (-> (get-in data [:objects parent-id']) - (update :shapes (strip-id shape-id)))] - ;; When the group is empty we should remove it - (if (and (= :group (:type parent)) - (empty? (:shapes parent))) - (-> data - (update :objects dissoc (:id parent)) - (update-in [:objects (:frame-id parent) :shapes] (strip-id (:id parent)))) - (update data :objects assoc parent-id' parent)))))) + (loop [sid shape-id + pid prev-parent-id + data data] + (let [obj (get-in data [:objects pid])] + (if (and (= 1 (count (:shapes obj))) + (= sid (first (:shapes obj))) + (= :group (:type obj))) + (recur pid + (:parent-id obj) + (update data :objects dissoc pid)) + (update-in data [:objects pid :shapes] (strip-id sid)))))))) parent (get-in data [:objects parent-id]) frame (if (= :frame (:type parent)) @@ -510,11 +447,16 @@ frame-id (:id frame) + ;; Update parent-id references. + update-parent-id + (fn [data id] + (update-in data [:objects id] assoc :parent-id parent-id)) + ;; Updates the frame-id references that might be outdated update-frame-ids (fn update-frame-ids [data id] (let [data (assoc-in data [:objects id :frame-id] frame-id) - obj (get-in data [:objects id])] + obj (get-in data [:objects id])] (cond-> data (not= :frame (:type obj)) (as-> $$ (reduce update-frame-ids $$ (:shapes obj))))))] @@ -522,9 +464,11 @@ (when valid? (as-> data $ (update-in $ [:objects parent-id :shapes] insert-items) - (reduce remove-in-parent $ shapes) + (reduce update-parent-id $ shapes) + (reduce remove-from-old-parent $ shapes) (reduce update-frame-ids $ (get-in $ [:objects parent-id :shapes])))))) + (defmethod process-operation :set [shape op] (let [attr (:attr op) diff --git a/common/uxbox/common/pages_migrations.cljc b/common/uxbox/common/pages_migrations.cljc index 4fc0a6ca9..02b8ac133 100644 --- a/common/uxbox/common/pages_migrations.cljc +++ b/common/uxbox/common/pages_migrations.cljc @@ -1,6 +1,6 @@ (ns uxbox.common.pages-migrations (:require - [uxbox.common.pages :as p] + [uxbox.common.pages :as cp] [uxbox.common.geom.shapes :as gsh] [uxbox.common.geom.point :as gpt] [uxbox.common.geom.matrix :as gmt] @@ -8,15 +8,17 @@ [uxbox.common.uuid :as uuid] [uxbox.common.data :as d])) +;; TODO: revisit this + (defmulti migrate :version) (defn migrate-data ([data] - (if (= (:version data) p/page-version) + (if (= (:version data) cp/page-version) data (reduce #(migrate-data %1 %2 (inc %2)) data - (range (:version data 0) p/page-version)))) + (range (:version data 0) cp/page-version)))) ([data from-version to-version] (-> data diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 0e12717ce..d5b4f16ff 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -16,6 +16,7 @@ [uxbox.common.data :as d] [uxbox.common.exceptions :as ex] [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] [uxbox.common.spec :as us] [uxbox.common.uuid :as uuid] [uxbox.config :as cfg] @@ -212,7 +213,7 @@ (initialize [state local] (let [page-id (get-in state [:workspace-page :id]) objects (get-in state [:workspace-data page-id :objects]) - shapes (cp/select-toplevel-shapes objects {:include-frames? true}) + shapes (cph/select-toplevel-shapes objects {:include-frames? true}) srect (geom/selection-rect shapes) local (assoc local :vport size)] (cond @@ -286,7 +287,7 @@ (let [page-id (:current-page-id state) objects (get-in state [:workspace-data page-id :objects]) groups-to-adjust (->> ids - (mapcat #(reverse (cp/get-all-parents % objects))) + (mapcat #(cph/get-parents % objects)) (map #(get objects %)) (filter #(= (:type %) :group)) (map #(:id %)) @@ -405,7 +406,7 @@ (update [_ state] (let [page-id (get-in state [:workspace-page :id]) objects (get-in state [:workspace-data page-id :objects]) - shapes (cp/select-toplevel-shapes objects {:include-frames? true}) + shapes (cph/select-toplevel-shapes objects {:include-frames? true}) srect (geom/selection-rect shapes)] (if (or (mth/nan? (:width srect)) @@ -481,7 +482,7 @@ unames (retrieve-used-names objects) name (generate-unique-name unames (:name shape)) - frames (cp/select-frames objects) + frames (cph/select-frames objects) frame-id (if (= :frame (:type shape)) uuid/zero @@ -578,7 +579,7 @@ grouped #{:frame :group}] (update-in state [:workspace-data page-id :objects] (fn [objects] - (->> (d/concat [id] (cp/get-children id objects)) + (->> (d/concat [id] (cph/get-children id objects)) (map #(get objects %)) (remove #(grouped (:type %))) (reduce #(update %1 (:id %2) update-shape) objects))))))))) @@ -647,6 +648,7 @@ :right (gpt/point 1 0))) ;; --- Delete Selected + (defn- delete-shapes [ids] (us/assert (s/coll-of ::us/uuid) ids) @@ -655,38 +657,57 @@ (watch [_ state stream] (let [page-id (:current-page-id state) objects (get-in state [:workspace-data page-id :objects]) - cpindex (cp/calculate-child-parent-map objects) del-change #(array-map :type :del-obj :id %) + reg-change #(array-map :type :reg-obj :id %) get-empty-parents - (fn get-empty-parents [id] - (let [parent (get objects (get cpindex id))] - (if (and (= :group (:type parent)) - (= 1 (count (:shapes parent)))) - (lazy-seq (cons (:id parent) - (get-empty-parents (:id parent)))) - nil))) + (fn get-empty-parents [parents] + (->> parents + (map (fn [id] + (let [obj (get objects id)] + (when (and (= :group (:type obj)) + (= 1 (count (:shapes obj)))) + obj)))) + (take-while (complement nil?)) + (map :id))) rchanges (reduce (fn [res id] - (let [chd (cp/get-children id objects)] - (into res (d/concat - (mapv del-change (reverse chd)) - [(del-change id)] - (map del-change (get-empty-parents id)))))) + (let [children (cph/get-children id objects) + parents (cph/get-parents id objects)] + (d/concat res + (map del-change (reverse children)) + [(del-change id)] + (map del-change (get-empty-parents parents)) + [{:type :reg-obj :ids parents}]))) [] ids) uchanges - (mapv (fn [id] - (let [obj (get objects id)] - {:type :add-obj - :id id - :frame-id (:frame-id obj) - :parent-id (get cpindex id) - :obj obj})) - (reverse (map :id rchanges)))] + (reduce (fn [res id] + (let [children (cph/get-children id objects) + parents (cph/get-parents id objects) + add-chg (fn [id] + (let [item (get objects id)] + {:type :add-obj + :id (:id item) + :frame-id (:frame-id item) + :parent-id (:parent-id item) + :obj item}))] + (d/concat res + (map add-chg (reverse (get-empty-parents parents))) + [(add-chg id)] + (map add-chg children) + [{:type :reg-obj :ids parents}]))) + [] + ids) + ] + + ;; (println "================ rchanges") + ;; (cljs.pprint/pprint rchanges) + ;; (println "================ uchanges") + ;; (cljs.pprint/pprint uchanges) (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true})))))) (def delete-selected @@ -764,7 +785,7 @@ (watch [_ state stream] (let [page-id (:current-page-id state) objects (get-in state [:workspace-data page-id :objects]) - parent (get objects (cp/get-parent id objects)) + parent (get objects (cph/get-parent id objects)) current-index (d/index-of (:shapes parent) id) selected (get-in state [:workspace-local :selected])] (rx/of (dwc/commit-changes [{:type :mov-objects @@ -992,7 +1013,7 @@ (update [_ state] (let [page-id (get-in state [:workspace-page :id]) objects (get-in state [:workspace-data page-id :objects]) - childs (cp/get-children id objects)] + childs (cph/get-children id objects)] (update-in state [:workspace-data page-id :objects] (fn [objects] (reduce (fn [objects id] @@ -1215,7 +1236,6 @@ (when (not-empty selected) (let [page-id (get-in state [:workspace-page :id]) objects (get-in state [:workspace-data page-id :objects]) - selected-objects (map (partial get objects) selected) selrect (geom/selection-rect selected-objects) frame-id (-> selected-objects first :frame-id) @@ -1226,7 +1246,6 @@ (map-indexed vector) (filter #(selected (second %))) (ffirst)) - rchanges [{:type :add-obj :id id :frame-id frame-id @@ -1255,7 +1274,7 @@ (when (and (= 1 (count selected)) (= (:type group) :group)) (let [shapes (:shapes group) - parent-id (cp/get-parent group-id objects) + parent-id (cph/get-parent group-id objects) parent (get objects parent-id) index-in-parent (->> (:shapes parent) (map-indexed vector) diff --git a/frontend/src/uxbox/main/data/workspace/common.cljs b/frontend/src/uxbox/main/data/workspace/common.cljs index fea80b434..5f97ce2b0 100644 --- a/frontend/src/uxbox/main/data/workspace/common.cljs +++ b/frontend/src/uxbox/main/data/workspace/common.cljs @@ -15,6 +15,7 @@ [potok.core :as ptk] [uxbox.common.data :as d] [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] [uxbox.common.spec :as us] [uxbox.common.uuid :as uuid] [uxbox.main.worker :as uw] @@ -202,8 +203,8 @@ (let [page-id (get-in state [:workspace-page :id]) objects (get-in state [:workspace-data page-id :objects]) - shapes (cp/select-toplevel-shapes objects) - frames (cp/select-frames objects) + shapes (cph/select-toplevel-shapes objects) + frames (cph/select-frames objects) [rch uch] (calculate-shape-to-frame-relationship-changes frames shapes)] (when-not (empty? rch) @@ -212,7 +213,7 @@ (defn get-frame-at-point [objects point] - (let [frames (cp/select-frames objects)] + (let [frames (cph/select-frames objects)] (loop [frame (first frames) rest (rest frames)] (d/seek #(geom/has-point? % point) frames)))) @@ -306,7 +307,7 @@ (let [expand-fn (fn [expanded] (merge expanded (->> ids - (map #(cp/get-all-parents % objects)) + (map #(cph/get-parents % objects)) flatten (filter #(not= % uuid/zero)) (map (fn [id] {id true})) diff --git a/frontend/src/uxbox/main/data/workspace/drawing.cljs b/frontend/src/uxbox/main/data/workspace/drawing.cljs index 803d12180..c42a1fc88 100644 --- a/frontend/src/uxbox/main/data/workspace/drawing.cljs +++ b/frontend/src/uxbox/main/data/workspace/drawing.cljs @@ -16,6 +16,7 @@ [uxbox.common.geom.shapes :as geom] [uxbox.common.geom.point :as gpt] [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] [uxbox.util.geom.path :as path] [uxbox.main.snap :as snap] [uxbox.main.streams :as ms] @@ -95,7 +96,7 @@ objects (get-in state [:workspace-data page-id :objects]) layout (get state :workspace-layout) - frames (cp/select-frames objects) + frames (cph/select-frames objects) fid (or (->> frames (filter #(geom/has-point? % initial)) first diff --git a/frontend/src/uxbox/main/data/workspace/selection.cljs b/frontend/src/uxbox/main/data/workspace/selection.cljs index 63642a807..10d634899 100644 --- a/frontend/src/uxbox/main/data/workspace/selection.cljs +++ b/frontend/src/uxbox/main/data/workspace/selection.cljs @@ -12,16 +12,17 @@ [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk] - [uxbox.main.data.workspace.common :as dwc] - [uxbox.main.worker :as uw] - [uxbox.main.streams :as ms] + [uxbox.common.data :as d] + [uxbox.common.geom.point :as gpt] + [uxbox.common.geom.shapes :as geom] + [uxbox.common.math :as mth] [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] [uxbox.common.spec :as us] [uxbox.common.uuid :as uuid] - [uxbox.common.data :as d] - [uxbox.common.geom.shapes :as geom] - [uxbox.common.geom.point :as gpt] - [uxbox.common.math :as mth])) + [uxbox.main.data.workspace.common :as dwc] + [uxbox.main.streams :as ms] + [uxbox.main.worker :as uw])) (s/def ::set-of-uuid (s/every uuid? :kind set?)) @@ -220,7 +221,7 @@ name (generate-unique-name names (:name obj)) renamed-obj (assoc obj :id id :name name) moved-obj (geom/move renamed-obj delta) - frames (cp/select-frames objects) + frames (cph/select-frames objects) frame-id (if frame-id frame-id (dwc/calculate-frame-overlap frames moved-obj)) diff --git a/frontend/src/uxbox/main/data/workspace/transforms.cljs b/frontend/src/uxbox/main/data/workspace/transforms.cljs index 2ad286aae..b08d82765 100644 --- a/frontend/src/uxbox/main/data/workspace/transforms.cljs +++ b/frontend/src/uxbox/main/data/workspace/transforms.cljs @@ -17,6 +17,7 @@ [uxbox.common.data :as d] [uxbox.common.spec :as us] [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] [uxbox.main.data.workspace.common :as dwc] [uxbox.main.data.workspace.selection :as dws] [uxbox.main.refs :as refs] @@ -346,7 +347,7 @@ (or recurse-frames? (not (= :frame (:type shape)))))) ;; ID's + Children but remove frame children if the flag is set to false - ids-with-children (concat ids (mapcat #(cp/get-children % objects) + ids-with-children (concat ids (mapcat #(cph/get-children % objects) (filter not-frame-id? ids))) ;; For each shape updates the modifiers given as arguments @@ -391,7 +392,7 @@ (let [objects (get-in state [:workspace-data page-id :objects]) id->obj #(get objects %) - get-children (fn [shape] (map id->obj (cp/get-children (:id shape) objects))) + get-children (fn [shape] (map id->obj (cph/get-children (:id shape) objects))) shapes (concat shapes (mapcat get-children shapes))] (rotate-around-center state delta-rotation center shapes)))))))) @@ -408,7 +409,7 @@ objects (get-in state [:workspace-data page-id :objects]) ;; ID's + Children - ids-with-children (concat ids (mapcat #(cp/get-children % objects) ids)) + ids-with-children (concat ids (mapcat #(cph/get-children % objects) ids)) ;; For each shape applies the modifiers by transforming the objects update-shape diff --git a/frontend/src/uxbox/main/exports.cljs b/frontend/src/uxbox/main/exports.cljs index 6802e84ad..c1b0abfaf 100644 --- a/frontend/src/uxbox/main/exports.cljs +++ b/frontend/src/uxbox/main/exports.cljs @@ -13,6 +13,7 @@ [rumext.alpha :as mf] [uxbox.common.uuid :as uuid] [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] [uxbox.common.math :as mth] [uxbox.common.geom.shapes :as geom] [uxbox.common.geom.point :as gpt] @@ -39,7 +40,7 @@ (defn- calculate-dimensions [{:keys [objects] :as data} vport] - (let [shapes (cp/select-toplevel-shapes objects {:include-frames? true})] + (let [shapes (cph/select-toplevel-shapes objects {:include-frames? true})] (->> (geom/selection-rect shapes) (geom/adjust-to-viewport vport) (geom/fix-invalid-rect-values)))) @@ -132,7 +133,7 @@ frame-id (:id frame) - modifier-ids (concat [frame-id] (cp/get-children frame-id objects)) + modifier-ids (concat [frame-id] (cph/get-children frame-id objects)) update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) objects (reduce update-fn objects modifier-ids) frame (assoc-in frame [:modifiers :displacement] modifier) diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index ce4454e0d..85dd108c9 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -10,12 +10,13 @@ (ns uxbox.main.refs "A collection of derived refs." (:require - [okulary.core :as l] [beicon.core :as rx] + [okulary.core :as l] [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] + [uxbox.common.uuid :as uuid] [uxbox.main.constants :as c] - [uxbox.main.store :as st] - [uxbox.common.uuid :as uuid])) + [uxbox.main.store :as st])) ;; ---- Global refs @@ -78,7 +79,7 @@ (l/derived :objects workspace-data)) (def workspace-frames - (l/derived cp/select-frames workspace-objects)) + (l/derived cph/select-frames workspace-objects)) (defn object-by-id [id] @@ -106,7 +107,7 @@ objects (get-in state [:workspace-data page-id :objects]) selected (get-in state [:workspace-local :selected]) shape (get objects id) - children (cp/get-children id objects)] + children (cph/get-children id objects)] (some selected children)))] (l/derived selector st/state))) @@ -118,7 +119,7 @@ (let [selected (get-in state [:workspace-local :selected]) page-id (get-in state [:workspace-page :id]) objects (get-in state [:workspace-data page-id :objects]) - children (mapcat #(cp/get-children % objects) selected)] + children (mapcat #(cph/get-children % objects) selected)] (into selected children)))] (l/derived selector st/state))) diff --git a/frontend/src/uxbox/main/ui/viewer/shapes.cljs b/frontend/src/uxbox/main/ui/viewer/shapes.cljs index 8011162eb..f2e0f88bc 100644 --- a/frontend/src/uxbox/main/ui/viewer/shapes.cljs +++ b/frontend/src/uxbox/main/ui/viewer/shapes.cljs @@ -13,6 +13,7 @@ [rumext.alpha :as mf] [uxbox.common.data :as d] [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] [uxbox.main.data.viewer :as dv] [uxbox.main.refs :as refs] [uxbox.main.store :as st] @@ -174,7 +175,7 @@ update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) frame-id (:id frame) - modifier-ids (d/concat [frame-id] (cp/get-children frame-id objects)) + modifier-ids (d/concat [frame-id] (cph/get-children frame-id objects)) objects (reduce update-fn objects modifier-ids) frame (assoc-in frame [:modifiers :displacement] modifier) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs index a26ade504..6b010a179 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs @@ -16,6 +16,7 @@ [uxbox.common.data :as d] [uxbox.common.uuid :as uuid] [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] [uxbox.main.data.workspace :as dw] [uxbox.main.refs :as refs] [uxbox.main.store :as st] @@ -154,7 +155,7 @@ (if (= side :center) (st/emit! (dw/relocate-shape id (:id item) 0)) (let [to-index (if (= side :top) (inc index) index) - parent-id (cp/get-parent (:id item) objects)] + parent-id (cph/get-parent (:id item) objects)] (st/emit! (dw/relocate-shape id parent-id to-index))))) on-hold @@ -223,7 +224,7 @@ old-obs (unchecked-get oprops "objects")] (and (= new-itm old-itm) (identical? new-idx old-idx) - (let [childs (cp/get-children (:id new-itm) new-obs) + (let [childs (cph/get-children (:id new-itm) new-obs) childs' (conj childs (:id new-itm))] (and (or (= new-sel old-sel) (not (or (boolean (some new-sel childs')) @@ -273,7 +274,7 @@ (defn- strip-objects [objects] - (let [strip-data #(select-keys % [:id :name :blocked :hidden :shapes :type :content :metadata])] + (let [strip-data #(select-keys % [:id :name :blocked :hidden :shapes :type :content :parent-id :metadata])] (persistent! (reduce-kv (fn [res id obj] (assoc! res id (strip-data obj))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs index f389fa7ca..09ae5f85a 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/interactions.cljs @@ -11,7 +11,7 @@ (:require [rumext.alpha :as mf] [uxbox.main.ui.icons :as i] - [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] [uxbox.main.data.workspace :as dw] [uxbox.main.refs :as refs] [uxbox.main.store :as st] @@ -31,7 +31,7 @@ destination (get objects (:destination interaction)) frames (mf/use-memo (mf/deps objects) - #(cp/select-frames objects)) + #(cph/select-frames objects)) show-frames-dropdown? (mf/use-state false) diff --git a/frontend/src/uxbox/worker/selection.cljs b/frontend/src/uxbox/worker/selection.cljs index b75dc7f22..06a4af085 100644 --- a/frontend/src/uxbox/worker/selection.cljs +++ b/frontend/src/uxbox/worker/selection.cljs @@ -12,12 +12,13 @@ [cljs.spec.alpha :as s] [okulary.core :as l] [uxbox.common.exceptions :as ex] - [uxbox.common.spec :as us] - [uxbox.common.pages :as cp] - [uxbox.common.uuid :as uuid] - [uxbox.worker.impl :as impl] [uxbox.common.geom.shapes :as geom] - [uxbox.util.quadtree :as qdt])) + [uxbox.common.pages :as cp] + [uxbox.common.pages-helpers :as cph] + [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid] + [uxbox.util.quadtree :as qdt] + [uxbox.worker.impl :as impl])) (defonce state (l/atom {})) @@ -63,7 +64,7 @@ (defn- create-index [objects] - (let [shapes (->> (cp/select-toplevel-shapes objects {:include-frames? true}) + (let [shapes (->> (cph/select-toplevel-shapes objects {:include-frames? true}) (map #(merge % (select-keys % [:x :y :width :height])))) bounds (geom/selection-rect shapes) bounds #js {:x (:x bounds) diff --git a/frontend/src/uxbox/worker/snaps.cljs b/frontend/src/uxbox/worker/snaps.cljs index 0d41634df..4558750bb 100644 --- a/frontend/src/uxbox/worker/snaps.cljs +++ b/frontend/src/uxbox/worker/snaps.cljs @@ -12,7 +12,8 @@ [okulary.core :as l] [uxbox.common.uuid :as uuid] [uxbox.common.pages :as cp] - [uxbox.common.data :as d] + [uxbox.common.pages-helpers :as cph] + [uxbox.common.data :as d] [uxbox.worker.impl :as impl] [uxbox.util.range-tree :as rt] [uxbox.util.geom.snap-points :as snap] @@ -45,7 +46,7 @@ (let [frame-shapes (->> (vals objects) (filter :frame-id) (group-by :frame-id)) - frame-shapes (->> (cp/select-frames objects) + frame-shapes (->> (cph/select-frames objects) (reduce #(update %1 (:id %2) conj %2) frame-shapes))] (d/mapm (fn [frame-id shapes] {:x (create-coord-data frame-id shapes :x) :y (create-coord-data frame-id shapes :y)}) From a1ff567b307edca29b8aa836f61e17206c6f7cef Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 12 Jun 2020 10:43:58 +0200 Subject: [PATCH 4/9] :lipstick: Minor improvements on outlines component. --- .../src/uxbox/main/ui/workspace/viewport.cljs | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/frontend/src/uxbox/main/ui/workspace/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index 2366fc129..839512ba1 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -116,6 +116,21 @@ (declare remote-user-cursors) +(mf/defc shape-outlines + {::mf/wrap-props false} + [props] + (let [objects (unchecked-get props "objects") + selected? (or (mf/deref refs/selected-shapes) #{}) + hover? (or (mf/deref refs/current-hover) #{}) + outline? (set/union selected? hover?) + shapes (->> (vals objects) (filter (comp outline? :id))) + transform (mf/deref refs/current-transform)] + (when (nil? transform) + [:g.outlines + (for [shape shapes] + [:& outline {:key (str "outline-" (:id shape)) + :shape (gsh/transform-shape shape)}])]))) + (mf/defc frames {:wrap [mf/memo]} [] @@ -124,27 +139,17 @@ root (get objects uuid/zero) shapes (->> (:shapes root) (map #(get objects %)))] - [:g.shapes - (for [item shapes] - (if (= (:type item) :frame) - [:& frame-wrapper {:shape item - :key (:id item) - :objects objects}] - [:& shape-wrapper {:shape item - :key (:id item)}]))])) + [:* + [:g.shapes + (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 shape-outlines [] - (let [selected-shape? (or (mf/deref refs/selected-shapes) #{}) - hover? (or (mf/deref refs/current-hover) #{}) - outline? (set/union selected-shape? hover?) - data (mf/deref refs/workspace-data) - shapes (->> data :objects vals (filter (comp outline? :id))) - current-transform (mf/deref refs/current-transform)] - (when (nil? current-transform) - [:g.outlines - (for [shape shapes] - [:& outline {:key (str "outline-" (:id shape)) - :shape (gsh/transform-shape shape)}])]))) + [:& shape-outlines {:objects objects}]])) (mf/defc viewport [{:keys [page local layout] :as props}] @@ -432,8 +437,6 @@ [:g [:& frames {:key (:id page)}] - [:& shape-outlines] - (when (seq selected) [:& selection-handlers {:selected selected :zoom zoom From 9ed6d23041599e09c2bcc758979f2c58e89cdcc7 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 12 Jun 2020 10:51:09 +0200 Subject: [PATCH 5/9] :bug: Group shapes in the correct parent. --- frontend/src/uxbox/main/data/workspace.cljs | 49 ++++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index d5b4f16ff..8ad41e9de 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -1236,29 +1236,34 @@ (when (not-empty selected) (let [page-id (get-in state [:workspace-page :id]) objects (get-in state [:workspace-data page-id :objects]) - selected-objects (map (partial get objects) selected) - selrect (geom/selection-rect selected-objects) - frame-id (-> selected-objects first :frame-id) - group (-> (group-shape id frame-id selected selrect) - (geom/setup selrect)) - index (->> (get-in objects [frame-id :shapes]) - (map-indexed vector) - (filter #(selected (second %))) - (ffirst)) - rchanges [{:type :add-obj - :id id - :frame-id frame-id - :obj group - :index index} - {:type :mov-objects - :parent-id id - :shapes (vec selected)}] - uchanges [{:type :mov-objects - :parent-id frame-id - :shapes (vec selected)} - {:type :del-obj - :id id}]] + items (map #(get objects %) selected) + selrect (geom/selection-rect items) + frame-id (-> items first :frame-id) + parent-id (-> items first :parent-id) + group (-> (group-shape id frame-id selected selrect) + (geom/setup selrect)) + + index (->> (get-in objects [frame-id :shapes]) + (map-indexed vector) + (filter #(selected (second %))) + (ffirst)) + + rchanges [{:type :add-obj + :id id + :frame-id frame-id + :parent-id parent-id + :obj group + :index index} + {:type :mov-objects + :parent-id id + :shapes (vec selected)}] + uchanges [{:type :mov-objects + :parent-id frame-id + :shapes (vec selected)} + {:type :del-obj + :id id}]] + (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) (dws/select-shapes #{id})))))))) From 861150ff466ea6a2bc4e4bfc1d21f85813caefb2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 12 Jun 2020 11:58:49 +0200 Subject: [PATCH 6/9] :bug: Fix positioning on group and ungroup. --- common/uxbox/common/pages_helpers.cljc | 7 ++++++ frontend/src/uxbox/main/data/workspace.cljs | 24 +++++++++++++-------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/common/uxbox/common/pages_helpers.cljc b/common/uxbox/common/pages_helpers.cljc index 1bcc63d3d..81e21cfd2 100644 --- a/common/uxbox/common/pages_helpers.cljc +++ b/common/uxbox/common/pages_helpers.cljc @@ -60,6 +60,13 @@ (or (not= (:type shape) :frame) (= parent-id uuid/zero)))) +(defn position-on-parent + [id objects] + (let [obj (get objects id) + pid (:parent-id obj) + prt (get objects pid)] + (d/index-of (:shapes prt) id))) + (defn insert-at-index [shapes index ids] (let [[before after] (split-at index shapes) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 8ad41e9de..c5dc1b901 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -1244,10 +1244,7 @@ group (-> (group-shape id frame-id selected selrect) (geom/setup selrect)) - index (->> (get-in objects [frame-id :shapes]) - (map-indexed vector) - (filter #(selected (second %))) - (ffirst)) + index (cph/position-on-parent (:id (first items)) objects) rchanges [{:type :add-obj :id id @@ -1258,11 +1255,20 @@ {:type :mov-objects :parent-id id :shapes (vec selected)}] - uchanges [{:type :mov-objects - :parent-id frame-id - :shapes (vec selected)} - {:type :del-obj - :id id}]] + + uchanges + (reduce (fn [res obj] + (conj res {:type :mov-objects + :parent-id (:parent-id obj) + :index (:index obj) + :shapes [(:id obj)]})) + [] + (->> selected + (map #(get objects %)) + (map #(assoc % :index (cph/position-on-parent (:id %) objects))) + (sort-by :index))) + + uchanges (conj uchanges {:type :del-obj :id id})] (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) (dws/select-shapes #{id})))))))) From 91c07d55730b7819e34d060854ba620977cd493d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 12 Jun 2020 12:20:25 +0200 Subject: [PATCH 7/9] :bug: Properly handle grouping from multiple frames. --- frontend/src/uxbox/main/data/workspace.cljs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index c5dc1b901..6db57a55c 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -1232,13 +1232,14 @@ ptk/WatchEvent (watch [_ state stream] (let [id (uuid/next) - selected (get-in state [:workspace-local :selected])] - (when (not-empty selected) - (let [page-id (get-in state [:workspace-page :id]) - objects (get-in state [:workspace-data page-id :objects]) - - items (map #(get objects %) selected) - selrect (geom/selection-rect items) + page-id (get-in state [:workspace-page :id]) + selected (get-in state [:workspace-local :selected]) + objects (get-in state [:workspace-data page-id :objects]) + items (->> selected + (map #(get objects %)) + (filter #(not= :frame (:type %))))] + (when (not-empty items) + (let [selrect (geom/selection-rect items) frame-id (-> items first :frame-id) parent-id (-> items first :parent-id) group (-> (group-shape id frame-id selected selrect) From 8874f0b0c8ccb2c0d90017bb01958dd00275c7a1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 12 Jun 2020 15:58:30 +0200 Subject: [PATCH 8/9] :bug: Fix outline leaking on grouping shapes. --- .../uxbox/main/data/workspace/selection.cljs | 17 ++++++++------ .../src/uxbox/main/ui/workspace/shapes.cljs | 21 ++++++++++------- .../src/uxbox/main/ui/workspace/viewport.cljs | 23 ++++++++++++------- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/frontend/src/uxbox/main/data/workspace/selection.cljs b/frontend/src/uxbox/main/data/workspace/selection.cljs index 10d634899..b3564e961 100644 --- a/frontend/src/uxbox/main/data/workspace/selection.cljs +++ b/frontend/src/uxbox/main/data/workspace/selection.cljs @@ -299,10 +299,13 @@ (rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}) (select-shapes selected)))))) -(defn change-hover-state [id value] - (ptk/reify ::change-hover-state - ptk/UpdateEvent - (update [_ state] - (-> state - (update-in [:workspace-local :hover] #(if (nil? %) #{} %)) - (update-in [:workspace-local :hover] (comp (if value conj disj)) id))))) +(defn change-hover-state + [id value] + (letfn [(update-hover [items] + (if value + (conj items id) + (disj items id)))] + (ptk/reify ::change-hover-state + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-local :hover] (fnil update-hover #{})))))) diff --git a/frontend/src/uxbox/main/ui/workspace/shapes.cljs b/frontend/src/uxbox/main/ui/workspace/shapes.cljs index a2a64fc46..0d4ff2006 100644 --- a/frontend/src/uxbox/main/ui/workspace/shapes.cljs +++ b/frontend/src/uxbox/main/ui/workspace/shapes.cljs @@ -54,17 +54,17 @@ (and (identical? n-shape o-shape) (identical? n-frame o-frame))))) -(defn use-mouse-over +(defn use-mouse-enter [{:keys [id] :as shape}] (mf/use-callback - (mf/deps shape) + (mf/deps id) (fn [] (st/emit! (dws/change-hover-state id true))))) -(defn use-mouse-out +(defn use-mouse-leave [{:keys [id] :as shape}] (mf/use-callback - (mf/deps shape) + (mf/deps id) (fn [] (st/emit! (dws/change-hover-state id false))))) @@ -78,14 +78,19 @@ opts #js {:shape shape :frame frame} alt? (mf/use-state false) - on-mouse-over (use-mouse-over shape) - on-mouse-out (use-mouse-out shape)] + on-mouse-enter (use-mouse-enter shape) + on-mouse-leave (use-mouse-leave shape)] (hooks/use-stream ms/keyboard-alt #(reset! alt? %)) + (mf/use-effect + (fn [] + (fn [] + (on-mouse-leave)))) + (when (and shape (not (:hidden shape))) - [:g.shape-wrapper {:on-mouse-over on-mouse-over - :on-mouse-out on-mouse-out + [:g.shape-wrapper {:on-mouse-enter on-mouse-enter + :on-mouse-leave on-mouse-leave :style {:cursor (if @alt? cur/duplicate nil)}} (case (:type shape) :curve [:> path/path-wrapper opts] diff --git a/frontend/src/uxbox/main/ui/workspace/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index 839512ba1..e7468850f 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -120,9 +120,9 @@ {::mf/wrap-props false} [props] (let [objects (unchecked-get props "objects") - selected? (or (mf/deref refs/selected-shapes) #{}) - hover? (or (mf/deref refs/current-hover) #{}) - outline? (set/union selected? hover?) + selected (or (unchecked-get props "selected") #{}) + hover (or (unchecked-get props "hover") #{}) + outline? (set/union selected hover) shapes (->> (vals objects) (filter (comp outline? :id))) transform (mf/deref refs/current-transform)] (when (nil? transform) @@ -132,9 +132,12 @@ :shape (gsh/transform-shape shape)}])]))) (mf/defc frames - {:wrap [mf/memo]} - [] - (let [data (mf/deref refs/workspace-data) + {::mf/wrap [mf/memo] + ::mf/wrap-props false} + [props] + (let [data (mf/deref refs/workspace-data) + hover (unchecked-get props "hover") + selected (unchecked-get props "selected") objects (:objects data) root (get objects uuid/zero) shapes (->> (:shapes root) @@ -149,7 +152,9 @@ [:& shape-wrapper {:shape item :key (:id item)}]))] - [:& shape-outlines {:objects objects}]])) + [:& shape-outlines {:objects objects + :selected selected + :hover hover}]])) (mf/defc viewport [{:keys [page local layout] :as props}] @@ -435,7 +440,9 @@ :on-drop on-drop} [:g - [:& frames {:key (:id page)}] + [:& frames {:key (:id page) + :hover (:hover local) + :selected (:selected selected)}] (when (seq selected) [:& selection-handlers {:selected selected From 7b0061c03811e154e233b06b673b6d02bc9fc6ae Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 12 Jun 2020 16:02:46 +0200 Subject: [PATCH 9/9] :bug: Fix ordering on delete shape undo operation. --- frontend/src/uxbox/main/data/workspace.cljs | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 6db57a55c..5d75023bc 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -692,6 +692,7 @@ (let [item (get objects id)] {:type :add-obj :id (:id item) + :index (cph/position-on-parent id objects) :frame-id (:frame-id item) :parent-id (:parent-id item) :obj item}))]