From 650bbabe945897c79af3f6286ff2722621450f78 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sat, 9 Apr 2016 23:13:25 +0300
Subject: [PATCH 01/19] Update externs file with more interop calls.

---
 resources/externs/main.js | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/resources/externs/main.js b/resources/externs/main.js
index be5aed904..1e3d5eb8c 100644
--- a/resources/externs/main.js
+++ b/resources/externs/main.js
@@ -25,5 +25,8 @@ var TopLevel = {
   "value": function() {},
   "identifier": function() {},
   "getBoundingClientRect": function() {},
-  "getBBox": function() {}
+  "getBBox": function() {},
+  "addEventListener": function() {},
+  "postMessage": function() {},
+  "data": function() {}
 };

From d976afda30c571dd2ecd7e0eb6581d29dfeac64b Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sat, 9 Apr 2016 23:13:54 +0300
Subject: [PATCH 02/19] Fix wrong filename on watch-tests script.

---
 scripts/watch-tests | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/watch-tests b/scripts/watch-tests
index 6958be417..76b57dda5 100755
--- a/scripts/watch-tests
+++ b/scripts/watch-tests
@@ -1,2 +1,2 @@
 #!/bin/sh
-lein trampoline run -m clojure.main scripts/watch.clj
+lein trampoline run -m clojure.main scripts/watch-tests.clj

From 89a21d7abb6948df5f14822bb54551af2ac49cde Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sat, 9 Apr 2016 23:14:21 +0300
Subject: [PATCH 03/19] Add update-shape event just for shape inplace update.

---
 src/uxbox/data/shapes.cljs | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/uxbox/data/shapes.cljs b/src/uxbox/data/shapes.cljs
index b552a6883..474a078b7 100644
--- a/src/uxbox/data/shapes.cljs
+++ b/src/uxbox/data/shapes.cljs
@@ -94,6 +94,14 @@
       (let [shape (get-in state [:shapes-by-id id])]
         (stsh/dissoc-shape state shape)))))
 
+(defn update-shape
+  "Just updates in place the shape."
+  [{:keys [id] :as shape}]
+  (reify
+    rs/UpdateEvent
+    (-apply-update [_ state]
+      (update-in state [:shapes-by-id id] merge shape))))
+
 (defn move-shape
   "Mark a shape selected for drawing in the canvas."
   [sid delta]

From 3dd741129c4b19cf540e0598f6c86a8c259f4575 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sat, 9 Apr 2016 23:14:38 +0300
Subject: [PATCH 04/19] Improve move-shape event constructor docstring.

---
 src/uxbox/data/shapes.cljs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/uxbox/data/shapes.cljs b/src/uxbox/data/shapes.cljs
index 474a078b7..7934dda3a 100644
--- a/src/uxbox/data/shapes.cljs
+++ b/src/uxbox/data/shapes.cljs
@@ -103,9 +103,8 @@
       (update-in state [:shapes-by-id id] merge shape))))
 
 (defn move-shape
-  "Mark a shape selected for drawing in the canvas."
+  "Move shape using relative position (delta)."
   [sid delta]
-  {:pre [(gpt/point? delta)]}
   (reify
     udp/IPageUpdate
     rs/UpdateEvent

From 6c64279c9437928ba2da3a82d0062878a252708f Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sat, 9 Apr 2016 23:15:44 +0300
Subject: [PATCH 05/19] Move transit ns under uxbox.util.

---
 src/uxbox/repo/core.cljs          | 2 +-
 src/uxbox/{ => util}/transit.cljs | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)
 rename src/uxbox/{ => util}/transit.cljs (96%)

diff --git a/src/uxbox/repo/core.cljs b/src/uxbox/repo/core.cljs
index 3f152186a..18105b4e7 100644
--- a/src/uxbox/repo/core.cljs
+++ b/src/uxbox/repo/core.cljs
@@ -13,7 +13,7 @@
             [hodgepodge.core :refer (local-storage)]
             [promesa.core :as p :include-macros true]
             [beicon.core :as rx]
-            [uxbox.transit :as t]
+            [uxbox.util.transit :as t]
             [uxbox.state :as ust])
   (:import [goog.Uri QueryData]))
 
diff --git a/src/uxbox/transit.cljs b/src/uxbox/util/transit.cljs
similarity index 96%
rename from src/uxbox/transit.cljs
rename to src/uxbox/util/transit.cljs
index 4718c6922..e5b8e403e 100644
--- a/src/uxbox/transit.cljs
+++ b/src/uxbox/util/transit.cljs
@@ -4,9 +4,8 @@
 ;;
 ;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
 
-(ns uxbox.transit
+(ns uxbox.util.transit
   "A lightweight abstraction for transit serialization."
-  (:refer-clojure :exclude [do])
   (:require [cognitect.transit :as t]
             [uxbox.util.data :refer (parse-int)]
             [uxbox.util.datetime :as dt]))

From b66be25a8e38064433b0ed3d2ca7f7af94aace36 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sat, 9 Apr 2016 23:17:31 +0300
Subject: [PATCH 06/19] Minor comments change.

---
 src/uxbox/shapes.cljs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/uxbox/shapes.cljs b/src/uxbox/shapes.cljs
index 374d74136..1e49d63c8 100644
--- a/src/uxbox/shapes.cljs
+++ b/src/uxbox/shapes.cljs
@@ -211,7 +211,7 @@
   [shape _]
   (throw (ex-info "Not implemented (size)" (select-keys shape [:type]))))
 
