diff --git a/frontend/deps.edn b/frontend/deps.edn index 29f93a12b..b1d4e1e3d 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -10,8 +10,8 @@ funcool/beicon {:mvn/version "5.1.0"} funcool/cuerdas {:mvn/version "2.2.0"} funcool/lentes {:mvn/version "1.3.0-SNAPSHOT"} - funcool/potok {:mvn/version "2.5.0"} - funcool/promesa {:mvn/version "3.0.0-SNAPSHOT"} + funcool/potok {:mvn/version "2.6.0"} + funcool/promesa {:mvn/version "4.0.0-SNAPSHOT"} funcool/rumext {:mvn/version "2.0.0-SNAPSHOT"} } :paths ["src" "vendor" "resources"] diff --git a/frontend/src/uxbox/main/data/auth.cljs b/frontend/src/uxbox/main/data/auth.cljs index a6d80b6c4..661f2c669 100644 --- a/frontend/src/uxbox/main/data/auth.cljs +++ b/frontend/src/uxbox/main/data/auth.cljs @@ -27,10 +27,7 @@ (defn logged-in [data] - (reify - ptk/EventType - (type [_] ::logged-in) - + (ptk/reify ::logged-in ptk/UpdateEvent (update [this state] (assoc state :auth data)) diff --git a/frontend/src/uxbox/main/data/pages.cljs b/frontend/src/uxbox/main/data/pages.cljs index fa6063b8c..d57271956 100644 --- a/frontend/src/uxbox/main/data/pages.cljs +++ b/frontend/src/uxbox/main/data/pages.cljs @@ -153,13 +153,10 @@ [id pages] (s/assert ::us/uuid id) (s/assert ::us/coll pages) - (reify + (ptk/reify ::page-fetched IDeref (-deref [_] (list id pages)) - ptk/EventType - (type [_] ::page-fetched) - ptk/UpdateEvent (update [_ state] (let [get-order #(get-in % [:metadata :order]) @@ -262,13 +259,10 @@ (defn page-persisted [data] (s/assert ::server-page data) - (reify + (ptk/reify ::page-persisted cljs.core/IDeref (-deref [_] data) - ptk/EventType - (type [_] ::page-persisted) - ptk/UpdateEvent (update [_ state] (let [{:keys [id version]} data] @@ -283,24 +277,19 @@ ;; --- Persist Page (defn persist-page - ([id] (persist-page id identity)) - ([id on-success] - (assert (uuid? id)) - (reify - ptk/EventType - (type [_] ::persist-page) - - ptk/WatchEvent - (watch [this state s] - (let [page (get-in state [:pages id])] - (if (:history page) - (rx/empty) - (let [page (pack-page state id)] - (->> (rp/req :update/page page) - (rx/map :payload) - (rx/map page-persisted) - (rx/catch (fn [err] (rx/of ::page-persist-error))))))))))) - + [id] + (s/assert ::us/uuid id) + (ptk/reify ::persist-page + ptk/WatchEvent + (watch [this state s] + (let [page (get-in state [:pages id])] + (if (:history page) + (rx/empty) + (let [page (pack-page state id)] + (->> (rp/req :update/page page) + (rx/map :payload) + (rx/map page-persisted) + (rx/catch (fn [err] (rx/of ::page-persist-error)))))))))) (defn persist-page? [v] @@ -314,10 +303,7 @@ (defn metadata-persisted [{:keys [id] :as data}] (s/assert ::metadata-persisted-params data) - (reify - ptk/EventType - (type [_] ::metadata-persisted) - + (ptk/reify ::metadata-persisted ptk/UpdateEvent (update [_ state] (assoc-in state [:pages id :version] (:version data))))) @@ -335,8 +321,8 @@ (defn persist-metadata [id] - {:pre [(uuid? id)]} - (reify + (s/assert ::us/uuid id) + (ptk/reify ::persist-metadata ptk/WatchEvent (watch [_ state stream] (let [page (get-in state [:pages id])] @@ -349,7 +335,7 @@ (defn update-page-attrs [{:keys [id] :as data}] (s/assert ::page-entity data) - (reify + (ptk/reify IPageUpdate ptk/UpdateEvent (update [_ state] @@ -446,6 +432,5 @@ (->> (rx/filter metadata-persisted? stream) (rx/take 1) (rx/ignore)))))) - (rx/take-until stopper) - (rx/retry 10000)))))) + (rx/take-until stopper)))))) diff --git a/frontend/src/uxbox/main/data/shapes.cljs b/frontend/src/uxbox/main/data/shapes.cljs index 9e3a89ed2..b7ae780ad 100644 --- a/frontend/src/uxbox/main/data/shapes.cljs +++ b/frontend/src/uxbox/main/data/shapes.cljs @@ -188,31 +188,16 @@ (defn dissoc-from-index "A function that dissoc shape from the indexed data structure of shapes from the state." - [state {:keys [id type] :as shape}] - (if (= :group type) - (let [items (map #(get-in state [:shapes %]) (:items shape))] - (as-> state $ - (update-in $ [:shapes] dissoc id) - (reduce dissoc-from-index $ items))) - (update-in state [:shapes] dissoc id))) + [state shape] + (update state :shapes dissoc (:id shape))) (defn dissoc-from-page "Given a shape, try to remove its reference from the corresponding page." [state {:keys [id page] :as shape}] - (as-> (get-in state [:pages page :shapes]) $ - (into [] (remove #(= % id) $)) - (assoc-in state [:pages page :shapes] $))) - -(defn dissoc-from-group - "Given a shape, try to remove its reference from the - corresponding group (only if it belongs to one group)." - [state {:keys [id group] :as shape}] - (if-let [group' (get-in state [:shapes group])] - (as-> (:items group') $ - (into [] (remove #(= % id) $)) - (assoc-in state [:shapes group :items] $)) - state)) + ;; TODO: handle canvas special case + (update-in state [:pages page :shapes] + (fn [items] (vec (remove #(= % id) items))))) (declare dissoc-shape) @@ -234,11 +219,9 @@ (defn dissoc-shape "Given a shape, removes it from the state." [state shape] - (as-> state $ - (dissoc-from-page $ shape) - (dissoc-from-group $ shape) - (dissoc-from-index $ shape) - (clear-empty-groups $ shape))) + (-> state + (dissoc-from-page shape) + (dissoc-from-index shape))) ;; --- Shape Movements @@ -265,8 +248,7 @@ :last (- (count shapes) 1)) state (-> state - (dissoc-from-page shape) - (dissoc-from-group shape)) + (dissoc-from-page shape)) shapes (if group (get-in state [:shapes group :items]) @@ -293,8 +275,7 @@ source (get-in state [:shapes sid]) state (-> state - (dissoc-from-page source) - (dissoc-from-group source)) + (dissoc-from-page source)) shapes (if group (get-in state [:shapes group :items]) @@ -323,8 +304,7 @@ {:pre [(not= tid sid)]} (let [source (get-in state [:shapes sid]) state (-> state - (dissoc-from-page source) - (dissoc-from-group source)) + (dissoc-from-page source)) shapes (get-in state [:shapes tid :items])] (if (seq shapes) (as-> state $ diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 93744025e..463a3a9ec 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -25,9 +25,13 @@ [uxbox.util.geom.point :as gpt] [uxbox.util.math :as mth] [uxbox.util.spec :as us] + [uxbox.util.perf :as perf] [uxbox.util.time :as dt] [uxbox.util.uuid :as uuid])) +(s/def ::set-of-uuid + (s/every ::us/uuid :kind set?)) + ;; --- Expose inner functions (def start-ruler nil) @@ -338,6 +342,8 @@ ;; Shapes events ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TODO: add spec + (defn add-shape [data] (reify @@ -352,14 +358,24 @@ (defn delete-shape [id] - {:pre [(uuid? id)]} - (reify + (s/assert ::us/uuid id) + (ptk/reify ::delete-shape udp/IPageUpdate ptk/UpdateEvent (update [_ state] (let [shape (get-in state [:shapes id])] (ds/dissoc-shape state shape))))) +(defn delete-many-shapes + [ids] + (s/assert ::us/set ids) + (ptk/reify ::delete-many-shapes + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (reduce ds/dissoc-shape state + (map #(get-in state [:shapes %]) ids))))) + (defrecord SelectShape [id] ptk/UpdateEvent (update [_ state] @@ -385,10 +401,7 @@ (let [pid (get-in state [:workspace :current])] (update-in state [:workspace pid] #(-> % (assoc :selected #{}) - (dissoc :selected-canvas))))) - ptk/WatchEvent - (watch [_ state stream] - (rx/just :interrupt))) + (dissoc :selected-canvas)))))) (defn deselect-all "Clear all possible state of drawing, edition @@ -398,22 +411,18 @@ ;; --- Select First Shape -(deftype SelectFirstShape [] - ptk/UpdateEvent - (update [_ state] - (let [pid (get-in state [:workspace :current]) - sid (first (get-in state [:pages pid :shapes]))] - (assoc-in state [:workspace pid :selected] #{sid})))) - -(defn select-first-shape - "Mark a shape selected for drawing." - [] - (SelectFirstShape.)) +(def select-first-shape + (ptk/reify ::select-first-shape + ptk/UpdateEvent + (update [_ state] + (let [pid (get-in state [:workspace :current]) + sid (first (get-in state [:pages pid :shapes]))] + (assoc-in state [:workspace pid :selected] #{sid}))))) ;; --- Select Shapes (By selrect) (def select-shapes-by-current-selrect - (reify + (ptk/reify ::select-shapes-by-current-selrect ptk/UpdateEvent (update [_ state] (let [pid (get-in state [:workspace :current]) @@ -471,11 +480,9 @@ :fast (gpt/point (if align? (* 3 gx) 10) (if align? (* 3 gy) 10))})) -(declare initial-shape-align) -(declare apply-displacement) -(declare assoc-temporal-modifier) -(declare materialize-current-modifier) -(declare apply-temporal-displacement) +(declare initial-selection-align) +(declare materialize-current-modifier-in-bulk) +(declare apply-temporal-displacement-in-bulk) (s/def ::direction #{:up :down :right :left}) (s/def ::speed #{:std :fast}) @@ -497,11 +504,9 @@ displacement (get-displacement direction speed distance)] (rx/concat (when align? - (rx/concat - (rx/from-coll (map initial-shape-align selected)) - (rx/from-coll (map apply-displacement selected)))) - (rx/from-coll (map #(apply-temporal-displacement % displacement) selected)) - (rx/from-coll (map materialize-current-modifier selected))))))) + (rx/of (initial-selection-align selected))) + (rx/of (apply-temporal-displacement-in-bulk selected displacement)) + (rx/of (materialize-current-modifier-in-bulk selected))))))) ;; --- Move Selected Layer @@ -539,8 +544,7 @@ (watch [_ state stream] (let [id (get-in state [:workspace :current]) selected (get-in state [:workspace id :selected])] - (rx/from-coll - (into [(deselect-all)] (map #(delete-shape %) selected))))))) + (rx/of (delete-many-shapes selected)))))) ;; --- Rename Shape @@ -584,56 +588,71 @@ canvas (vec (concat before [id] after))] (assoc-in state [:pages page-id :canvas] canvas))))) -;; --- Shape Transformations +;; --- Shape / Selection Alignment -(defn initial-shape-align - [id] - {:pre [(uuid? id)]} - (reify - ptk/WatchEvent - (watch [_ state s] - (let [{:keys [x1 y1] :as shape} (->> (get-in state [:shapes id]) - (geom/shape->rect-shape state)) - point (gpt/point x1 y1)] - (->> (uwrk/align-point point) - (rx/map (fn [{:keys [x y] :as pt}] - (apply-temporal-displacement id (gpt/subtract pt point))))))))) - - -;; --- Apply Temporal Displacement - -(defn apply-temporal-displacement - [id delta] - {:pre [(uuid? id) (gpt/point? delta)]} - (reify +(defn initial-selection-align + "Align the selection of shapes." + [ids] + (s/assert ::set-of-uuid ids) + (ptk/reify ::initialize-shapes-align-in-bulk ptk/WatchEvent (watch [_ state stream] - (let [prev (get-in state [:shapes id :modifier-mtx] (gmt/matrix)) - curr (gmt/translate prev delta)] - (rx/of (assoc-temporal-modifier id curr)))))) + (let [shapes-by-id (:shapes state) + shapes (mapv #(get shapes-by-id %) ids) + sshape (geom/shapes->rect-shape shapes) + point (gpt/point (:x1 sshape) + (:y1 sshape))] + (->> (uwrk/align-point point) + (rx/map (fn [{:keys [x y] :as pt}] + (apply-temporal-displacement-in-bulk ids (gpt/subtract pt point))))))))) + +;; --- Temportal displacement for Shape / Selection + +;; TODO: this can be done in more performant way using transients + +(defn apply-temporal-displacement-in-bulk + "Apply the same displacement delta to all shapes identified by the + set if ids." + [ids delta] + (s/assert ::set-of-uuid ids) + (s/assert gpt/point? delta) + (letfn [(process-shape [state id] + (let [prev (get-in state [:shapes id :modifier-mtx] (gmt/matrix)) + xfmt (gmt/translate prev delta)] + (assoc-in state [:shapes id :modifier-mtx] xfmt)))] + (ptk/reify ::apply-temporal-displacement-in-bulk + ptk/UpdateEvent + (update [_ state] + (perf/with-measure ::apply-temporal-displacement-in-bulk + (reduce process-shape state ids)))))) ;; --- Modifiers -(defn assoc-temporal-modifier - [id xfmt] - {:pre [(uuid? id) - (gmt/matrix? xfmt)]} - (reify +(defn assoc-temporal-modifier-in-bulk + [ids xfmt] + (s/assert ::set-of-uuid ids) + (s/assert gmt/matrix? xfmt) + (ptk/reify ::assoc-temporal-modifier-in-bulk ptk/UpdateEvent (update [_ state] - (assoc-in state [:shapes id :modifier-mtx] xfmt)))) + (reduce #(assoc-in %1 [:shapes %2 :modifier-mtx] xfmt) state ids)))) -(defn materialize-current-modifier - [id] - {:pre [(uuid? id)]} - (reify - ptk/WatchEvent - (watch [_ state stream] - (let [xfmt (get-in state [:shapes id :modifier-mtx])] - (when (gmt/matrix? xfmt) - (rx/of #(update-in % [:shapes id] geom/transform xfmt) - #(update-in % [:shapes id] dissoc :modifier-mtx) - ::udp/page-update)))))) +(defn materialize-current-modifier-in-bulk + [ids] + (s/assert ::us/set ids) + (letfn [(process-shape [state id] + (let [xfmt (get-in state [:shapes id :modifier-mtx])] + (if (gmt/matrix? xfmt) + (-> state + (update-in [:shapes id] geom/transform xfmt) + (update-in [:shapes id] dissoc :modifier-mtx)) + state)))] + (ptk/reify ::materialize-current-modifier-in-bulk + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (perf/with-measure ::materialize-current-modifier-in-bulk + (reduce process-shape state ids)))))) (defn rehash-shape-relationship "Checks shape overlaping with existing canvas, if one or more @@ -644,10 +663,7 @@ (let [shape1 (geom/shape->rect-shape canvas) shape2 (geom/shape->rect-shape shape)] (geom/overlaps? shape1 shape2)))] - (reify - ptk/EventType - (type [_] ::rehash-shape-relationship) - + (ptk/reify ::rehash-shape-relationship ptk/UpdateEvent (update [_ state] (let [shape (get-in state [:shapes id]) @@ -945,38 +961,36 @@ ;; Canvas Interactions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; ;; --- Group Collapsing - -;; (deftype CollapseGroupShape [id] -;; udp/IPageUpdate -;; ptk/UpdateEvent -;; (update [_ state] -;; (update-in state [:shapes id] assoc :collapsed true))) - -;; (defn collapse-shape -;; [id] -;; {:pre [(uuid? id)]} -;; (CollapseGroupShape. id)) - -;; (deftype UncollapseGroupShape [id] -;; udp/IPageUpdate -;; ptk/UpdateEvent -;; (update [_ state] -;; (update-in state [:shapes id] assoc :collapsed false))) - -;; (defn uncollapse-shape -;; [id] -;; {:pre [(uuid? id)]} -;; (UncollapseGroupShape. id)) - (defn select-canvas [id] - (reify + (s/assert ::us/uuid id) + (ptk/reify ::select-canvas ptk/UpdateEvent (update [_ state] (let [pid (get-in state [:workspace :current])] (update-in state [:workspace pid] assoc :selected-canvas id))))) +;; (defn watch-page-changes +;; [id] +;; (s/assert ::us/uuid id) +;; (ptk/reify ::watch-page-changes +;; ptk/WatchEvent +;; (watch [_ state stream] +;; (let [stopper (rx/filter #(= % ::stop-page-watcher) stream)] +;; (->> (rx/merge +;; (->> stream +;; (rx/filter #(or (satisfies? IPageUpdate %) +;; (= ::page-update %))) +;; (rx/map #(rehash-shape-relationship +;; (->> stream +;; (rx/filter #(satisfies? IMetadataUpdate %)) +;; (rx/debounce 1000) +;; (rx/mapcat #(rx/merge (rx/of (persist-metadata id)) +;; (->> (rx/filter metadata-persisted? stream) +;; (rx/take 1) +;; (rx/ignore)))))) +;; (rx/take-until stopper)))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Server Interactions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/uxbox/main/geom.cljs b/frontend/src/uxbox/main/geom.cljs index 9d1884f00..9195e725c 100644 --- a/frontend/src/uxbox/main/geom.cljs +++ b/frontend/src/uxbox/main/geom.cljs @@ -456,31 +456,50 @@ (defn shape->rect-shape "Coerce shape to rect like shape." - ([shape] (shape->rect-shape @st/state shape)) - ([state {:keys [type] :as shape}] - (case type - :circle (circle->rect-shape state shape) - :path (path->rect-shape state shape) - :curve (path->rect-shape state shape) - shape))) + [{:keys [type] :as shape}] + (case type + :circle (circle->rect-shape shape) + :path (path->rect-shape shape) + :curve (path->rect-shape shape) + shape)) (defn shapes->rect-shape - ([shapes] (shapes->rect-shape @st/state shapes)) - ([state [shape :as shapes]] - {:pre [(seq shapes)]} - (let [shapes (map shape->rect-shape shapes) - minx (apply min (map :x1 shapes)) - miny (apply min (map :y1 shapes)) - maxx (apply max (map :x2 shapes)) - maxy (apply max (map :y2 shapes))] - {:x1 minx - :y1 miny - :x2 maxx - :y2 maxy - :type :rect}))) + [shapes] + (let [shapes (mapv shape->rect-shape shapes) + minx (apply js/Math.min (mapv :x1 shapes)) + miny (apply js/Math.min (mapv :y1 shapes)) + maxx (apply js/Math.max (mapv :x2 shapes)) + maxy (apply js/Math.max (mapv :y2 shapes))] + {:x1 minx + :y1 miny + :x2 maxx + :y2 maxy + :type :rect})) + +(defn shapes->rect-shape' + [shapes] + (let [shapes (mapv shape->rect-shape shapes) + total (count shapes)] + (loop [idx (int 0) + minx js/Number.POSITIVE_INFINITY + miny js/Number.POSITIVE_INFINITY + maxx js/Number.NEGATIVE_INFINITY + maxy js/Number.NEGATIVE_INFINITY] + (if (> total idx) + (let [{:keys [x1 y1 x2 y2]} (nth shapes idx)] + (recur (inc idx) + (min minx x1) + (min miny y1) + (max maxx x2) + (max maxy y2))) + {:x1 minx + :y1 miny + :x2 maxx + :y2 maxy + :type :rect})))) (defn- path->rect-shape - [state {:keys [segments] :as shape}] + [{:keys [segments] :as shape}] (let [minx (apply min (map :x segments)) miny (apply min (map :y segments)) maxx (apply max (map :x segments)) @@ -492,7 +511,7 @@ :y2 maxy))) (defn- circle->rect-shape - [state {:keys [cx cy rx ry] :as shape}] + [{:keys [cx cy rx ry] :as shape}] (let [width (* rx 2) height (* ry 2) x1 (- cx rx) diff --git a/frontend/src/uxbox/main/store.cljs b/frontend/src/uxbox/main/store.cljs index 7b7b90998..b26bd4ecb 100644 --- a/frontend/src/uxbox/main/store.cljs +++ b/frontend/src/uxbox/main/store.cljs @@ -20,6 +20,24 @@ (defonce store (ptk/store {:on-error #(*on-error* %)})) (defonce stream (ptk/input-stream store)) +;; (defn repr-event +;; [event] +;; (cond +;; (satisfies? ptk/Event event) +;; (str "typ: " (pr-str (ptk/type event))) + +;; (and (fn? event) +;; (pos? (count (.-name event)))) +;; (str "fn: " (demunge (.-name event))) + +;; :else +;; (str "unk: " (pr-str event)))) + +;; (defonce debug (as-> stream $ +;; (rx/filter ptk/event? $) +;; (rx/subscribe $ (fn [event] +;; (println "[stream]: " (repr-event event)))))) + (def auth-ref (-> (l/key :auth) (l/derive state))) diff --git a/frontend/src/uxbox/main/ui/shapes.cljs b/frontend/src/uxbox/main/ui/shapes.cljs index 256c0a78d..e0da96739 100644 --- a/frontend/src/uxbox/main/ui/shapes.cljs +++ b/frontend/src/uxbox/main/ui/shapes.cljs @@ -30,12 +30,39 @@ :image [:& image/image-component {:shape shape}] :circle [:& circle/circle-component {:shape shape}]))) +(mf/defc render-shape' + {:wrap [mf/wrap-memo]} + [{:keys [shape] :as props}] + (render-shape shape)) + (mf/defc shape-component {:wrap [mf/wrap-memo]} [{:keys [id] :as props}] - (let [shape-iref (mf/use-memo {:deps #js [id] - :fn #(-> (l/in [:shapes id]) - (l/derive st/state))})] - (when-let [shape (mf/deref shape-iref)] - (when-not (:hidden shape) - (render-shape shape))))) + (uxbox.util.perf/with-measure ::foobar + (let [shape-iref (mf/use-memo {:deps #js [id] + :fn #(-> (l/in [:shapes id]) + (l/derive st/state))})] + (when-let [shape (mf/deref shape-iref)] + (when-not (:hidden shape) + (mf/html + [:& render-shape' {:shape shape}])))))) + +(mf/defc shape-component' + {:wrap [mf/wrap-memo]} + [{:keys [shape] :as props}] + (when (and shape (not (:hidden shape))) + [:& render-shape' {:shape shape}])) + +(def ^:private shapes-iref + (-> (l/key :shapes) + (l/derive st/state))) + +(mf/defc all-shapes + {:wrap [mf/wrap-memo]} + [{:keys [page] :as props}] + (let [shapes-by-id (mf/deref shapes-iref) + shapes (map #(get shapes-by-id %) (:shapes page))] + [:* + (for [item shapes] + [:& shape-component' {:shape item :key (:id item)}])])) + diff --git a/frontend/src/uxbox/main/ui/shapes/common.cljs b/frontend/src/uxbox/main/ui/shapes/common.cljs index aba6cfbd2..7da6d3823 100644 --- a/frontend/src/uxbox/main/ui/shapes/common.cljs +++ b/frontend/src/uxbox/main/ui/shapes/common.cljs @@ -18,31 +18,21 @@ ;; --- Shape Movement (by mouse) -(defn start-move - [id] - {:pre [(uuid? id)]} - (reify - ptk/WatchEvent - (watch [_ state stream] - (let [pid (get-in state [:workspace :current]) - flags (get-in state [:workspace pid :flags]) - stoper (rx/filter uws/mouse-up? stream)] - (rx/concat - (when (refs/alignment-activated? flags) - (rx/of (dw/initial-shape-align id))) - (->> uws/mouse-position-deltas - (rx/map #(dw/apply-temporal-displacement id %)) - (rx/take-until stoper)) - (rx/of (dw/materialize-current-modifier id) - (dw/rehash-shape-relationship id))))))) - (def start-move-selected (reify ptk/WatchEvent (watch [_ state stream] (let [pid (get-in state [:workspace :current]) - selected (get-in state [:workspace pid :selected])] - (rx/from-coll (map start-move selected)))))) + flags (get-in state [:workspace pid :flags]) + selected (get-in state [:workspace pid :selected]) + stoper (rx/filter uws/mouse-up? stream)] + (rx/concat + (when (refs/alignment-activated? flags) + (rx/of (dw/initial-selection-align selected))) + (->> uws/mouse-position-deltas + (rx/map #(dw/apply-temporal-displacement-in-bulk selected %)) + (rx/take-until stoper)) + (rx/of (dw/materialize-current-modifier-in-bulk selected))))))) (defn on-mouse-down [event {:keys [id type] :as shape} selected] diff --git a/frontend/src/uxbox/main/ui/shapes/rect.cljs b/frontend/src/uxbox/main/ui/shapes/rect.cljs index 9bd03450b..8df875ae8 100644 --- a/frontend/src/uxbox/main/ui/shapes/rect.cljs +++ b/frontend/src/uxbox/main/ui/shapes/rect.cljs @@ -55,9 +55,9 @@ props {:x x1 :y y1 :id (str "shape-" id) - :class-name (classnames :move-cursor moving?) + :className (classnames :move-cursor moving?) :width width :height height :transform transform} attrs (merge (attrs/extract-style-attrs shape) props)] - [:> :rect (normalize-props attrs)])) + [:& "rect" attrs])) diff --git a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs index 89d92eeb0..5f7cfe62b 100644 --- a/frontend/src/uxbox/main/ui/workspace/drawarea.cljs +++ b/frontend/src/uxbox/main/ui/workspace/drawarea.cljs @@ -36,27 +36,6 @@ (declare handle-finish-drawing) (declare conditional-align) -(defn start-drawing - [type] - {:pre [(keyword? type)]} - (let [id (gensym "drawing")] - (reify - ptk/UpdateEvent - (update [_ state] - (update-in state [:workspace :drawing-lock] #(if (nil? %) id %))) - - ptk/WatchEvent - (watch [_ state stream] - (let [pid (get-in state [:workspace :current]) - lock (get-in state [:workspace :drawing-lock])] - (if (= lock id) - (rx/merge - (->> (rx/filter #(= % handle-finish-drawing) stream) - (rx/take 1) - (rx/map (fn [_] #(update % :workspace dissoc :drawing-lock)))) - (rx/of (handle-drawing type))) - (rx/empty))))))) - (def ^:private minimal-shapes [{:type :rect :name "Rect" @@ -87,6 +66,27 @@ :name "Text" :content "Type your text here"}]) +(defn start-drawing + [type] + {:pre [(keyword? type)]} + (let [id (gensym "drawing")] + (ptk/reify ::start-drawing + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace :drawing-lock] #(if (nil? %) id %))) + + ptk/WatchEvent + (watch [_ state stream] + (let [pid (get-in state [:workspace :current]) + lock (get-in state [:workspace :drawing-lock])] + (if (= lock id) + (rx/merge + (->> (rx/filter #(= % handle-finish-drawing) stream) + (rx/take 1) + (rx/map (fn [_] #(update % :workspace dissoc :drawing-lock)))) + (rx/of (handle-drawing type))) + (rx/empty))))))) + (defn- make-minimal-shape [type] (let [tool (seek #(= type (:type %)) minimal-shapes)] @@ -95,7 +95,7 @@ (defn handle-drawing [type] - (reify + (ptk/reify ::handle-drawing ptk/UpdateEvent (update [_ state] (let [pid (get-in state [:workspace :current]) @@ -131,7 +131,7 @@ (let [pid (get-in state [:workspace :current])] (update-in state [:workspace pid :drawing] resize-shape point lock?)))] - (reify + (ptk/reify ::handle-drawing-generic ptk/WatchEvent (watch [_ state stream] (let [pid (get-in state [:workspace :current]) @@ -184,7 +184,7 @@ (remove-dangling-segmnet [state] (let [pid (get-in state [:workspace :current])] (update-in state [:workspace pid :drawing :segments] #(vec (butlast %)))))] - (reify + (ptk/reify ::handle-drawing-path ptk/WatchEvent (watch [_ state stream] (let [pid (get-in state [:workspace :current]) @@ -262,7 +262,8 @@ (simplify-drawing-path [state tolerance] (let [pid (get-in state [:workspace :current])] (update-in state [:workspace pid :drawing :segments] path/simplify tolerance)))] - (reify + + (ptk/reify ::handle-drawing-curve ptk/WatchEvent (watch [_ state stream] (let [pid (get-in state [:workspace :current]) @@ -283,7 +284,7 @@ handle-finish-drawing))))))) (def handle-finish-drawing - (reify + (ptk/reify ::handle-finish-drawing ptk/WatchEvent (watch [_ state stream] (let [pid (get-in state [:workspace :current]) @@ -298,10 +299,10 @@ shape (dissoc shape ::initialized? :modifier-mtx)] ;; Add & select the cred shape to the workspace (rx/of (dw/add-shape shape) - (dw/select-first-shape))))))))) + dw/select-first-shape)))))))) (def close-drawing-path - (reify + (ptk/reify ::close-drawing-path ptk/UpdateEvent (update [_ state] (let [pid (get-in state [:workspace :current])] diff --git a/frontend/src/uxbox/main/ui/workspace/selection.cljs b/frontend/src/uxbox/main/ui/workspace/selection.cljs index 1b55a5eb3..97bcac7ea 100644 --- a/frontend/src/uxbox/main/ui/workspace/selection.cljs +++ b/frontend/src/uxbox/main/ui/workspace/selection.cljs @@ -39,7 +39,7 @@ (let [result (geom/resize-shape vid shape point lock?) scale (geom/calculate-scale-ratio shape result) mtx (geom/generate-resize-matrix vid shape scale)] - (apply rx/of (map #(dw/assoc-temporal-modifier % mtx) ids)))) + (rx/of (dw/assoc-temporal-modifier-in-bulk ids mtx)))) ;; Unifies the instantaneous proportion lock modifier ;; activated by Ctrl key and the shapes own proportion @@ -72,9 +72,7 @@ (rx/map normalize-proportion-lock) (rx/mapcat (partial resize shape)) (rx/take-until stoper)) - (rx/from-coll (map dw/materialize-current-modifier ids)))))))) - - ;; (rx/subscribe stream (partial on-resize shape) nil on-end)))) + (rx/of (dw/materialize-current-modifier-in-bulk ids)))))))) ;; --- Controls (Component) @@ -188,14 +186,16 @@ :stroke "#28c4d4" :style {:cursor "pointer"}}])]))) +;; TODO: add specs for clarity + (mf/defc multiple-selection-handlers - [{:keys [shapes zoom] :as props}] + [{:keys [shapes selected zoom] :as props}] (let [shape (->> shapes (map #(geom/selection-rect %)) (geom/shapes->rect-shape) (geom/selection-rect)) on-click #(do (dom/stop-propagation %2) - (st/emit! (start-resize %1 (mapv :id shapes) shape)))] + (st/emit! (start-resize %1 selected shape)))] [:& controls {:shape shape :zoom zoom :on-click on-click}])) @@ -239,6 +239,7 @@ (> num 1) [:& multiple-selection-handlers {:shapes shapes + :selected (:selected wst) :zoom zoom}] (and (= type :text) diff --git a/frontend/src/uxbox/main/ui/workspace/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index 05e4ec8db..6954bd78d 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -85,10 +85,11 @@ (clear-state [state] (let [id (get-in state [:workspace :current])] (update-in state [:workspace id] dissoc :selrect)))] - (reify + (ptk/reify ::handle-selrect ptk/WatchEvent (watch [_ state stream] - (let [stoper (rx/filter #(or (dw/interrupt? %) (uws/mouse-up? %)) stream)] + (let [stoper (->> (rx/filter #(or (dw/interrupt? %) (uws/mouse-up? %)) stream) + (rx/pr-log "handle-selrect|stoper:"))] (rx/concat (rx/of (dw/deselect-all)) (->> uws/mouse-position @@ -118,7 +119,7 @@ cy (.-scrollTop dom)] (set! (.-scrollLeft dom) (- cx x)) (set! (.-scrollTop dom) (- cy y))))] - (reify + (ptk/reify ::handle-viewport-positioning ptk/EffectEvent (effect [_ state stream] (let [stoper (rx/filter #(= ::finish-positioning %) stream) @@ -145,7 +146,7 @@ (when (not edition) (if drawing-tool (st/emit! (start-drawing drawing-tool)) - (st/emit! :interrupt handle-selrect)))) + (st/emit! handle-selrect)))) (on-context-menu [event] (dom/prevent-default event) @@ -249,8 +250,9 @@ (for [id (reverse (:canvas page))] [:& uus/shape-component {:id id :key id}]) - (for [id (reverse (:shapes page))] - [:& uus/shape-component {:id id :key id}]) + #_(for [id (reverse (:shapes page))] + [:& uus/shape-component {:id id :key id}]) + [:& uus/all-shapes {:page page}] (when (seq (:selected wst)) [:& selection-handlers {:wst wst}]) diff --git a/frontend/src/uxbox/util/messages.cljs b/frontend/src/uxbox/util/messages.cljs index ff2611f5f..f5ff97a5c 100644 --- a/frontend/src/uxbox/util/messages.cljs +++ b/frontend/src/uxbox/util/messages.cljs @@ -56,10 +56,7 @@ (defn show [data] - (reify - ptk/EventType - (type [_] ::show) - + (ptk/reify ::show ptk/UpdateEvent (update [_ state] (let [message (assoc data :state :visible)] diff --git a/frontend/src/uxbox/util/spec.cljs b/frontend/src/uxbox/util/spec.cljs index 6d706668d..8c8cd80f8 100644 --- a/frontend/src/uxbox/util/spec.cljs +++ b/frontend/src/uxbox/util/spec.cljs @@ -54,6 +54,7 @@ (s/def ::inst inst?) (s/def ::keyword keyword?) (s/def ::fn fn?) +(s/def ::set set?) (s/def ::coll coll?) (s/def ::not-empty-string diff --git a/frontend/src/uxbox/worker/align.cljs b/frontend/src/uxbox/worker/align.cljs index b570d1003..d1bf8a83c 100644 --- a/frontend/src/uxbox/worker/align.cljs +++ b/frontend/src/uxbox/worker/align.cljs @@ -23,6 +23,6 @@ [{:keys [sender point] :as message}] (let [point [(:x point) (:y point)] results (kd/nearest tree point 1) - [[x y] d] (first results) + [x y] (ffirst results) result (gpt/point x y)] (impl/reply! sender {:point (gpt/point x y)})))