From 80a86b14b8903a85a8c0dfa759e62c894aa47826 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 8 May 2020 15:53:30 +0200 Subject: [PATCH] :recycle: Moved snap calculations to web-worker --- frontend/deps.edn | 2 +- frontend/src/uxbox/main/data/workspace.cljs | 3 +- .../src/uxbox/main/data/workspace/common.cljs | 24 +-- .../uxbox/main/data/workspace/transforms.cljs | 13 +- frontend/src/uxbox/main/refs.cljs | 3 + .../src/uxbox/main/ui/workspace/drawarea.cljs | 43 ++-- .../uxbox/main/ui/workspace/shapes/bbox.cljs | 26 +-- .../main/ui/workspace/snap_feedback.cljs | 76 +++++--- frontend/src/uxbox/main/worker.cljs | 2 +- frontend/src/uxbox/util/geom/snap.cljs | 183 +++++++----------- frontend/src/uxbox/util/range_tree.js | 18 +- frontend/src/uxbox/worker.cljs | 1 + frontend/src/uxbox/worker/impl.cljs | 14 ++ frontend/src/uxbox/worker/snaps.cljs | 97 ++++++++++ 14 files changed, 291 insertions(+), 214 deletions(-) create mode 100644 frontend/src/uxbox/worker/snaps.cljs diff --git a/frontend/deps.edn b/frontend/deps.edn index 21c5800d1..db4f79961 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -10,7 +10,7 @@ danlentz/clj-uuid {:mvn/version "0.1.9"} - funcool/beicon {:mvn/version "2020.03.29-1"} + funcool/beicon {:mvn/version "2020.05.08-2"} funcool/cuerdas {:mvn/version "2020.03.26-3"} funcool/lentes {:mvn/version "1.4.0-SNAPSHOT"} diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 9efdfa641..7d2ad4f54 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -155,8 +155,7 @@ (-> state (assoc :current-page-id page-id ; mainly used by events :workspace-local local - :workspace-page (dissoc page :data) - :workspace-snap-data (snap/initialize-snap-data (get-in page [:data :objects]))) + :workspace-page (dissoc page :data)) (assoc-in [:workspace-data page-id] (:data page))))) ptk/WatchEvent diff --git a/frontend/src/uxbox/main/data/workspace/common.cljs b/frontend/src/uxbox/main/data/workspace/common.cljs index 85268e1c0..c130e43d1 100644 --- a/frontend/src/uxbox/main/data/workspace/common.cljs +++ b/frontend/src/uxbox/main/data/workspace/common.cljs @@ -29,8 +29,7 @@ (get-ids [this])) (declare setup-selection-index) -(declare update-selection-index) -(declare update-snap-data) +(declare update-page-indices) (declare reset-undo) (declare append-undo) @@ -63,8 +62,7 @@ (let [page (:workspace-page state) uidx (get-in state [:workspace-local :undo-index] ::not-found)] (rx/concat - (rx/of (update-selection-index (:id page)) - (update-snap-data (:id page))) + (rx/of (update-page-indices (:id page))) (when (and save-undo? (not= uidx ::not-found)) (rx/of (reset-undo uidx))) @@ -146,34 +144,24 @@ (ptk/reify ::setup-selection-index ptk/WatchEvent (watch [_ state stream] - (let [msg {:cmd :selection/create-index + (let [msg {:cmd :create-page-indices :file-id (:id file) :pages pages}] (->> (uw/ask! msg) (rx/map (constantly ::index-initialized))))))) -(defn update-selection-index +(defn update-page-indices [page-id] - (ptk/reify ::update-selection-index + (ptk/reify ::update-page-indices ptk/EffectEvent (effect [_ state stream] (let [objects (get-in state [:workspace-pages page-id :data :objects]) lookup #(get objects %)] - (uw/ask! {:cmd :selection/update-index + (uw/ask! {:cmd :update-page-indices :page-id page-id :objects objects}))))) -(defn update-snap-data - [page-id] - (ptk/reify ::update-snap-data - ptk/UpdateEvent - (update [_ state] - (let [page (get-in state [:workspace-pages page-id]) - objects (get-in page [:data :objects])] - (-> state - (assoc :workspace-snap-data (snap/initialize-snap-data objects))))))) - ;; --- Common Helpers & Events (defn- calculate-frame-overlap diff --git a/frontend/src/uxbox/main/data/workspace/transforms.cljs b/frontend/src/uxbox/main/data/workspace/transforms.cljs index e7cd4aab6..80fd3799a 100644 --- a/frontend/src/uxbox/main/data/workspace/transforms.cljs +++ b/frontend/src/uxbox/main/data/workspace/transforms.cljs @@ -79,15 +79,13 @@ ;; -- RESIZE (defn start-resize [handler ids shape] - (letfn [(resize [shape initial resizing-shapes snap-data [point lock?]] + (letfn [(resize [shape initial resizing-shapes [point lock? point-snap]] (let [{:keys [width height rotation]} shape shapev (-> (gpt/point width height)) ;; Vector modifiers depending on the handler handler-modif (let [[x y] (handler-modifiers handler)] (gpt/point x y)) - point-snap (snap/closest-snap-point snap-data resizing-shapes point) - ;; Difference between the origin point in the coordinate system of the rotation deltav (-> (gpt/to-vec initial (if (= rotation 0) point-snap point)) (gpt/transform (gmt/rotate-matrix (- rotation))) @@ -137,7 +135,6 @@ (let [shape (gsh/shape->rect-shape shape) initial (handler->initial-point shape handler) stoper (rx/filter ms/mouse-up? stream) - snap-data (get state :workspace-snap-data) page-id (get state :current-page-id) resizing-shapes (map #(get-in state [:workspace-data page-id :objects %]) ids)] (rx/concat @@ -145,7 +142,10 @@ ;; (rx/mapcat apply-grid-alignment) (rx/with-latest vector ms/mouse-position-ctrl) (rx/map normalize-proportion-lock) - (rx/mapcat (partial resize shape initial resizing-shapes snap-data)) + (rx/switch-map (fn [[point :as current]] + (->> (snap/closest-snap-point page-id resizing-shapes point) + (rx/map #(conj current %))))) + (rx/mapcat (partial resize shape initial resizing-shapes)) (rx/take-until stoper)) #_(rx/empty) (rx/of (apply-modifiers ids) @@ -222,13 +222,12 @@ (watch [_ state stream] (let [page-id (get state :current-page-id) shapes (mapv #(get-in state [:workspace-data page-id :objects %]) ids) - snap-data (get state :workspace-snap-data) stopper (rx/filter ms/mouse-up? stream)] (rx/concat (->> ms/mouse-position (rx/take-until stopper) (rx/map #(gpt/to-vec from-position %)) - (rx/map (snap/closest-snap-move snap-data shapes)) + (rx/switch-map #(snap/closest-snap-move page-id shapes %)) (rx/map gmt/translate-matrix) (rx/map #(set-modifiers ids {:displacement %}))) diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index 97be5025c..f0e7aab19 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -42,6 +42,9 @@ (def workspace-page (l/derived :workspace-page st/state)) +(def workspace-page-id + (l/derived :id workspace-page)) + (def workspace-file (l/derived :workspace-file st/state)) diff --git a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs index ec74c2763..c36ca7d96 100644 --- a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs +++ b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs @@ -116,20 +116,9 @@ (rx/of handle-drawing-generic))))) (def handle-drawing-generic - (letfn [(initialize-drawing [state point frame-id] - (let [shape (get-in state [:workspace-local :drawing]) - shape (geom/setup shape {:x (:x point) - :y (:y point) - :width 1 - :height 1})] - (assoc-in state [:workspace-local :drawing] (-> shape - (assoc :frame-id frame-id) - (assoc ::initialized? true))))) - - (resize-shape [{:keys [x y] :as shape} initial snap-data point lock?] + (letfn [(resize-shape [{:keys [x y] :as shape} initial point lock? point-snap] (let [shape' (geom/shape->rect-shape shape) shapev (gpt/point (:width shape') (:height shape')) - point-snap (snap/closest-snap-point snap-data [shape] point) deltav (gpt/to-vec initial point-snap) scalev (gpt/divide (gpt/add shapev deltav) shapev) scalev (if lock? @@ -141,8 +130,8 @@ (assoc-in [:modifiers :resize-origin] (gpt/point x y)) (assoc-in [:modifiers :resize-rotation] 0)))) - (update-drawing [state initial snap-data point lock?] - (update-in state [:workspace-local :drawing] resize-shape initial snap-data point lock?))] + (update-drawing [state initial point lock? point-snap] + (update-in state [:workspace-local :drawing] resize-shape initial point lock? point-snap))] (ptk/reify ::handle-drawing-generic ptk/WatchEvent @@ -151,7 +140,6 @@ stoper? #(or (ms/mouse-up? %) (= % :interrupt)) stoper (rx/filter stoper? stream) initial @ms/mouse-position - snap-data (get state :workspace-snap-data) mouse ms/mouse-position page-id (get state :current-page-id) @@ -162,17 +150,26 @@ (filter (comp #{:frame} :type)) (remove #(= (:id %) uuid/zero) )) - frame-id (->> frames - (filter #(geom/has-point? % initial)) - first - :id)] + frame-id (or (->> frames + (filter #(geom/has-point? % initial)) + first + :id) + uuid/zero) + + shape (-> state + (get-in [:workspace-local :drawing]) + (geom/setup {:x (:x initial) :y (:y initial) :width 1 :height 1}) + (assoc :frame-id frame-id) + (assoc ::initialized? true))] (rx/concat - (->> mouse - (rx/take 1) - (rx/map (fn [pt] #(initialize-drawing % pt (or frame-id uuid/zero))))) + (rx/of #(assoc-in state [:workspace-local :drawing] shape)) + (->> mouse (rx/with-latest vector ms/mouse-position-ctrl) - (rx/map (fn [[pt ctrl?]] #(update-drawing % initial snap-data pt ctrl?))) + (rx/switch-map (fn [[point :as current]] + (->> (snap/closest-snap-point page-id [shape] point) + (rx/map #(conj current %))))) + (rx/map (fn [[pt ctrl? point-snap]] #(update-drawing % initial pt ctrl? point-snap))) (rx/take-until stoper)) (rx/of handle-finish-drawing))))))) diff --git a/frontend/src/uxbox/main/ui/workspace/shapes/bbox.cljs b/frontend/src/uxbox/main/ui/workspace/shapes/bbox.cljs index 2f61119d1..6433aaf2f 100644 --- a/frontend/src/uxbox/main/ui/workspace/shapes/bbox.cljs +++ b/frontend/src/uxbox/main/ui/workspace/shapes/bbox.cljs @@ -48,28 +48,4 @@ :fill "transparent" :stroke-width "1px" :stroke-opacity 0.5 - :pointer-events "none"}}] - - #_(for [point (snap/shape-snap-points shape)] - (let [point (gpt/subtract point (gpt/point (:x frame) (:y frame)))] - [:* {:key (str "point-" (:id shape) "-" (:x point) "-" (:y point))} - [:circle {:cx (:x point) - :cy (:y point) - :r 4 - :fill line-color}] - - [:line {:x1 (:x point) - :y1 -10000 - :x2 (:x point) - :y2 10000 - :style {:stroke line-color :stroke-width "1"} - :opacity 0.4}] - - [:line {:x1 -10000 - :y1 (:y point) - :x2 10000 - :y2 (:y point) - :style {:stroke line-color :stroke-width "1"} - :opacity 0.4}]] - )) - ]))) + :pointer-events "none"}}]]))) diff --git a/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs b/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs index e87013f03..b44351410 100644 --- a/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs +++ b/frontend/src/uxbox/main/ui/workspace/snap_feedback.cljs @@ -1,6 +1,7 @@ (ns uxbox.main.ui.workspace.snap-feedback (:require [rumext.alpha :as mf] + [beicon.core :as rx] [uxbox.main.refs :as refs] [uxbox.util.geom.snap :as snap] [uxbox.util.geom.point :as gpt])) @@ -30,33 +31,62 @@ :style {:stroke line-color :stroke-width "1"} :opacity 0.4}]) -(mf/defc snap-feedback [] - (let [selected (mf/deref refs/selected-shapes) +(defn get-snap [coord {:keys [shapes page-id filter-shapes]}] + (->> (rx/from shapes) + (rx/flat-map (fn [shape] + (->> (snap/shape-snap-points shape) + (map #(vector (:frame-id shape) %))))) + (rx/flat-map (fn [[frame-id point]] + (->> (snap/get-snap-points page-id frame-id filter-shapes point coord) + (rx/map #(vector point % coord))))) + (rx/reduce conj []))) + +(mf/defc snap-feedback-points + [{:keys [shapes page-id filter-shapes] :as props}] + (let [state (mf/use-state []) + subject (mf/use-memo #(rx/subject))] + + (mf/use-effect + (fn [] + (->> subject + (rx/switch-map #(rx/combine-latest + concat + (get-snap :y %) + (get-snap :x %))) + (rx/subs #(reset! state %))))) + + (mf/use-effect + (mf/deps shapes) + (fn [] + (rx/push! subject props))) + + [:g.snap-feedback + (for [[point snaps coord] @state] + (if (not-empty snaps) + [:g.point {:key (str "point-" (:x point) "-" (:y point) "-" (name coord))} + [:& snap-point {:key (str "point-" (:x point) "-" (:y point) "-" (name coord)) + :point point}] + + (for [snap snaps] + [:& snap-point {:key (str "snap-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap) "-" (name coord)) + :point snap}]) + + (for [snap snaps] + [:& snap-line {:key (str "line-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap) "-" (name coord)) + :snap snap + :point point}])]))])) + +(mf/defc snap-feedback [{:keys []}] + (let [page-id (mf/deref refs/workspace-page-id) + selected (mf/deref refs/selected-shapes) selected-shapes (mf/deref (refs/objects-by-id selected)) drawing (mf/deref refs/current-drawing-shape) filter-shapes (mf/deref refs/selected-shapes-with-children) current-transform (mf/deref refs/current-transform) snap-data (mf/deref refs/workspace-snap-data) shapes (if drawing [drawing] selected-shapes)] - (when (or drawing current-transform) - (for [shape shapes] - (for [point (snap/shape-snap-points shape)] - (let [frame-id (:frame-id shape) - shape-id (:id shape) - snaps (into #{} - (concat - (snap/get-snap-points snap-data frame-id filter-shapes point :x) - (snap/get-snap-points snap-data frame-id filter-shapes point :y)))] - (if (not-empty snaps) - [:* {:key (str "point-" (:id shape) "-" (:x point) "-" (:y point))} - [:& snap-point {:point point}] - - (for [snap snaps] - [:& snap-point {:key (str "snap-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap)) - :point snap}]) - - (for [snap snaps] - [:& snap-line {:key (str "line-" (:id shape) "-" (:x point) "-" (:y point) "-" (:x snap) "-" (:y snap)) - :snap snap - :point point}])]))))))) + (when (or drawing current-transform) + [:& snap-feedback-points {:shapes shapes + :page-id page-id + :filter-shapes filter-shapes}]))) diff --git a/frontend/src/uxbox/main/worker.cljs b/frontend/src/uxbox/main/worker.cljs index b5f56c04e..af4a92c5e 100644 --- a/frontend/src/uxbox/main/worker.cljs +++ b/frontend/src/uxbox/main/worker.cljs @@ -16,7 +16,7 @@ (defn on-error [instance error] - (js/console.log "error on worker" error)) + (js/console.error "Error on worker" (.-data error))) (defonce instance (when (not= *target* "nodejs") diff --git a/frontend/src/uxbox/util/geom/snap.cljs b/frontend/src/uxbox/util/geom/snap.cljs index 90ce9afcb..c1a83ea6b 100644 --- a/frontend/src/uxbox/util/geom/snap.cljs +++ b/frontend/src/uxbox/util/geom/snap.cljs @@ -11,19 +11,15 @@ (:require [cljs.spec.alpha :as s] [clojure.set :as set] + [beicon.core :as rx] [uxbox.util.math :as mth] [uxbox.common.uuid :refer [zero]] [uxbox.util.geom.shapes :as gsh] [uxbox.util.geom.point :as gpt] - [uxbox.util.range-tree :as rt])) + [uxbox.main.worker :as uw])) (def ^:private snap-accuracy 10) -(defn mapm - "Map over the values of a map" - [mfn coll] - (into {} (map (fn [[key val]] [key (mfn val)]) coll))) - (defn- frame-snap-points [{:keys [x y width height]}] #{(gpt/point x y) (gpt/point (+ x (/ width 2)) y) @@ -55,15 +51,6 @@ :bottom-left 3 :left 3}) -(defn shape-snap-points-resize - [handler shape] - (let [modified-path (gsh/transform-apply-modifiers shape) - point-idx (handler->point-idx handler)] - #{(case (:type shape) - :frame (frame-snap-points-resize shape handler) - (:path :curve) (-> modified-path gsh/shape->rect-shape :segments (nth point-idx)) - (-> modified-path :segments (nth point-idx)))})) - (defn shape-snap-points [shape] (let [modified-path (gsh/transform-apply-modifiers shape) @@ -73,63 +60,23 @@ (:path :curve) (into #{shape-center} (-> modified-path gsh/shape->rect-shape :segments)) (into #{shape-center} (-> modified-path :segments))))) -(defn create-coord-data [shapes coord] - (let [process-shape (fn [coord] - (fn [shape] - (let [points (shape-snap-points shape)] - (map #(vector % (:id shape)) points)))) - into-tree (fn [tree [point _ :as data]] - (rt/insert tree (coord point) data))] - (->> shapes - (mapcat (process-shape coord)) - (reduce into-tree (rt/make-tree))))) -(defn initialize-snap-data - "Initialize the snap information with the current workspace information" - [objects] - (let [shapes (vals objects) - frame-shapes (->> shapes - (filter (comp not nil? :frame-id)) - (group-by :frame-id)) +(defn remove-from-snap-points [ids-to-remove] + (fn [query-result] + (->> query-result + (map (fn [[value data]] [value (remove (comp ids-to-remove second) data)])) + (filter (fn [[_ data]] (not (empty? data))))))) - frame-shapes (->> shapes - (filter #(= :frame (:type %))) - (remove #(= zero (:id %))) - (reduce #(update %1 (:id %2) conj %2) frame-shapes))] - (mapm (fn [shapes] {:x (create-coord-data shapes :x) - :y (create-coord-data shapes :y)}) - frame-shapes))) +(defn- calculate-distance [query-result point coord] + (->> query-result + (map (fn [[value data]] [(mth/abs (- value (coord point))) [(coord point) value]])))) -(defn remove-from-snap-points [snap-points ids-to-remove] - (->> snap-points - (map (fn [[value data]] [value (remove (comp ids-to-remove second) data)])) - (filter (fn [[_ data]] (not (empty? data)))))) - -(defn search-snap-point - "Search snap for a single point in the `coord` given" - [point coord snap-data filter-shapes] - - (let [coord-value (get point coord) - - ;; This gives a list of [value [[point1 uuid1] [point2 uuid2] ...] we need to remove - ;; the shapes in filter shapes - candidates (-> snap-data - (rt/range-query (- coord-value snap-accuracy) (+ coord-value snap-accuracy)) - (remove-from-snap-points filter-shapes)) - - ;; Now return with the distance and the from-to pair that we'll return if this is the chosen - point-snaps (map (fn [[cand-value data]] [(mth/abs (- coord-value cand-value)) [coord-value cand-value]]) candidates)] - point-snaps)) - -(defn search-snap - "Search a snap point in one axis `snap-data` contains the information to make the snap. - `points` are the points that we need to search for a snap and `filter-shapes` is a set of uuids - containgin the shapes that should be ignored to get a snap (usually because they are being moved)" - [points coord snap-data filter-shapes] - - (let [snap-points (mapcat #(search-snap-point % coord snap-data filter-shapes) points) - result (->> snap-points (apply min-key first) second)] - result)) +(defn- get-min-distance-snap [points coord] + (fn [query-result] + (->> points + (mapcat #(calculate-distance query-result % coord)) + (apply min-key first) + second))) (defn snap-frame-id [shapes] (let [frames (into #{} (map :frame-id shapes))] @@ -143,53 +90,63 @@ ;; Otherwise the root frame is the common :else zero))) +(defn flatten-to-points + [query-result] + (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) query-result)) + +(defn get-snap-points [page-id frame-id filter-shapes point coord] + (let [value (coord point)] + (->> (uw/ask! {:cmd :snaps/range-query + :page-id page-id + :frame-id frame-id + :coord coord + :ranges [[(- value 1) (+ value 1)]]}) + (rx/first) + (rx/map (remove-from-snap-points filter-shapes)) + (rx/map flatten-to-points)))) + +(defn- search-snap + [page-id frame-id points coord filter-shapes] + (let [ranges (->> points + (map coord) + (mapv #(vector (- % snap-accuracy) + (+ % snap-accuracy))))] + (->> (uw/ask! {:cmd :snaps/range-query + :page-id page-id + :frame-id frame-id + :coord coord + :ranges ranges}) + (rx/first) + (rx/map (remove-from-snap-points filter-shapes)) + (rx/map (get-min-distance-snap points coord))))) + (defn- closest-snap - [snap-data shapes trans-vec shapes-points] - (let [;; Get the common frame-id to make the snap - frame-id (snap-frame-id shapes) - - ;; We don't want to snap to the shapes currently transformed - remove-shapes (into #{} (map :id shapes)) - - ;; The snap is a tuple. The from is the point in the current moving shape - ;; the "to" is the point where we'll snap. So we need to create a vector - ;; snap-from --> snap-to and move the position in that vector - [snap-from-x snap-to-x] (search-snap shapes-points :x (get-in snap-data [frame-id :x]) remove-shapes) - [snap-from-y snap-to-y] (search-snap shapes-points :y (get-in snap-data [frame-id :y]) remove-shapes) - - snapv (gpt/to-vec (gpt/point (or snap-from-x 0) (or snap-from-y 0)) - (gpt/point (or snap-to-x 0) (or snap-to-y 0)))] - - (gpt/add trans-vec snapv))) + [page-id frame-id points filter-shapes] + (let [snap-x (search-snap page-id frame-id points :x filter-shapes) + snap-y (search-snap page-id frame-id points :y filter-shapes) + snap-as-vector (fn [[from-x to-x] [from-y to-y]] + (let [from (gpt/point (or from-x 0) (or from-y 0)) + to (gpt/point (or to-x 0) (or to-y 0))] + (gpt/to-vec from to)))] + ;; snap-x is the second parameter because is the "source" to combine + (rx/combine-latest snap-as-vector snap-y snap-x))) (defn closest-snap-point - [snap-data shapes point] - (closest-snap snap-data shapes point [point])) + [page-id shapes point] + (let [frame-id (snap-frame-id shapes) + filter-shapes (into #{} (map :id shapes))] + (->> (closest-snap page-id frame-id [point] filter-shapes) + (rx/map #(gpt/add point %))))) (defn closest-snap-move - ([snap-data shapes] (partial closest-snap-move snap-data shapes)) - ([snap-data shapes trans-vec] - (let [shapes-points (->> shapes - ;; Unroll all the possible snap-points - (mapcat (partial shape-snap-points)) + [page-id shapes movev] + (let [frame-id (snap-frame-id shapes) + filter-shapes (into #{} (map :id shapes)) + shapes-points (->> shapes + ;; Unroll all the possible snap-points + (mapcat (partial shape-snap-points)) - ;; Move the points in the translation vector - (map #(gpt/add % trans-vec)))] - (closest-snap snap-data shapes trans-vec shapes-points)))) - -(defn get-snap-points [snap-data frame-id filter-shapes point coord] - (let [value (coord point) - - ;; Search for values within 1 pixel - snap-matches (-> (get-in snap-data [frame-id coord]) - (rt/range-query (- value 1) (+ value 1)) - (remove-from-snap-points filter-shapes)) - - snap-points (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) snap-matches)] - snap-points)) - -(defn is-snapping? [snap-data frame-id shape-id point coord] - (let [value (coord point) - ;; Search for values within 1 pixel - snap-points (rt/range-query (get-in snap-data [frame-id coord]) (- value 1.0) (+ value 1.0))] - (some (fn [[point other-shape-id]] (not (= shape-id other-shape-id))) snap-points))) + ;; Move the points in the translation vector + (map #(gpt/add % movev)))] + (->> (closest-snap page-id frame-id shapes-points filter-shapes) + (rx/map #(gpt/add movev %))))) diff --git a/frontend/src/uxbox/util/range_tree.js b/frontend/src/uxbox/util/range_tree.js index bc16a6a8f..076d0ca1c 100644 --- a/frontend/src/uxbox/util/range_tree.js +++ b/frontend/src/uxbox/util/range_tree.js @@ -103,6 +103,12 @@ goog.scope(function() { recToString(this.root, result); return result.join(", "); } + + asMap() { + const result = {}; + recTreeAsMap(this.root, result); + return result; + } } // Tree implementation functions @@ -243,7 +249,6 @@ goog.scope(function() { recRangeQuery(branch.left, fromValue, toValue, result); } if (fromValue <= branch.value && toValue >= branch.value) { - // Array.prototype.push.apply(result, branch.data); result.push(vec([branch.value, vec(branch.data)])) } if (toValue > branch.value) { @@ -345,6 +350,16 @@ goog.scope(function() { return "[" + printTree(tree.left) + " " + val + " " + printTree(tree.right) + "]"; } + function recTreeAsMap(branch, result) { + if (!branch) { + return result; + } + recTreeAsMap(branch.left, result); + result[branch.value] = branch.data; + recTreeAsMap(branch.right, result); + return result; + } + // External API to CLJS const self = uxbox.util.range_tree; self.make_tree = () => new RangeTree(); @@ -367,5 +382,6 @@ goog.scope(function() { self.empty_QMARK_ = (tree) => tree.isEmpty(); self.height = (tree) => tree.height(); self.print = (tree) => printTree(tree.root); + self.as_map = (tree) => tree.asMap(); }); diff --git a/frontend/src/uxbox/worker.cljs b/frontend/src/uxbox/worker.cljs index 2d5ff494f..3cd00e7bd 100644 --- a/frontend/src/uxbox/worker.cljs +++ b/frontend/src/uxbox/worker.cljs @@ -19,6 +19,7 @@ [uxbox.worker.impl :as impl] [uxbox.worker.selection] [uxbox.worker.thumbnails] + [uxbox.worker.snaps] [uxbox.util.transit :as t] [uxbox.util.worker :as w])) diff --git a/frontend/src/uxbox/worker/impl.cljs b/frontend/src/uxbox/worker/impl.cljs index 8b44e74c6..3289cf55d 100644 --- a/frontend/src/uxbox/worker/impl.cljs +++ b/frontend/src/uxbox/worker/impl.cljs @@ -20,3 +20,17 @@ (defmethod handler :echo [message] message) + +(defmethod handler :create-page-indices + [message] + (handler (-> message + (assoc :cmd :selection/create-index))) + (handler (-> message + (assoc :cmd :snaps/create-index)))) + +(defmethod handler :update-page-indices + [message] + (handler (-> message + (assoc :cmd :selection/update-index))) + (handler (-> message + (assoc :cmd :snaps/update-index)))) diff --git a/frontend/src/uxbox/worker/snaps.cljs b/frontend/src/uxbox/worker/snaps.cljs new file mode 100644 index 000000000..e1573524f --- /dev/null +++ b/frontend/src/uxbox/worker/snaps.cljs @@ -0,0 +1,97 @@ +;; 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.worker.snaps + (:require + [okulary.core :as l] + [uxbox.common.uuid :as uuid] + [uxbox.worker.impl :as impl] + [uxbox.util.range-tree :as rt] + [uxbox.util.geom.snap :as snap])) + +(defonce state (l/atom {})) + +(defn- create-coord-data + "Initializes the range tree given the shapes" + [shapes coord] + (let [process-shape (fn [coord] + (fn [shape] + (let [points (snap/shape-snap-points shape)] + (map #(vector % (:id shape)) points)))) + into-tree (fn [tree [point _ :as data]] + (rt/insert tree (coord point) data))] + (->> shapes + (mapcat (process-shape coord)) + (reduce into-tree (rt/make-tree))))) + +(defn- mapm + "Map over the values of a map" + [mfn coll] + (into {} (map (fn [[key val]] [key (mfn val)]) coll))) + +(defn- initialize-snap-data + "Initialize the snap information with the current workspace information" + [objects] + (let [shapes (vals objects) + frame-shapes (->> shapes + (filter (comp not nil? :frame-id)) + (group-by :frame-id)) + + frame-shapes (->> shapes + (filter #(= :frame (:type %))) + (remove #(= uuid/zero (:id %))) + (reduce #(update %1 (:id %2) conj %2) frame-shapes))] + (mapm (fn [shapes] {:x (create-coord-data shapes :x) + :y (create-coord-data shapes :y)}) + frame-shapes))) + +(defn- log-state + "Helper function to print a friendly version of the snap tree. Debugging purposes" + [] + (let [process-frame-data #(mapm rt/as-map %) + process-page-data #(mapm process-frame-data %)] + (js/console.log "STATE" (clj->js (mapm process-page-data @state))))) + +(defn- index-page [state page-id objects] + (let [snap-data (initialize-snap-data objects)] + (assoc state page-id snap-data))) + +;; Public API +(defmethod impl/handler :snaps/create-index + [{:keys [file-id pages] :as message}] + + ;; Create the index + (letfn [(process-page [state page] + (let [id (:id page) + objects (get-in page [:data :objects])] + (index-page state id objects)))] + (swap! state #(reduce process-page % pages))) + + #_(log-state) + ;; Return nil so the worker will not answer anything back + nil) + +(defmethod impl/handler :snaps/update-index + [{:keys [page-id objects] :as message}] + ;; TODO: Check the difference and update the index acordingly + (swap! state index-page page-id objects) + #_(log-state) + nil) + +(defmethod impl/handler :snaps/range-query + [{:keys [page-id frame-id coord ranges] :as message}] + (letfn [(calculate-range [[from to]] + (-> @state + (get-in [page-id frame-id coord]) + (rt/range-query from to)))] + (->> ranges + (mapcat calculate-range) + set ;; unique + (into [])))) +