-;; Move
+;; Move (with deltas)
 
 (defmethod move ::rect
   [shape {dx :x dy :y}]

From bfc0fc0f987ab3217f8c3443b0e93fc52ae75197 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sat, 9 Apr 2016 23:17:43 +0300
Subject: [PATCH 07/19] Temporary comment duplicated code.

---
 src/uxbox/ui/shapes/circle.cljs | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/uxbox/ui/shapes/circle.cljs b/src/uxbox/ui/shapes/circle.cljs
index ba34d7e1d..715362baa 100644
--- a/src/uxbox/ui/shapes/circle.cljs
+++ b/src/uxbox/ui/shapes/circle.cljs
@@ -20,20 +20,20 @@
 
 (declare handlers)
 
-(defmethod uusc/render-component :default ;; :builtin/icon
-  [own shape]
-  (let [{:keys [id x y width height group]} shape
-        selected (rum/react uusc/selected-shapes-l)
-        selected? (contains? selected id)
-        on-mouse-down #(uusi/on-mouse-down % shape selected)
-        on-mouse-up #(uusi/on-mouse-up % shape)]
-    (html
-     [:g.shape {:class (when selected? "selected")
-                :on-mouse-down on-mouse-down
-                :on-mouse-up on-mouse-up}
-      (uusc/render-shape shape #(uusc/shape %))
-      (when (and selected? (= (count selected) 1))
-        (handlers shape))])))
+;; (defmethod uusc/render-component :default ;; :builtin/icon
+;;   [own shape]
+;;   (let [{:keys [id x y width height group]} shape
+;;         selected (rum/react uusc/selected-shapes-l)
+;;         selected? (contains? selected id)
+;;         on-mouse-down #(uusi/on-mouse-down % shape selected)
+;;         on-mouse-up #(uusi/on-mouse-up % shape)]
+;;     (html
+;;      [:g.shape {:class (when selected? "selected")
+;;                 :on-mouse-down on-mouse-down
+;;                 :on-mouse-up on-mouse-up}
+;;       (uusc/render-shape shape #(uusc/shape %))
+;;       (when (and selected? (= (count selected) 1))
+;;         (handlers shape))])))
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Circle Handlers

From 50e8de4552fa5d2ae939ceda1d2cbe7364b78583 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sat, 9 Apr 2016 23:18:04 +0300
Subject: [PATCH 08/19] Send the select-shape event before acquire move action.

---
 src/uxbox/ui/shapes/icon.cljs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/uxbox/ui/shapes/icon.cljs b/src/uxbox/ui/shapes/icon.cljs
index 48d97aadc..44e16f3de 100644
--- a/src/uxbox/ui/shapes/icon.cljs
+++ b/src/uxbox/ui/shapes/icon.cljs
@@ -28,8 +28,8 @@
         (and (not selected?) (empty? selected))
         (do
           (dom/stop-propagation event)
-          (uuc/acquire-action! "ui.shape.move")
-          (rs/emit! (dw/select-shape id)))
+          (rs/emit! (dw/select-shape id))
+          (uuc/acquire-action! "ui.shape.move"))
 
         (and (not selected?) (not (empty? selected)))
         (do

From 69329f7f6452f3e8e80d45ba363af93a3c29df9d Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sat, 9 Apr 2016 23:19:11 +0300
Subject: [PATCH 09/19] Make grid fill all the workspace instead only canvas.

---
 src/uxbox/ui/workspace/canvas.cljs | 13 +++++++------
 src/uxbox/ui/workspace/grid.cljs   |  4 +++-
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/src/uxbox/ui/workspace/canvas.cljs b/src/uxbox/ui/workspace/canvas.cljs
index 2fa038d92..1f3fb3363 100644
--- a/src/uxbox/ui/workspace/canvas.cljs
+++ b/src/uxbox/ui/workspace/canvas.cljs
@@ -69,9 +69,7 @@
         (for [item (reverse (:shapes page))]
           (-> (uus/shape item)
               (rum/with-key (str item))))
-        (draw-area)]]
-      (when (contains? flags :grid)
-        (grid))])))
+        (draw-area)]]])))
 
 (def canvas
   (mx/component
@@ -87,6 +85,7 @@
   [own]
   (let [workspace (rum/react uuwb/workspace-l)
         page (rum/react uuwb/page-l)
+        flags (:flags workspace)
         drawing? (:drawing workspace)
         zoom (or (:zoom workspace) 1)]
     (letfn [(on-mouse-down [event]
@@ -109,9 +108,11 @@
                        :on-mouse-up on-mouse-up}
         [:g.zoom {:transform (str "scale(" zoom ", " zoom ")")}
          (if page
-           (canvas page))]
-         (ruler)
-         (selrect)]))))
+           (canvas page))
+         (if (contains? flags :grid)
+           (grid))]
+        (ruler)
+        (selrect)]))))
 
 (defn- viewport-did-mount
   [own]
diff --git a/src/uxbox/ui/workspace/grid.cljs b/src/uxbox/ui/workspace/grid.cljs
index af2813ba1..5e7b8a255 100644
--- a/src/uxbox/ui/workspace/grid.cljs
+++ b/src/uxbox/ui/workspace/grid.cljs
@@ -20,8 +20,10 @@
 
 (defn- grid-render
   [own]
-  (let [{:keys [width height options]} (deref wb/page-l)
+  (let [options (:options @wb/page-l)
         color (:grid/color options "#cccccc")
+        width wb/viewport-width
+        height wb/viewport-height
         x-ticks (ticks-range width (:grid/x-axis options 10))
         y-ticks (ticks-range height (:grid/y-axis options 10))
         path (as-> [] $

From 551c95b8fe72578d9dfb5fa1c0ed68c736daa200 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sat, 9 Apr 2016 23:20:00 +0300
Subject: [PATCH 10/19] Change the shapes moviment tracking approach.

Will facilitate grid alignment.
---
 src/uxbox/ui/workspace/movement.cljs | 32 +++++++++++++++++++---------
 1 file changed, 22 insertions(+), 10 deletions(-)

diff --git a/src/uxbox/ui/workspace/movement.cljs b/src/uxbox/ui/workspace/movement.cljs
index 08541eef4..b80d30485 100644
--- a/src/uxbox/ui/workspace/movement.cljs
+++ b/src/uxbox/ui/workspace/movement.cljs
@@ -8,16 +8,31 @@
 (ns uxbox.ui.workspace.movement
   "Shape movement in workspace logic."
   (:require [beicon.core :as rx]
+            [lentes.core :as l]
             [uxbox.rstore :as rs]
             [uxbox.state :as st]
+            [uxbox.shapes :as sh]
             [uxbox.ui.core :as uuc]
             [uxbox.ui.workspace.base :as wb]
+            [uxbox.ui.workspace.align :as align]
             [uxbox.data.shapes :as uds]
             [uxbox.util.geom.point :as gpt]))
 
 (declare initialize)
 (declare handle-movement)
 
+;; --- Lenses
+
+(defn- resolve-selected
+  [state]
+  (let [selected (get-in state [:workspace :selected])
+        xf (map #(get-in state [:shapes-by-id %]))]
+    (into #{} xf selected)))
+
+(def ^:const ^:private selected-shapes-l
+  (-> (l/getter resolve-selected)
+      (l/focus-atom st/state)))
+
 ;; --- Public Api
 
 (defn watch-move-actions
@@ -30,22 +45,19 @@
 
 (defn- initialize
   []
-  (let [stoper (->> uuc/actions-s
+  (let [shapes @selected-shapes-l
+        stoper (->> uuc/actions-s
                     (rx/map :type)
                     (rx/filter empty?)
                     (rx/take 1))]
     (as-> wb/mouse-delta-s $
       (rx/take-until stoper $)
-      (rx/on-value $ handle-movement))))
+      (rx/scan (fn [acc delta]
+                 (mapv #(sh/move % delta) acc)) shapes $)
+      (rx/subscribe $ handle-movement))))
 
 (defn- handle-movement
   [delta]
-  (let [pageid (get-in @st/state [:workspace :page])
-        selected (get-in @st/state [:workspace :selected])
-        shapes (->> (vals @wb/shapes-by-id-l)
-                    (filter #(= (:page %) pageid))
-                    (filter (comp selected :id)))
-        delta (gpt/divide delta @wb/zoom-l)]
-    (doseq [{:keys [id group]} shapes]
-      (rs/emit! (uds/move-shape id delta)))))
+  (doseq [shape delta]
+    (rs/emit! (uds/update-shape shape))))
 

From bcc82bc5cf4fb0239ab3f4fa69ac3cf2bbbe6e30 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sun, 10 Apr 2016 18:01:25 +0300
Subject: [PATCH 11/19] Move shape related events from data.workspace to
 data.shapes.

---
 src/uxbox/data/shapes.cljs    | 141 ++++++++++++++++++++++++++++++++
 src/uxbox/data/workspace.cljs | 150 +---------------------------------
 2 files changed, 144 insertions(+), 147 deletions(-)

diff --git a/src/uxbox/data/shapes.cljs b/src/uxbox/data/shapes.cljs
index 7934dda3a..d2c3ab8c7 100644
--- a/src/uxbox/data/shapes.cljs
+++ b/src/uxbox/data/shapes.cljs
@@ -345,3 +345,144 @@
     (-apply-update [_ state]
       (stsh/drop-shape state sid tid loc))))
 
+(defn select-shape
+  "Mark a shape selected for drawing in the canvas."
+  [id]
+  (reify
+    rs/UpdateEvent
+    (-apply-update [_ state]
+      (let [selected (get-in state [:workspace :selected])]
+        (if (contains? selected id)
+          (update-in state [:workspace :selected] disj id)
+          (update-in state [:workspace :selected] conj id))))))
+
+(defn select-shapes
+  "Select shapes that matches the select rect."
+  [selrect]
+  (reify
+    rs/UpdateEvent
+    (-apply-update [_ state]
+      (let [pageid (get-in state [:workspace :page])
+            xf (comp
+                (filter #(= (:page %) pageid))
+                (remove :hidden)
+                (remove :blocked)
+                (map sh/outer-rect')
+                (filter #(sh/contained-in? % selrect))
+                (map :id))]
+        (->> (into #{} xf (vals (:shapes-by-id state)))
+             (assoc-in state [:workspace :selected]))))))
+
+;; --- Events (implicit) (for selected)
+
+(defn deselect-all
+  "Mark a shape selected for drawing in the canvas."
+  []
+  (reify
+    rs/UpdateEvent
+    (-apply-update [_ state]
+      (assoc-in state [:workspace :selected] #{}))))
+
+(defn group-selected
+  []
+  (letfn [(update-shapes-on-page [state pid selected group]
+            (as-> (get-in state [:pages-by-id pid :shapes]) $
+              (remove selected $)
+              (into [group] $)
+              (assoc-in state [:pages-by-id pid :shapes] $)))
+
+          (update-shapes-on-index [state shapes group]
+            (reduce (fn [state {:keys [id] :as shape}]
+                      (as-> shape $
+                        (assoc $ :group group)
+                        (assoc-in state [:shapes-by-id id] $)))
+                    state
+                    shapes))
+          (valid-selection? [shapes]
+            (let [groups (into #{} (map :group shapes))]
+              (= 1 (count groups))))]
+    (reify
+      udp/IPageUpdate
+      rs/UpdateEvent
+      (-apply-update [_ state]
+        (let [shapes-by-id (get state :shapes-by-id)
+              sid (random-uuid)
+              pid (get-in state [:workspace :page])
+              selected (get-in state [:workspace :selected])
+              selected' (map #(get shapes-by-id %) selected)
+              group {:type :builtin/group
+                    :name (str "Group " (rand-int 1000))
+                    :items (into [] selected)
+                    :id sid
+                    :page pid}]
+          (if (valid-selection? selected')
+            (as-> state $
+              (update-shapes-on-index $ selected' sid)
+              (update-shapes-on-page $ pid selected sid)
+              (update $ :shapes-by-id assoc sid group)
+              (update $ :workspace assoc :selected #{}))
+            state))))))
+
+;; TODO: maybe split in two separate events
+(defn duplicate-selected
+  []
+  (reify
+    udp/IPageUpdate
+    rs/UpdateEvent
+    (-apply-update [_ state]
+      (let [selected (get-in state [:workspace :selected])]
+        (stsh/duplicate-shapes state selected)))))
+
+(defn delete-selected
+  "Deselect all and remove all selected shapes."
+  []
+  (reify
+    rs/WatchEvent
+    (-apply-watch [_ state s]
+      (let [selected (get-in state [:workspace :selected])]
+        (rx/from-coll
+         (into [(deselect-all)] (map #(delete-shape %) selected)))))))
+
+(defn move-selected
+  "Move a minimal position unit the selected shapes."
+  ([dir] (move-selected dir 1))
+  ([dir n]
+   {:pre [(contains? #{:up :down :right :left} dir)]}
+   (reify
+     rs/WatchEvent
+     (-apply-watch [_ state s]
+       (let [selected (get-in state [:workspace :selected])
+             delta (case dir
+                    :up (gpt/point 0 (- n))
+                    :down (gpt/point 0 n)
+                    :right (gpt/point n 0)
+                    :left (gpt/point (- n) 0))]
+         (rx/from-coll
+          (map #(move-shape % delta) selected)))))))
+
+(defn update-selected-shapes-fill
+  "Update the fill related attributed on
+  selected shapes."
+  [opts]
+  (sc/validate! +shape-fill-attrs-schema+ opts)
+  (reify
+    rs/WatchEvent
+    (-apply-watch [_ state s]
+      (rx/from-coll
+       (->> (get-in state [:workspace :selected])
+            (map #(update-fill-attrs % opts)))))))
+
+
+(defn update-selected-shapes-stroke
+  "Update the fill related attributed on
+  selected shapes."
+  [opts]
+  (sc/validate! +shape-stroke-attrs-schema+ opts)
+  (reify
+    rs/WatchEvent
+    (-apply-watch [_ state s]
+      (rx/from-coll
+       (->> (get-in state [:workspace :selected])
+            (map #(update-stroke-attrs % opts)))))))
+
+
diff --git a/src/uxbox/data/workspace.cljs b/src/uxbox/data/workspace.cljs
index bf7a529b4..af6af7891 100644
--- a/src/uxbox/data/workspace.cljs
+++ b/src/uxbox/data/workspace.cljs
@@ -10,19 +10,15 @@
             [beicon.core :as rx]
             [uxbox.shapes :as sh]
             [uxbox.rstore :as rs]
-            [uxbox.router :as r]
-            [uxbox.state :as st]
             [uxbox.state.shapes :as stsh]
             [uxbox.schema :as sc]
-            [uxbox.xforms :as xf]
-            [uxbox.shapes :as sh]
             [uxbox.data.pages :as udp]
             [uxbox.data.shapes :as uds]
+            ;; [uxbox.data.worker :as wrk]
             [uxbox.util.datetime :as dt]
-            [uxbox.util.geom.point :as gpt]
-            [uxbox.util.data :refer (index-of)]))
+            [uxbox.util.geom.point :as gpt]))
 
-;; --- Events (concrete)
+;; --- Workspace Initialization
 
 (defn initialize
   "Initialize the workspace state."
@@ -65,146 +61,6 @@
         (assoc-in state [:workspace :drawing] shape)
         (update-in state [:workspace] dissoc :drawing)))))
 
-(defn select-shape
-  "Mark a shape selected for drawing in the canvas."
-  [id]
-  (reify
-    rs/UpdateEvent
-    (-apply-update [_ state]
-      (let [selected (get-in state [:workspace :selected])]
-        (if (contains? selected id)
-          (update-in state [:workspace :selected] disj id)
-          (update-in state [:workspace :selected] conj id))))))
-
-(defn select-shapes
-  "Select shapes that matches the select rect."
-  [selrect]
-  (reify
-    rs/UpdateEvent
-    (-apply-update [_ state]
-      (let [pageid (get-in state [:workspace :page])
-            xf (comp
-                (filter #(= (:page %) pageid))
-                (remove :hidden)
-                (remove :blocked)
-                (map sh/outer-rect')
-                (filter #(sh/contained-in? % selrect))
-                (map :id))]
-        (->> (into #{} xf (vals (:shapes-by-id state)))
-             (assoc-in state [:workspace :selected]))))))
-
-;; --- Events (implicit) (for selected)
-
-(defn deselect-all
-  "Mark a shape selected for drawing in the canvas."
-  []
-  (reify
-    rs/UpdateEvent
-    (-apply-update [_ state]
-      (assoc-in state [:workspace :selected] #{}))))
-
-(defn group-selected
-  []
-  (letfn [(update-shapes-on-page [state pid selected group]
-            (as-> (get-in state [:pages-by-id pid :shapes]) $
-              (remove selected $)
-              (into [group] $)
-              (assoc-in state [:pages-by-id pid :shapes] $)))
-
-          (update-shapes-on-index [state shapes group]
-            (reduce (fn [state {:keys [id] :as shape}]
-                      (as-> shape $
-                        (assoc $ :group group)
-                        (assoc-in state [:shapes-by-id id] $)))
-                    state
-                    shapes))
-          (valid-selection? [shapes]
-            (let [groups (into #{} (map :group shapes))]
-              (= 1 (count groups))))]
-    (reify
-      udp/IPageUpdate
-      rs/UpdateEvent
-      (-apply-update [_ state]
-        (let [shapes-by-id (get state :shapes-by-id)
-              sid (random-uuid)
-              pid (get-in state [:workspace :page])
-              selected (get-in state [:workspace :selected])
-              selected' (map #(get shapes-by-id %) selected)
-              group {:type :builtin/group
-                    :name (str "Group " (rand-int 1000))
-                    :items (into [] selected)
-                    :id sid
-                    :page pid}]
-          (if (valid-selection? selected')
-            (as-> state $
-              (update-shapes-on-index $ selected' sid)
-              (update-shapes-on-page $ pid selected sid)
-              (update $ :shapes-by-id assoc sid group)
-              (update $ :workspace assoc :selected #{}))
-            state))))))
-
-;; TODO: maybe split in two separate events
-(defn duplicate-selected
-  []
-  (reify
-    udp/IPageUpdate
-    rs/UpdateEvent
-    (-apply-update [_ state]
-      (let [selected (get-in state [:workspace :selected])]
-        (stsh/duplicate-shapes state selected)))))
-
-(defn delete-selected
-  "Deselect all and remove all selected shapes."
-  []
-  (reify
-    rs/WatchEvent
-    (-apply-watch [_ state s]
-      (let [selected (get-in state [:workspace :selected])]
-        (rx/from-coll
-         (into [(deselect-all)] (map #(uds/delete-shape %) selected)))))))
-
-(defn move-selected
-  "Move a minimal position unit the selected shapes."
-  ([dir] (move-selected dir 1))
-  ([dir n]
-   {:pre [(contains? #{:up :down :right :left} dir)]}
-   (reify
-     rs/WatchEvent
-     (-apply-watch [_ state s]
-       (let [selected (get-in state [:workspace :selected])
-             delta (case dir
-                    :up (gpt/point 0 (- n))
-                    :down (gpt/point 0 n)
-                    :right (gpt/point n 0)
-                    :left (gpt/point (- n) 0))]
-         (rx/from-coll
-          (map #(uds/move-shape % delta) selected)))))))
-
-(defn update-selected-shapes-fill
-  "Update the fill related attributed on
-  selected shapes."
-  [opts]
-  (sc/validate! uds/+shape-fill-attrs-schema+ opts)
-  (reify
-    rs/WatchEvent
-    (-apply-watch [_ state s]
-      (rx/from-coll
-       (->> (get-in state [:workspace :selected])
-            (map #(uds/update-fill-attrs % opts)))))))
-
-
-(defn update-selected-shapes-stroke
-  "Update the fill related attributed on
-  selected shapes."
-  [opts]
-  (sc/validate! uds/+shape-stroke-attrs-schema+ opts)
-  (reify
-    rs/WatchEvent
-    (-apply-watch [_ state s]
-      (rx/from-coll
-       (->> (get-in state [:workspace :selected])
-            (map #(uds/update-stroke-attrs % opts)))))))
-
 ;; --- Copy to Clipboard
 
 (defrecord CopyToClipboard []

From aa06d824ee8612a357806a859b24eac0efe29016 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sun, 10 Apr 2016 18:03:28 +0300
Subject: [PATCH 12/19] Adapt imports to previos data api change.

---
 src/uxbox/ui/shapes/icon.cljs              |  9 +++++----
 src/uxbox/ui/shapes/text.cljs              | 12 ++++++------
 src/uxbox/ui/workspace/base.cljs           | 16 ++++------------
 src/uxbox/ui/workspace/canvas.cljs         |  3 ++-
 src/uxbox/ui/workspace/colorpalette.cljs   |  5 +++--
 src/uxbox/ui/workspace/selrect.cljs        |  3 ++-
 src/uxbox/ui/workspace/shortcuts.cljs      | 13 +++++++------
 src/uxbox/ui/workspace/sidebar/layers.cljs | 20 ++++++++++----------
 8 files changed, 39 insertions(+), 42 deletions(-)

diff --git a/src/uxbox/ui/shapes/icon.cljs b/src/uxbox/ui/shapes/icon.cljs
index 44e16f3de..654a9a90b 100644
--- a/src/uxbox/ui/shapes/icon.cljs
+++ b/src/uxbox/ui/shapes/icon.cljs
@@ -7,6 +7,7 @@
             [uxbox.state :as st]
             [uxbox.shapes :as ush]
             [uxbox.data.workspace :as dw]
+            [uxbox.data.shapes :as uds]
             [uxbox.ui.core :as uuc]
             [uxbox.ui.mixins :as mx]
             [uxbox.ui.keyboard :as kbd]
@@ -28,16 +29,16 @@
         (and (not selected?) (empty? selected))
         (do
           (dom/stop-propagation event)
-          (rs/emit! (dw/select-shape id))
+          (rs/emit! (uds/select-shape id))
           (uuc/acquire-action! "ui.shape.move"))
 
         (and (not selected?) (not (empty? selected)))
         (do
           (dom/stop-propagation event)
           (if (kbd/shift? event)
-            (rs/emit! (dw/select-shape id))
-            (rs/emit! (dw/deselect-all)
-                      (dw/select-shape id))))
+            (rs/emit! (uds/select-shape id))
+            (rs/emit! (uds/deselect-all)
+                      (uds/select-shape id))))
 
         :else
         (do
diff --git a/src/uxbox/ui/shapes/text.cljs b/src/uxbox/ui/shapes/text.cljs
index 5e882bd1b..0f58f8091 100644
--- a/src/uxbox/ui/shapes/text.cljs
+++ b/src/uxbox/ui/shapes/text.cljs
@@ -7,7 +7,7 @@
             [uxbox.rstore :as rs]
             [uxbox.state :as st]
             [uxbox.shapes :as ush]
-            [uxbox.data.shapes :as ds]
+            [uxbox.data.shapes :as uds]
             [uxbox.data.workspace :as dw]
             [uxbox.ui.core :as uuc]
             [uxbox.ui.mixins :as mx]
@@ -34,15 +34,15 @@
         (do
           (dom/stop-propagation event)
           (uuc/acquire-action! "ui.shape.move")
-          (rs/emit! (dw/select-shape id)))
+          (rs/emit! (uds/select-shape id)))
 
         (and (not selected?) (not (empty? selected)))
         (do
           (dom/stop-propagation event)
           (if (kbd/shift? event)
-            (rs/emit! (dw/select-shape id))
-            (rs/emit! (dw/deselect-all)
-                      (dw/select-shape id))))
+            (rs/emit! (uds/select-shape id))
+            (rs/emit! (uds/deselect-all)
+                      (uds/select-shape id))))
 
         :else
         (do
@@ -80,7 +80,7 @@
           (on-input [ev]
             (let [content (dom/event->inner-text ev)
                   sid (:id (first (:rum/props own)))]
-              (rs/emit! (ds/update-text sid {:content content}))))]
+              (rs/emit! (uds/update-text sid {:content content}))))]
     (let [dom (mx/get-ref-dom own "main")
           dom2 (mx/get-ref-dom own "container")
           key1 (events/listen dom EventType.DBLCLICK on-double-click)
diff --git a/src/uxbox/ui/workspace/base.cljs b/src/uxbox/ui/workspace/base.cljs
index 0ed1381b0..ffca0c1e6 100644
--- a/src/uxbox/ui/workspace/base.cljs
+++ b/src/uxbox/ui/workspace/base.cljs
@@ -18,9 +18,7 @@
             [goog.events :as events])
   (:import goog.events.EventType))
 
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Lenses
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; --- Lenses
 
 (def ^:const workspace-l
   (as-> (l/in [:workspace]) $
@@ -60,9 +58,7 @@
   (-> (l/in [:workspace :zoom])
       (l/focus-atom st/state)))
 
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Scroll Stream
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; --- Scroll Stream
 
 (defonce scroll-b (rx/bus))
 
@@ -75,9 +71,7 @@
 (defonce scroll-a
   (rx/to-atom scroll-s))
 
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Mouse Position Stream
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; --- Mouse Position Stream
 
 (defonce mouse-b (rx/bus))
 (defonce mouse-s
@@ -120,9 +114,7 @@
        (rx/map coords-delta)
        (rx/share)))
 
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Constants
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; --- Constants
 
 (def ^:const viewport-width 4000)
 (def ^:const viewport-height 4000)
diff --git a/src/uxbox/ui/workspace/canvas.cljs b/src/uxbox/ui/workspace/canvas.cljs
index 1f3fb3363..e5e382901 100644
--- a/src/uxbox/ui/workspace/canvas.cljs
+++ b/src/uxbox/ui/workspace/canvas.cljs
@@ -15,6 +15,7 @@
             [uxbox.shapes :as sh]
             [uxbox.data.projects :as dp]
             [uxbox.data.workspace :as dw]
+            [uxbox.data.shapes :as uds]
             [uxbox.util.geom.point :as gpt]
             [uxbox.util.dom :as dom]
             [uxbox.util.data :refer (parse-int)]
@@ -91,7 +92,7 @@
     (letfn [(on-mouse-down [event]
               (dom/stop-propagation event)
               (when-not (empty? (:selected workspace))
-                (rs/emit! (dw/deselect-all)))
+                (rs/emit! (uds/deselect-all)))
               (if-let [shape (:drawing workspace)]
                 (uuc/acquire-action! "ui.shape.draw")
                 (uuc/acquire-action! "ui.selrect")))
diff --git a/src/uxbox/ui/workspace/colorpalette.cljs b/src/uxbox/ui/workspace/colorpalette.cljs
index ff7bab258..e9e1f2ed2 100644
--- a/src/uxbox/ui/workspace/colorpalette.cljs
+++ b/src/uxbox/ui/workspace/colorpalette.cljs
@@ -14,6 +14,7 @@
             [uxbox.state :as st]
             [uxbox.library :as library]
             [uxbox.data.workspace :as dw]
+            [uxbox.data.shapes :as uds]
             [uxbox.util.lens :as ul]
             [uxbox.util.data :refer (read-string)]
             [uxbox.util.color :refer (hex->rgb)]
@@ -48,8 +49,8 @@
   [color event]
   (dom/prevent-default event)
   (if (kbd/shift? event)
-    (rs/emit! (dw/update-selected-shapes-stroke {:color color}))
-    (rs/emit! (dw/update-selected-shapes-fill {:color color}))))
+    (rs/emit! (uds/update-selected-shapes-stroke {:color color}))
+    (rs/emit! (uds/update-selected-shapes-fill {:color color}))))
 
 (defn- colorpalette-render
   [own]
diff --git a/src/uxbox/ui/workspace/selrect.cljs b/src/uxbox/ui/workspace/selrect.cljs
index 15c7b2135..00b2574c3 100644
--- a/src/uxbox/ui/workspace/selrect.cljs
+++ b/src/uxbox/ui/workspace/selrect.cljs
@@ -13,6 +13,7 @@
             [uxbox.rstore :as rs]
             [uxbox.shapes :as sh]
             [uxbox.data.workspace :as dw]
+            [uxbox.data.shapes :as uds]
             [uxbox.ui.core :as uuc]
             [uxbox.ui.mixins :as mx]
             [uxbox.ui.workspace.base :as wb]))
@@ -94,7 +95,7 @@
           (on-complete []
             (rs/emit! (-> (selrect->rect @position)
                           (translate-to-canvas)
-                          (dw/select-shapes)))
+                          (uds/select-shapes)))
             (reset! position nil))
 
           (init []
diff --git a/src/uxbox/ui/workspace/shortcuts.cljs b/src/uxbox/ui/workspace/shortcuts.cljs
index 4e543be7f..9d14ac147 100644
--- a/src/uxbox/ui/workspace/shortcuts.cljs
+++ b/src/uxbox/ui/workspace/shortcuts.cljs
@@ -11,6 +11,7 @@
             [uxbox.rstore :as rs]
             [uxbox.ui.lightbox :as lightbox]
             [uxbox.data.workspace :as dw]
+            [uxbox.data.shapes :as uds]
             [uxbox.data.history :as udh])
   (:import goog.events.EventType
            goog.events.KeyCodes
@@ -28,15 +29,15 @@
    :ctrl+shift+l #(rs/emit! (dw/toggle-flag :layers))
    :ctrl+0 #(rs/emit! (dw/reset-zoom))
    :ctrl+r #(rs/emit! (dw/toggle-flag :ruler))
-   :ctrl+d #(rs/emit! (dw/duplicate-selected))
+   :ctrl+d #(rs/emit! (uds/duplicate-selected))
    :ctrl+c #(rs/emit! (dw/copy-to-clipboard))
    :ctrl+v #(rs/emit! (dw/paste-from-clipboard))
    :ctrl+z #(rs/emit! (udh/backwards-to-previous-version))
    :ctrl+shift+z #(rs/emit! (udh/forward-to-next-version))
    :ctrl+shift+v #(lightbox/open! :clipboard)
-   :esc #(rs/emit! (dw/deselect-all))
-   :backspace #(rs/emit! (dw/delete-selected))
-   :delete #(rs/emit! (dw/delete-selected))
+   :esc #(rs/emit! (uds/deselect-all))
+   :backspace #(rs/emit! (uds/delete-selected))
+   :delete #(rs/emit! (uds/delete-selected))
    :shift+up #(move-selected :up :fast)
    :shift+down #(move-selected :down :fast)
    :shift+right #(move-selected :right :fast)
@@ -78,8 +79,8 @@
 (defn- move-selected
   [dir speed]
   (case speed
-    :std (rs/emit! (dw/move-selected dir 1))
-    :fast (rs/emit! (dw/move-selected dir 20))))
+    :std (rs/emit! (uds/move-selected dir 1))
+    :fast (rs/emit! (uds/move-selected dir 20))))
 
 ;; --- Mixin
 
diff --git a/src/uxbox/ui/workspace/sidebar/layers.cljs b/src/uxbox/ui/workspace/sidebar/layers.cljs
index a18bd65cf..f33e41617 100644
--- a/src/uxbox/ui/workspace/sidebar/layers.cljs
+++ b/src/uxbox/ui/workspace/sidebar/layers.cljs
@@ -49,18 +49,18 @@
       nil
 
       (.-ctrlKey event)
-      (rs/emit! (udw/select-shape id))
+      (rs/emit! (uds/select-shape id))
 
       (> (count selected) 1)
-      (rs/emit! (udw/deselect-all)
-                (udw/select-shape id))
+      (rs/emit! (uds/deselect-all)
+                (uds/select-shape id))
 
       (contains? selected id)
-      (rs/emit! (udw/select-shape id))
+      (rs/emit! (uds/select-shape id))
 
       :else
-      (rs/emit! (udw/deselect-all)
-                (udw/select-shape id)))))
+      (rs/emit! (uds/deselect-all)
+                (uds/select-shape id)))))
 
 (defn- toggle-visibility
   [selected item event]
@@ -71,7 +71,7 @@
       (rs/emit! (uds/show-shape id))
       (rs/emit! (uds/hide-shape id)))
     (when (contains? selected id)
-      (rs/emit! (udw/select-shape id)))))
+      (rs/emit! (uds/select-shape id)))))
 
 (defn- toggle-blocking
   [item event]
@@ -289,9 +289,9 @@
         shapes-by-id (rum/react wb/shapes-by-id-l)
         page (rum/react (focus-page (:page workspace)))
         close #(rs/emit! (udw/toggle-flag :layers))
-        duplicate #(rs/emit! (udw/duplicate-selected))
-        group #(rs/emit! (udw/group-selected))
-        delete #(rs/emit! (udw/delete-selected))
+        duplicate #(rs/emit! (uds/duplicate-selected))
+        group #(rs/emit! (uds/group-selected))
+        delete #(rs/emit! (uds/delete-selected))
         dragel (volatile! nil)]
     (html
      [:div#layers.tool-window

From e9d3e7578e3c2ccd394d0dea29f33d44e169ee74 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sun, 10 Apr 2016 18:04:37 +0300
Subject: [PATCH 13/19] Minor improvements on grid render code.

---
 src/uxbox/ui/workspace/grid.cljs | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/src/uxbox/ui/workspace/grid.cljs b/src/uxbox/ui/workspace/grid.cljs
index 5e7b8a255..a76c11c1b 100644
--- a/src/uxbox/ui/workspace/grid.cljs
+++ b/src/uxbox/ui/workspace/grid.cljs
@@ -14,7 +14,6 @@
 
 ;; --- Grid (Component)
 
-(declare ticks-range)
 (declare vertical-line)
 (declare horizontal-line)
 
@@ -24,8 +23,14 @@
         color (:grid/color options "#cccccc")
         width wb/viewport-width
         height wb/viewport-height
-        x-ticks (ticks-range width (:grid/x-axis options 10))
-        y-ticks (ticks-range height (:grid/y-axis options 10))
+        x-ticks (range (- 0 wb/canvas-start-x)
+                       (- width wb/canvas-start-x)
+                       (:grid/x-axis options 10))
+
+        y-ticks (range (- 0 wb/canvas-start-x)
+                       (- height wb/canvas-start-x)
+                       (:grid/y-axis options 10))
+
         path (as-> [] $
                (reduce (partial vertical-line height) $ x-ticks)
                (reduce (partial horizontal-line width) $ y-ticks))]
@@ -50,9 +55,3 @@
   [height acc value]
   (let [pos (+ value wb/canvas-start-y)]
     (conj acc (str/format "M %s %s L %s %s" pos 0 pos height))))
-
-(defn- ticks-range
-  [size step]
-  (range (- 0 wb/canvas-start-y)
-         (- size wb/canvas-start-y)
-         step))

From 9bd358a1195d1a7491769ac23660c51dcd383f44 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sun, 10 Apr 2016 18:05:04 +0300
Subject: [PATCH 14/19] Properly persist as int the grid axis on workspace
 settings lightbox.

---
 src/uxbox/ui/workspace/settings.cljs | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/uxbox/ui/workspace/settings.cljs b/src/uxbox/ui/workspace/settings.cljs
index 87a725954..489d314cb 100644
--- a/src/uxbox/ui/workspace/settings.cljs
+++ b/src/uxbox/ui/workspace/settings.cljs
@@ -16,7 +16,8 @@
             [uxbox.ui.lightbox :as lightbox]
             [uxbox.ui.colorpicker :as uucp]
             [uxbox.ui.workspace.base :as wb]
-            [uxbox.util.dom :as dom]))
+            [uxbox.util.dom :as dom]
+            [uxbox.util.data :refer (parse-int)]))
 
 ;; --- Lentes
 
@@ -33,7 +34,8 @@
         opts (merge (:options page)
                     (deref local))]
     (letfn [(on-field-change [field event]
-              (let [value (dom/event->value event)]
+              (let [value (dom/event->value event)
+                    value (parse-int value)]
                 (swap! local assoc field value)))
             (on-color-change [color]
               (swap! local assoc :grid/color color))
@@ -42,6 +44,7 @@
                                  (dom/checked?))]
                 (swap! local assoc :grid/align checked?)))
             (on-submit [event]
+              (dom/prevent-default event)
               (let [page (assoc page :options opts)]
                 (rs/emit! (udp/update-page-metadata page))
                 (lightbox/close!)))]
@@ -52,14 +55,15 @@
          [:input#grid-x.input-text
           {:placeholder "X px"
            :type "number"
-           :value (:grid/x-axis opts "2")
+           ;; TODO: put here the default from constants
+           :value (:grid/x-axis opts "10")
            :on-change (partial on-field-change :grid/x-axis)
            :min 1 ;;TODO check this value
            :max 100}]
          [:input#grid-y.input-text
           {:placeholder "Y px"
            :type "number"
-           :value (:grid/y-axis opts "2")
+           :value (:grid/y-axis opts "10")
            :on-change (partial on-field-change :grid/y-axis)
            :min 1
            :max 100}]]

From e791f49921c2eeb5d481472f36c9423273a7bcd4 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sun, 10 Apr 2016 18:05:32 +0300
Subject: [PATCH 15/19] Add point read/write handler to transit.

---
 src/uxbox/util/transit.cljs | 44 +++++++++++++++++++++++--------------
 1 file changed, 27 insertions(+), 17 deletions(-)

diff --git a/src/uxbox/util/transit.cljs b/src/uxbox/util/transit.cljs
index e5b8e403e..4493f616a 100644
--- a/src/uxbox/util/transit.cljs
+++ b/src/uxbox/util/transit.cljs
@@ -7,34 +7,44 @@
 (ns uxbox.util.transit
   "A lightweight abstraction for transit serialization."
   (:require [cognitect.transit :as t]
+            [com.cognitect.transit :as tr]
             [uxbox.util.data :refer (parse-int)]
+            [uxbox.util.geom.point :as gpt]
             [uxbox.util.datetime :as dt]))
 
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Read/Write Transit handlers
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; --- Transit Handlers
 
-(def ^:private datetime-write-handler
-  (reify
-    Object
-    (tag [_ v] "m")
-    (rep [_ v] (dt/format v :offset))
-    (stringRep [this v] (str (dt/format v :offset)))))
+(def datetime-write-handler
+  (t/write-handler (constantly "m")
+                   #(str (dt/format % :offset))))
 
-(defn- datetime-read-handler
-  [v]
-  (dt/datetime (parse-int v)))
+(def datetime-read-handler
+  (t/read-handler
+   #(dt/datetime (parse-int %))))
+
+(def point-write-handler
+  (t/write-handler
+   (constantly "point")
+   (fn [v]
+     (let [ret #js []]
+       (.push ret (:x v))
+       (.push ret (:y v))
+       ret))))
+
+(def point-read-handler
+  (t/read-handler
+   #(gpt/point (js->clj %))))
 
 (def ^:privare +read-handlers+
   {"u" uuid
-   "m" datetime-read-handler})
+   "m" datetime-read-handler
+   "point" point-read-handler})
 
 (def ^:privare +write-handlers+
-  {dt/DateTime datetime-write-handler})
+  {dt/DateTime datetime-write-handler
+   gpt/Point point-write-handler})
 
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Public Api
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; --- Public Api
 
 (defn decode
   [data]

From d69be9a3785d5022163c745887333980dac78702 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sun, 10 Apr 2016 18:19:24 +0300
Subject: [PATCH 16/19] Split constants to specific namespace.

---
 src/uxbox/constants.cljs            | 22 ++++++++++++++++++++
 src/uxbox/ui/workspace.cljs         | 16 +++++++++++----
 src/uxbox/ui/workspace/base.cljs    | 11 ----------
 src/uxbox/ui/workspace/canvas.cljs  |  9 +++++----
 src/uxbox/ui/workspace/grid.cljs    | 17 ++++++++--------
 src/uxbox/ui/workspace/ruler.cljs   |  5 +++--
 src/uxbox/ui/workspace/rules.cljs   | 31 +++++++++++++++--------------
 src/uxbox/ui/workspace/scroll.cljs  |  1 +
 src/uxbox/ui/workspace/selrect.cljs |  5 +++--
 9 files changed, 71 insertions(+), 46 deletions(-)
 create mode 100644 src/uxbox/constants.cljs

diff --git a/src/uxbox/constants.cljs b/src/uxbox/constants.cljs
new file mode 100644
index 000000000..64151d602
--- /dev/null
+++ b/src/uxbox/constants.cljs
@@ -0,0 +1,22 @@
+;; 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/.
+;;
+;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
+;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
+
+(ns uxbox.constants)
+
+(def ^:const grid-x-axis 10)
+(def ^:const grid-y-axis 10)
+
+(def ^:const viewport-width 4000)
+(def ^:const viewport-height 4000)
+
+(def ^:const canvas-start-x 1200)
+(def ^:const canvas-start-y 1200)
+(def ^:const canvas-scroll-padding 50)
+(def ^:const canvas-start-scroll-x (- canvas-start-x canvas-scroll-padding))
+(def ^:const canvas-start-scroll-y (- canvas-start-y canvas-scroll-padding))
+
+
diff --git a/src/uxbox/ui/workspace.cljs b/src/uxbox/ui/workspace.cljs
index 95c1232d8..b600313d0 100644
--- a/src/uxbox/ui/workspace.cljs
+++ b/src/uxbox/ui/workspace.cljs
@@ -1,7 +1,15 @@
+;; 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/.
+;;
+;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
+;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
+
 (ns uxbox.ui.workspace
   (:require [sablono.core :as html :refer-macros [html]]
             [rum.core :as rum]
             [beicon.core :as rx]
+            [uxbox.constants :as c]
             [uxbox.rstore :as rs]
             [uxbox.data.workspace :as dw]
             [uxbox.data.pages :as udp]
@@ -42,8 +50,8 @@
         dom (mx/get-ref-dom own "workspace-canvas")]
 
     ;; Set initial scroll position
-    (set! (.-scrollLeft dom) (* wb/canvas-start-scroll-x @wb/zoom-l))
-    (set! (.-scrollTop dom) (* wb/canvas-start-scroll-y @wb/zoom-l))
+    (set! (.-scrollLeft dom) (* c/canvas-start-scroll-x @wb/zoom-l))
+    (set! (.-scrollTop dom) (* c/canvas-start-scroll-y @wb/zoom-l))
 
     (assoc own ::sub1 sub1 ::sub2 sub2)))
 
@@ -89,8 +97,8 @@
       (rs/emit! (dw/decrease-zoom)))
 
     (let [dom (mx/get-ref-dom own "workspace-canvas")]
-      (set! (.-scrollLeft dom) (* wb/canvas-start-scroll-x @wb/zoom-l))
-      (set! (.-scrollTop dom) (* wb/canvas-start-scroll-y @wb/zoom-l)))))
+      (set! (.-scrollLeft dom) (* c/canvas-start-scroll-x @wb/zoom-l))
+      (set! (.-scrollTop dom) (* c/canvas-start-scroll-y @wb/zoom-l)))))
 
 (defn- workspace-render
   [own projectid]
diff --git a/src/uxbox/ui/workspace/base.cljs b/src/uxbox/ui/workspace/base.cljs
index ffca0c1e6..b1c1c49c1 100644
--- a/src/uxbox/ui/workspace/base.cljs
+++ b/src/uxbox/ui/workspace/base.cljs
@@ -113,14 +113,3 @@
        (rx/buffer 2 1)
        (rx/map coords-delta)
        (rx/share)))
-
-;; --- Constants
-
-(def ^:const viewport-width 4000)
-(def ^:const viewport-height 4000)
-
-(def ^:const canvas-start-x 1200)
-(def ^:const canvas-start-y 1200)
-(def ^:const canvas-scroll-padding 50)
-(def ^:const canvas-start-scroll-x (- canvas-start-x canvas-scroll-padding))
-(def ^:const canvas-start-scroll-y (- canvas-start-y canvas-scroll-padding))
diff --git a/src/uxbox/ui/workspace/canvas.cljs b/src/uxbox/ui/workspace/canvas.cljs
index e5e382901..794e35453 100644
--- a/src/uxbox/ui/workspace/canvas.cljs
+++ b/src/uxbox/ui/workspace/canvas.cljs
@@ -11,6 +11,7 @@
             [beicon.core :as rx]
             [lentes.core :as l]
             [goog.events :as events]
+            [uxbox.constants :as c]
             [uxbox.rstore :as rs]
             [uxbox.shapes :as sh]
             [uxbox.data.projects :as dp]
@@ -58,8 +59,8 @@
   (let [workspace (rum/react uuwb/workspace-l)
         flags (:flags workspace)]
     (html
-     [:svg.page-canvas {:x uuwb/canvas-start-x
-                        :y uuwb/canvas-start-y
+     [:svg.page-canvas {:x c/canvas-start-x
+                        :y c/canvas-start-y
                         :ref (str "canvas" id)
                         :width width
                         :height height}
@@ -101,8 +102,8 @@
               (uuc/release-action! "ui.shape"
                                    "ui.selrect"))]
       (html
-       [:svg.viewport {:width (* uuwb/viewport-width zoom)
-                       :height (* uuwb/viewport-height zoom)
+       [:svg.viewport {:width (* c/viewport-width zoom)
+                       :height (* c/viewport-height zoom)
                        :ref "viewport"
                        :class (when drawing? "drawing")
                        :on-mouse-down on-mouse-down
diff --git a/src/uxbox/ui/workspace/grid.cljs b/src/uxbox/ui/workspace/grid.cljs
index a76c11c1b..e58557058 100644
--- a/src/uxbox/ui/workspace/grid.cljs
+++ b/src/uxbox/ui/workspace/grid.cljs
@@ -9,6 +9,7 @@
   (:require [sablono.core :as html :refer-macros [html]]
             [rum.core :as rum]
             [cuerdas.core :as str]
+            [uxbox.constants :as c]
             [uxbox.ui.mixins :as mx]
             [uxbox.ui.workspace.base :as wb]))
 
@@ -21,14 +22,14 @@
   [own]
   (let [options (:options @wb/page-l)
         color (:grid/color options "#cccccc")
-        width wb/viewport-width
-        height wb/viewport-height
-        x-ticks (range (- 0 wb/canvas-start-x)
-                       (- width wb/canvas-start-x)
+        width c/viewport-width
+        height c/viewport-height
+        x-ticks (range (- 0 c/canvas-start-x)
+                       (- width c/canvas-start-x)
                        (:grid/x-axis options 10))
 
-        y-ticks (range (- 0 wb/canvas-start-x)
-                       (- height wb/canvas-start-x)
+        y-ticks (range (- 0 c/canvas-start-x)
+                       (- height c/canvas-start-x)
                        (:grid/y-axis options 10))
 
         path (as-> [] $
@@ -48,10 +49,10 @@
 
 (defn- horizontal-line
   [width acc value]
-  (let [pos (+ value wb/canvas-start-y)]
+  (let [pos (+ value c/canvas-start-y)]
     (conj acc (str/format "M %s %s L %s %s" 0 pos width pos))))
 
 (defn- vertical-line
   [height acc value]
-  (let [pos (+ value wb/canvas-start-y)]
+  (let [pos (+ value c/canvas-start-y)]
     (conj acc (str/format "M %s %s L %s %s" pos 0 pos height))))
diff --git a/src/uxbox/ui/workspace/ruler.cljs b/src/uxbox/ui/workspace/ruler.cljs
index 7868e2206..0afe957e9 100644
--- a/src/uxbox/ui/workspace/ruler.cljs
+++ b/src/uxbox/ui/workspace/ruler.cljs
@@ -9,6 +9,7 @@
   (:require [sablono.core :as html :refer-macros [html]]
             [rum.core :as rum]
             [beicon.core :as rx]
+            [uxbox.constants :as c]
             [uxbox.rstore :as rs]
             [uxbox.util.math :as mth]
             [uxbox.ui.workspace.base :as wb]
@@ -104,8 +105,8 @@
       [:rect {:style {:fill "transparent"
                       :stroke "transparent"
                       :cursor "cell"}
-              :width wb/viewport-width
-              :height wb/viewport-height}]
+              :width c/viewport-width
+              :height c/viewport-height}]
       (if (and p1 p2)
         (overlay-line-render own p1 p2))])))
 
diff --git a/src/uxbox/ui/workspace/rules.cljs b/src/uxbox/ui/workspace/rules.cljs
index f3c5e83f7..71062b1ae 100644
--- a/src/uxbox/ui/workspace/rules.cljs
+++ b/src/uxbox/ui/workspace/rules.cljs
@@ -10,6 +10,7 @@
             [rum.core :as rum]
             [cuerdas.core :as str]
             [beicon.core :as rx]
+            [uxbox.constants :as c]
             [uxbox.state :as s]
             [uxbox.util.dom :as dom]
             [uxbox.ui.workspace.base :as wb]
@@ -24,8 +25,8 @@
 (defn mid-ticks-mod [zoom] (/ 50 zoom))
 
 (def ^:const +ticks+
-  (concat (range (- (/ wb/viewport-width 1)) 0 step-size)
-          (range 0 (/ wb/viewport-width 1) step-size)))
+  (concat (range (- (/ c/viewport-width 1)) 0 step-size)
+          (range 0 (/ c/viewport-width 1) step-size)))
 
 (def ^:const rule-padding 20)
 
@@ -35,8 +36,8 @@
         mid-ticks-mod (mid-ticks-mod zoom)
         pos (+ (* value zoom)
                rule-padding
-               (* wb/canvas-start-x zoom)
-               wb/canvas-scroll-padding)]
+               (* c/canvas-start-x zoom)
+               c/canvas-scroll-padding)]
     (cond
       (< (mod value big-ticks-mod) step-size)
       (conj acc (str/format "M %s %s L %s %s" pos 5 pos step-padding))
@@ -52,8 +53,8 @@
   (let [big-ticks-mod (big-ticks-mod zoom)
         mid-ticks-mod (mid-ticks-mod zoom)
         pos (+ (* value zoom)
-               (* wb/canvas-start-x zoom)
-               wb/canvas-scroll-padding)]
+               (* c/canvas-start-x zoom)
+               c/canvas-scroll-padding)]
     (cond
       (< (mod value big-ticks-mod) step-size)
       (conj acc (str/format "M %s %s L %s %s" 5 pos step-padding pos))
@@ -71,8 +72,8 @@
   (let [big-ticks-mod (big-ticks-mod zoom)
         pos (+ (* value zoom)
                rule-padding
-               (* wb/canvas-start-x zoom)
-               wb/canvas-scroll-padding)]
+               (* c/canvas-start-x zoom)
+               c/canvas-scroll-padding)]
     (when (< (mod value big-ticks-mod) step-size)
       (html
        [:text {:x (+ pos 2)
@@ -88,9 +89,9 @@
   [zoom value]
   (let [big-ticks-mod (big-ticks-mod zoom)
         pos (+ (* value zoom)
-               (* wb/canvas-start-x zoom)
-               ;; wb/canvas-start-x
-               wb/canvas-scroll-padding)]
+               (* c/canvas-start-x zoom)
+               ;; c/canvas-start-x
+               c/canvas-scroll-padding)]
     (when (< (mod value big-ticks-mod) step-size)
       (html
        [:text {:y (- pos 3)
@@ -145,10 +146,10 @@
   [own zoom]
   (let [scroll (rum/react wb/scroll-a)
         scroll-x (:x scroll)
-        translate-x (- (- wb/canvas-scroll-padding) (:x scroll))]
+        translate-x (- (- c/canvas-scroll-padding) (:x scroll))]
     (html
      [:svg.horizontal-rule
-      {:width wb/viewport-width
+      {:width c/viewport-width
        :height 20}
       [:g {:transform (str "translate(" translate-x ", 0)")}
        (horizontal-rule-ticks zoom)]])))
@@ -165,11 +166,11 @@
   [own zoom]
   (let [scroll (rum/react wb/scroll-a)
         scroll-y (:y scroll)
-        translate-y (- (- wb/canvas-scroll-padding) (:y scroll))]
+        translate-y (- (- c/canvas-scroll-padding) (:y scroll))]
     (html
      [:svg.vertical-rule
       {:width 20
-       :height wb/viewport-height}
+       :height c/viewport-height}
 
       [:g {:transform (str  "translate(0, " translate-y ")")}
        (vertical-rule-ticks zoom)]
diff --git a/src/uxbox/ui/workspace/scroll.cljs b/src/uxbox/ui/workspace/scroll.cljs
index 121e79afa..fcc3a1698 100644
--- a/src/uxbox/ui/workspace/scroll.cljs
+++ b/src/uxbox/ui/workspace/scroll.cljs
@@ -9,6 +9,7 @@
   (:require-macros [uxbox.util.syntax :refer [define-once]])
   (:require [beicon.core :as rx]
             [lentes.core :as l]
+            [uxbox.constants :as c]
             [uxbox.rstore :as rs]
             [uxbox.state :as ust]
             [uxbox.data.shapes :as uds]
diff --git a/src/uxbox/ui/workspace/selrect.cljs b/src/uxbox/ui/workspace/selrect.cljs
index 00b2574c3..a1a168440 100644
--- a/src/uxbox/ui/workspace/selrect.cljs
+++ b/src/uxbox/ui/workspace/selrect.cljs
@@ -10,6 +10,7 @@
   (:require [sablono.core :as html :refer-macros [html]]
             [rum.core :as rum]
             [beicon.core :as rx]
+            [uxbox.constants :as c]
             [uxbox.rstore :as rs]
             [uxbox.shapes :as sh]
             [uxbox.data.workspace :as dw]
@@ -79,8 +80,8 @@
   "Translate the given rect to the canvas coordinates system."
   [rect]
   (let [zoom @wb/zoom-l
-        startx (* wb/canvas-start-x zoom)
-        starty (* wb/canvas-start-y zoom)]
+        startx (* c/canvas-start-x zoom)
+        starty (* c/canvas-start-y zoom)]
     (assoc rect
            :x (- (:x rect) startx)
            :y (- (:y rect) starty)

From b92034218817925c1d90e12aa9077666a9c7dde0 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sun, 10 Apr 2016 18:22:40 +0300
Subject: [PATCH 17/19] Use constants on workspace settings lightbox.

---
 src/uxbox/ui/workspace/settings.cljs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/uxbox/ui/workspace/settings.cljs b/src/uxbox/ui/workspace/settings.cljs
index 489d314cb..fecf67e00 100644
--- a/src/uxbox/ui/workspace/settings.cljs
+++ b/src/uxbox/ui/workspace/settings.cljs
@@ -9,6 +9,7 @@
   (:require [sablono.core :as html :refer-macros [html]]
             [lentes.core :as l]
             [rum.core :as rum]
+            [uxbox.constants :as c]
             [uxbox.rstore :as rs]
             [uxbox.data.pages :as udp]
             [uxbox.ui.icons :as i]
@@ -55,15 +56,14 @@
          [:input#grid-x.input-text
           {:placeholder "X px"
            :type "number"
-           ;; TODO: put here the default from constants
-           :value (:grid/x-axis opts "10")
+           :value (:grid/x-axis opts c/grid-x-axis)
            :on-change (partial on-field-change :grid/x-axis)
-           :min 1 ;;TODO check this value
+           :min 1
            :max 100}]
          [:input#grid-y.input-text
           {:placeholder "Y px"
            :type "number"
-           :value (:grid/y-axis opts "10")
+           :value (:grid/y-axis opts c/grid-y-axis)
            :on-change (partial on-field-change :grid/y-axis)
            :min 1
            :max 100}]]

From 051163c3ae1e50d6534487d017d452164b26264c Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sun, 10 Apr 2016 18:44:12 +0300
Subject: [PATCH 18/19] Add webworkers architecture (rcp).

---
 scripts/figwheel.clj       | 20 +++++++++++++++++---
 src/uxbox/worker.cljs      | 21 +++++++++++++++++++++
 src/uxbox/worker/core.cljs | 31 +++++++++++++++++++++++++++++++
 3 files changed, 69 insertions(+), 3 deletions(-)
 create mode 100644 src/uxbox/worker.cljs
 create mode 100644 src/uxbox/worker/core.cljs

diff --git a/scripts/figwheel.clj b/scripts/figwheel.clj
index 599c6fa73..2ed5f7fd7 100644
--- a/scripts/figwheel.clj
+++ b/scripts/figwheel.clj
@@ -7,7 +7,7 @@
 
 (ra/start-figwheel!
   {:figwheel-options {:css-dirs ["resources/public/css"]}
-   :build-ids ["dev"]
+   :build-ids ["dev" "worker"]
    :all-builds
    [{:id "dev"
      :figwheel {:on-jsload "uxbox.ui/init"}
@@ -20,10 +20,24 @@
                                   "https://test.uxbox.io/api"}
                 :warnings {:ns-var-clash false}
                 :pretty-print true
-                :language-in  :ecmascript5
+                :language-in  :ecmascript6
                 :language-out :ecmascript5
                 :output-to "resources/public/js/main.js"
                 :output-dir "resources/public/js"
+                :verbose true}}
+
+    {:id "worker"
+     :source-paths ["src" "vendor"]
+     :compiler {:main 'uxbox.worker
+                :asset-path "js"
+                :parallel-build false
+                :optimizations :simple
+                :warnings {:ns-var-clash false}
+                :pretty-print true
+                :static-fns true
+                :language-in  :ecmascript6
+                :language-out :ecmascript5
+                :output-to "resources/public/js/worker.js"
                 :verbose true}}]})
 
-(ra/cljs-repl)
+(ra/cljs-repl "dev")
diff --git a/src/uxbox/worker.cljs b/src/uxbox/worker.cljs
new file mode 100644
index 000000000..4db0d390f
--- /dev/null
+++ b/src/uxbox/worker.cljs
@@ -0,0 +1,21 @@
+;; 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/.
+;;
+;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
+
+(ns uxbox.worker
+  (:require [beicon.core :as rx]
+            [uxbox.util.transit :as t]
+            [uxbox.worker.core :as wrk]
+            [uxbox.worker.align]))
+
+(enable-console-print!)
+
+(defn- on-message
+  [event]
+  (let [message (t/decode (.-data event))]
+    (wrk/handler message)))
+
+(defonce _
+  (.addEventListener js/self "message" on-message))
diff --git a/src/uxbox/worker/core.cljs b/src/uxbox/worker/core.cljs
new file mode 100644
index 000000000..43988fb93
--- /dev/null
+++ b/src/uxbox/worker/core.cljs
@@ -0,0 +1,31 @@
+;; 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/.
+;;
+;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
+
+(ns uxbox.worker.core
+  (:require [uxbox.util.transit :as t]))
+
+(enable-console-print!)
+
+;; --- Handler
+
+(defmulti handler :cmd)
+
+(defmethod handler :default
+  [message]
+  (println "Unexpected message:" message))
+
+;; --- Helpers
+
+(defn worker?
+  "Check if the code is executed in webworker context."
+  []
+  (undefined? (.-document js/self)))
+
+(defn reply!
+  [sender message]
+  (let [message (assoc message :reply-to sender)]
+    (println "replying " message)
+    (.postMessage js/self (t/encode message))))

From 935bafd512024b6fdca3a0f77cb6d23a6f7480a0 Mon Sep 17 00:00:00 2001
From: Andrey Antukh <niwi@niwi.nz>
Date: Sun, 10 Apr 2016 18:45:20 +0300
Subject: [PATCH 19/19] Add initial approach for grid alignment.

A little bit buggy because the main algorithm
does not works well.
---
 src/uxbox/data/worker.cljs           | 32 ++++++++++++++
 src/uxbox/data/workspace.cljs        | 44 ++++++++++++--------
 src/uxbox/ui/workspace/align.cljs    | 37 +++++++++++++++++
 src/uxbox/ui/workspace/movement.cljs | 55 +++++++++++++++++++++++-
 src/uxbox/util/workers.cljs          | 62 ++++++++++++++++++++++++++++
 src/uxbox/worker/align.cljs          | 34 +++++++++++++++
 6 files changed, 245 insertions(+), 19 deletions(-)
 create mode 100644 src/uxbox/data/worker.cljs
 create mode 100644 src/uxbox/ui/workspace/align.cljs
 create mode 100644 src/uxbox/util/workers.cljs
 create mode 100644 src/uxbox/worker/align.cljs

diff --git a/src/uxbox/data/worker.cljs b/src/uxbox/data/worker.cljs
new file mode 100644
index 000000000..6a549362c
--- /dev/null
+++ b/src/uxbox/data/worker.cljs
@@ -0,0 +1,32 @@
+;; 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/.
+;;
+;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
+
+(ns uxbox.data.worker
+  "Worker related api and initialization events."
+  (:require [beicon.core :as rx]
+            [uxbox.rstore :as rs]
+            [uxbox.constants :as c]
+            [uxbox.util.workers :as uw]))
+
+(defonce worker (uw/init "/js/worker.js"))
+
+;; --- Worker Initialization
+
+(defrecord InitializeWorker [id]
+  rs/EffectEvent
+  (-apply-effect [_ state]
+    (let [page (get-in state [:pages-by-id id])
+          opts (:options page)
+          message {:cmd :grid/init
+                   :width c/viewport-width
+                   :height c/viewport-height
+                   :x-axis (:grid/x-axis opts c/grid-x-axis)
+                   :y-axis (:grid/y-axis opts c/grid-y-axis)}]
+      (uw/send! worker message))))
+
+(defn initialize
+  [id]
+  (InitializeWorker. id))
diff --git a/src/uxbox/data/workspace.cljs b/src/uxbox/data/workspace.cljs
index af6af7891..445013b05 100644
--- a/src/uxbox/data/workspace.cljs
+++ b/src/uxbox/data/workspace.cljs
@@ -14,31 +14,41 @@
             [uxbox.schema :as sc]
             [uxbox.data.pages :as udp]
             [uxbox.data.shapes :as uds]
-            ;; [uxbox.data.worker :as wrk]
+            [uxbox.data.worker :as wrk]
             [uxbox.util.datetime :as dt]
             [uxbox.util.geom.point :as gpt]))
 
 ;; --- Workspace Initialization
 
+(defrecord InitializeWorkspace [project page]
+  rs/UpdateEvent
+  (-apply-update [_ state]
+    (if (:workspace state)
+      (update state :workspace merge
+              {:project project
+               :page page
+               :selected #{}
+               :drawing nil})
+      (assoc state :workspace
+             {:project project
+              :zoom 1
+              :page page
+              :flags #{:layers :element-options}
+              :selected #{}
+              :drawing nil})))
+
+    rs/WatchEvent
+    (-apply-watch [_ state s]
+      (if (get-in state [:pages-by-id page])
+        (rx/of (wrk/initialize page))
+        (->> (rx/filter udp/pages-fetched? s)
+             (rx/take 1)
+             (rx/map #(wrk/initialize page))))))
+
 (defn initialize
   "Initialize the workspace state."
   [project page]
-  (reify
-    rs/UpdateEvent
-    (-apply-update [_ state]
-      (if (:workspace state)
-        (update state :workspace merge
-                {:project project
-                 :page page
-                 :selected #{}
-                 :drawing nil})
-        (assoc state :workspace
-               {:project project
-                :zoom 1
-                :page page
-                :flags #{:layers :element-options}
-                :selected #{}
-                :drawing nil})))))
+  (InitializeWorkspace. project page))
 
 (defn toggle-flag
   "Toggle the enabled flag of the specified tool."
diff --git a/src/uxbox/ui/workspace/align.cljs b/src/uxbox/ui/workspace/align.cljs
new file mode 100644
index 000000000..d57eca2ce
--- /dev/null
+++ b/src/uxbox/ui/workspace/align.cljs
@@ -0,0 +1,37 @@
+;; 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/.
+;;
+;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
+
+(ns uxbox.ui.workspace.align
+  "Shape alignmen impl."
+  (:require [beicon.core :as rx]
+            [lentes.core :as l]
+            [uxbox.state :as st]
+            [uxbox.shapes :as sh]
+            [uxbox.data.worker :refer (worker)]
+            [uxbox.ui.workspace.base :as wb]
+            [uxbox.util.geom.point :as gpt]
+            [uxbox.util.workers :as uw]))
+
+(defn- move
+  [shape p1]
+  (let [dx (- (:x2 shape) (:x1 shape))
+        dy (- (:y2 shape) (:y1 shape))
+        p2 (gpt/add p1 [dx dy])]
+    (assoc shape
+           :x1 (:x p1)
+           :y1 (:y p1)
+           :x2 (:x p2)
+           :y2 (:y p2))))
+
+(defn translate
+  [{:keys [x1 y1] :as shape}]
+  (let [message {:cmd :grid/align
+                 :point (gpt/point x1 y1)}]
+    (->> (uw/ask! worker message)
+         (rx/map (fn [{:keys [point]}]
+                   (if point
+                     (move shape point)
+                     shape))))))
diff --git a/src/uxbox/ui/workspace/movement.cljs b/src/uxbox/ui/workspace/movement.cljs
index b80d30485..364994cad 100644
--- a/src/uxbox/ui/workspace/movement.cljs
+++ b/src/uxbox/ui/workspace/movement.cljs
@@ -9,6 +9,7 @@
   "Shape movement in workspace logic."
   (:require [beicon.core :as rx]
             [lentes.core :as l]
+            [uxbox.constants :as c]
             [uxbox.rstore :as rs]
             [uxbox.state :as st]
             [uxbox.shapes :as sh]
@@ -23,16 +24,24 @@
 
 ;; --- Lenses
 
+(declare translate-to-viewport)
+
 (defn- resolve-selected
   [state]
   (let [selected (get-in state [:workspace :selected])
-        xf (map #(get-in state [:shapes-by-id %]))]
+        xf (comp
+            (map #(get-in state [:shapes-by-id %]))
+            (map translate-to-viewport))]
     (into #{} xf selected)))
 
 (def ^:const ^:private selected-shapes-l
   (-> (l/getter resolve-selected)
       (l/focus-atom st/state)))
 
+(def ^:const ^:privae page-options-l
+  (-> (l/key :options)
+      (l/focus-atom wb/page-l)))
+
 ;; --- Public Api
 
 (defn watch-move-actions
@@ -43,9 +52,40 @@
 
 ;; --- Implementation
 
+(def coords
+  (gpt/point c/canvas-start-x
+             c/canvas-start-y))
+
+(defn- translate-to-viewport
+  [shape]
+  (let [dx (- (:x2 shape) (:x1 shape))
+        dy (- (:y2 shape) (:y1 shape))
+        p1 (gpt/point (:x1 shape) (:y1 shape))
+        p2 (gpt/add p1 coords)
+        p3 (gpt/add p2 [dx dy])]
+    (assoc shape
+           :x1 (:x p2)
+           :y1 (:y p2)
+           :x2 (:x p3)
+           :y2 (:y p3))))
+
+(defn- translate-to-canvas
+  [shape]
+  (let [dx (- (:x2 shape) (:x1 shape))
+        dy (- (:y2 shape) (:y1 shape))
+        p1 (gpt/point (:x1 shape) (:y1 shape))
+        p2 (gpt/subtract p1 coords)
+        p3 (gpt/add p2 [dx dy])]
+    (assoc shape
+           :x1 (:x p2)
+           :y1 (:y p2)
+           :x2 (:x p3)
+           :y2 (:y p3))))
+
 (defn- initialize
   []
   (let [shapes @selected-shapes-l
+        options @page-options-l
         stoper (->> uuc/actions-s
                     (rx/map :type)
                     (rx/filter empty?)
@@ -53,7 +93,17 @@
     (as-> wb/mouse-delta-s $
       (rx/take-until stoper $)
       (rx/scan (fn [acc delta]
-                 (mapv #(sh/move % delta) acc)) shapes $)
+                 (let [xf (map #(sh/move % delta))]
+                   (into [] xf acc))) shapes $)
+      (rx/mapcat (fn [items]
+                   (if (:grid/align options)
+                     (->> (apply rx/of items)
+                          (rx/mapcat align/translate)
+                          (rx/reduce conj []))
+                     (rx/of items))) $)
+      (rx/map (fn [items]
+                (mapv translate-to-canvas items)) $)
+
       (rx/subscribe $ handle-movement))))
 
 (defn- handle-movement
@@ -61,3 +111,4 @@
   (doseq [shape delta]
     (rs/emit! (uds/update-shape shape))))
 
+
diff --git a/src/uxbox/util/workers.cljs b/src/uxbox/util/workers.cljs
new file mode 100644
index 000000000..e96b9a1dd
--- /dev/null
+++ b/src/uxbox/util/workers.cljs
@@ -0,0 +1,62 @@
+;; 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/.
+;;
+;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
+
+(ns uxbox.util.workers
+  "A lightweight layer on top of webworkers api."
+  (:require [beicon.core :as rx]
+            [uxbox.util.transit :as t]))
+
+;; --- Implementation
+
+(defprotocol IWorker
+  (-ask [_ msg] "Send and receive message as rx stream.")
+  (-send [_ msg] "Send message and forget."))
+
+(deftype WebWorker [stream wrk]
+  IWorker
+  (-ask [this message]
+    (let [sender (random-uuid)
+          data (assoc message :sender sender)
+          data (t/encode data)]
+      (.postMessage wrk data)
+      (->> stream
+           (rx/filter #(= (:reply-to %) sender))
+           (rx/take 1))))
+
+  (-send [this message]
+    (let [sender (random-uuid)
+          data (assoc message :sender sender)
+          data (t/encode data)]
+      (.postMessage wrk data)
+      (->> stream
+           (rx/filter #(= (:reply-to %) sender))))))
+
+;; --- Public Api
+
+(defn init
+  "Return a initialized webworker instance."
+  [path]
+  (let [wrk (js/Worker. path)
+        bus (rx/bus)]
+    (.addEventListener wrk "message"
+                       (fn [event]
+                         (let [data (.-data event)
+                               data (t/decode data)]
+                           (rx/push! bus data))))
+    (.addEventListener wrk "error"
+                       (fn [event]
+                         (rx/error! bus event)))
+
+    (WebWorker. (rx/map identity bus) wrk)))
+
+(defn ask!
+  [wrk message]
+  (-ask wrk message))
+
+(defn send!
+  [wrk message]
+  (-send wrk message))
+
diff --git a/src/uxbox/worker/align.cljs b/src/uxbox/worker/align.cljs
new file mode 100644
index 000000000..704fb908c
--- /dev/null
+++ b/src/uxbox/worker/align.cljs
@@ -0,0 +1,34 @@
+;; 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/.
+;;
+;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
+
+(ns uxbox.worker.align
+  "Workspace aligment indexes worker."
+  (:require [beicon.core :as rx]
+            [kdtree :as kd]
+            [uxbox.worker.core :as wrk]
+            [uxbox.util.geom.point :as gpt]))
+
+(defonce state (volatile! nil))
+
+(defmethod wrk/handler :grid/init
+  [{:keys [width height x-axis y-axis] :as opts}]
+  (println ":grid/init" opts)
+  (let [points (into-array
+                (for [x (range 0 width (or x-axis 10))
+                      y (range 0 height (or y-axis 10))]
+                   #js [x y]))
+        tree (kd/create2d points)]
+     (vreset! state tree)))
+
+(defmethod wrk/handler :grid/align
+  [{:keys [sender point] :as message}]
+  (println "request" point)
+  (let [point #js [(:x point) (:y point)]
+        results (js->clj (.nearest @state point 1))
+        [[x y] d] (first results)
+        result (gpt/point x y)]
+    (println "result:" result)
+    (wrk/reply! sender {:point (gpt/point x y)})))