diff --git a/frontend/uxbox/ui/workspace/base.cljs b/frontend/uxbox/ui/workspace/base.cljs index 66a1a4c52..213713eb7 100644 --- a/frontend/uxbox/ui/workspace/base.cljs +++ b/frontend/uxbox/ui/workspace/base.cljs @@ -2,7 +2,7 @@ (:require [beicon.core :as rx] [cats.labs.lens :as l] [uxbox.rstore :as rs] - [uxbox.state :as s] + [uxbox.state :as st] [uxbox.data.projects :as dp] [uxbox.data.workspace :as dw] [uxbox.util.lens :as ul] @@ -16,32 +16,20 @@ (def ^:static project-state (as-> (ul/dep-in [:projects-by-id] [:workspace :project]) $ - (l/focus-atom $ s/state))) + (l/focus-atom $ st/state))) (def ^:static page-state (as-> (ul/dep-in [:pages-by-id] [:workspace :page]) $ - (l/focus-atom $ s/state))) + (l/focus-atom $ st/state))) (def ^:static pages-state (as-> (ul/getter #(let [pid (get-in % [:workspace :project])] (dp/project-pages % pid))) $ - (l/focus-atom $ s/state))) - -(def ^:static shapes-state - (as-> (ul/getter (fn [state] - (let [pid (get-in state [:workspace :page]) - shapes (->> (vals (:shapes-by-id state)) - (filter #(= (:page %) pid)))] - (into [] shapes)))) $ - (l/focus-atom $ s/state))) + (l/focus-atom $ st/state))) (def ^:static workspace-state (as-> (l/in [:workspace]) $ - (l/focus-atom $ s/state))) - -(def ^:static selected-state - (as-> (l/in [:workspace :selected]) $ - (l/focus-atom $ s/state))) + (l/focus-atom $ st/state))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Scroll Stream @@ -68,12 +56,14 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defonce shapes-dragging? (atom false)) - (defonce selrect-dragging? (atom false)) (defonce selrect-pos (atom nil)) (defonce mouse-b (rx/bus)) -(defonce mouse-s (rx/dedupe mouse-b)) +(defonce mouse-s + (->> mouse-b + (rx/filter #(= (:id %) (:id @page-state))) + (rx/map :coords))) ;; Deltas @@ -94,8 +84,11 @@ (as-> mouse-delta-s $ (rx/filter #(deref shapes-dragging?) $) (rx/on-value $ (fn [delta] - (doseq [sid @selected-state] - (rs/emit! (dw/move-shape sid delta))))))) + (let [pageid (get-in @st/state [:workspace :page]) + selected (get-in @st/state [:workspace :selected]) + page (get-in @st/state [:pages-by-id pageid])] + (doseq [sid (filter selected (:shapes page))] + (rs/emit! (dw/move-shape sid delta)))))))) (defn selrect->rect [data] @@ -140,38 +133,12 @@ ;; Materialized views -(defonce mouse-position (rx/to-atom (rx/sample 50 mouse-s))) +(defonce mouse-position + (->> mouse-s + (rx/sample 50) + (rx/to-atom))) -(defn- mouse-mixin-did-mount - [own] - (letfn [(on-mousemove [event] - (let [canvas (util/get-ref-dom own "canvas") - brect (.getBoundingClientRect canvas) - offset-x (.-left brect) - offset-y (.-top brect) - x (.-clientX event) - y (.-clientY event)] - (rx/push! mouse-b [(- x offset-x) - (- y offset-y)])))] - (let [key (events/listen js/document EventType.MOUSEMOVE on-mousemove)] - (js/console.log "mouse-mixin-did-mount" key) - (assoc own ::eventkey key)))) - -(defn- mouse-mixin-will-unmount - [own] - (let [key (::eventkey own)] - (events/unlistenByKey key) - (dissoc own ::eventkey))) - -(defn- mouse-mixin-transfer-state - [old-own own] - (let [key (::eventkey old-own)] - (assoc own ::eventkey key))) - -(def ^:static mouse-mixin - {:did-mount mouse-mixin-did-mount - :will-unmount mouse-mixin-will-unmount - :transfer-state mouse-mixin-transfer-state}) +(defonce bounding-rect (atom {})) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Constants diff --git a/frontend/uxbox/ui/workspace/canvas.cljs b/frontend/uxbox/ui/workspace/canvas.cljs index 478f6ad53..6c5ceb982 100644 --- a/frontend/uxbox/ui/workspace/canvas.cljs +++ b/frontend/uxbox/ui/workspace/canvas.cljs @@ -2,10 +2,13 @@ (:require [sablono.core :as html :refer-macros [html]] [rum.core :as rum] [beicon.core :as rx] + [cats.labs.lens :as l] + [goog.events :as events] [uxbox.router :as r] [uxbox.rstore :as rs] - [uxbox.state :as s] + [uxbox.state :as st] [uxbox.shapes :as shapes] + [uxbox.util.lens :as ul] [uxbox.library.icons :as _icons] [uxbox.data.projects :as dp] [uxbox.data.workspace :as dw] @@ -14,7 +17,17 @@ [uxbox.ui.util :as util] [uxbox.ui.workspace.base :as wb] [uxbox.ui.workspace.grid :refer (grid)] - [uxbox.ui.workspace.toolboxes :as toolboxes])) + [uxbox.ui.workspace.options :refer (element-opts)] + [uxbox.ui.workspace.toolboxes :as toolboxes]) + (:import goog.events.EventType)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Lenses +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^:static shapes-by-id + (as-> (l/key :shapes-by-id) $ + (l/focus-atom $ st/state))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Background @@ -33,7 +46,28 @@ :mixins [mx/static]})) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Shape +;; Select Rect +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn selrect-render + [own] + (when-let [data (rum/react wb/selrect-pos)] + (let [{:keys [x y width height]} (wb/selrect->rect data)] + (html + [:rect.selection-rect + {:x x + :y y + :width width + :height height}])))) + +(def ^:static selrect + (util/component + {:render selrect-render + :name "selrect" + :mixins [mx/static rum/reactive]})) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Selected shapes group ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private @@ -48,10 +82,56 @@ :fill "lavender" :stroke "gray"}) +(defn- selected-shapes-render + [own shapes selected] + (let [local (:rum/local own) + x (apply min (map :x shapes)) + y (apply min (map :y shapes)) + x' (apply max (map (fn [{:keys [x width]}] (+ x width)) shapes)) + y' (apply max (map (fn [{:keys [y height]}] (+ y height)) shapes)) + width (- x' x) + height (- y' y)] + (letfn [(on-mouse-down [event] + (dom/stop-propagation event) + (swap! local assoc :init-coords [x y]) + (reset! wb/shapes-dragging? true)) + (on-mouse-up [event] + (dom/stop-propagation event) + (reset! wb/shapes-dragging? false))] + (html + [:g {:class "shape selected" + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up} + (for [item shapes] + (shapes/render item)) + [:g.controls + [:rect {:x x :y y :width width :height height + :style {:stroke "black" :fill "transparent" + :stroke-opacity "0.5"}}] + [:circle.top-left (merge default-selection-props + {:cx x :cy y})] + [:circle.top-right (merge default-selection-props + {:cx (+ x width) :cy y})] + [:circle.bottom-left (merge default-selection-props + {:cx x :cy (+ y height)})] + [:circle.bottom-right (merge default-selection-props + {:cx (+ x width) :cy (+ y height)})]]])))) + +(def selected-shapes + (util/component + {:render selected-shapes-render + :name "selected-shapes" + :mixins [mx/static rum/reactive (mx/local {})]})) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Shape +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn shape-render - [own {:keys [id x y width height] :as shape}] - (let [selected (rum/react wb/selected-state) + [own shape selected] + (let [{:keys [id x y width height] :as shape} shape selected? (contains? selected id)] + (println "shape-render" id) (letfn [(on-mouse-down [event] (let [local (:rum/local own)] (dom/stop-propagation event) @@ -94,88 +174,55 @@ (util/component {:render shape-render :name "shape" - :mixins [mx/static rum/reactive (mx/local {})]})) - -(defn- selected-shapes-render - [own shapes] - (let [selected (rum/react wb/selected-state) - local (:rum/local own) - x (apply min (map :x shapes)) - y (apply min (map :y shapes)) - x' (apply max (map (fn [{:keys [x width]}] (+ x width)) shapes)) - y' (apply max (map (fn [{:keys [y height]}] (+ y height)) shapes)) - width (- x' x) - height (- y' y)] - (letfn [(on-mouse-down [event] - (dom/stop-propagation event) - (swap! local assoc :init-coords [x y]) - (reset! wb/shapes-dragging? true)) - (on-mouse-up [event] - (dom/stop-propagation event) - (reset! wb/shapes-dragging? false))] - (html - [:g {:class "shape selected" - :on-mouse-down on-mouse-down - :on-mouse-up on-mouse-up} - (for [item shapes] - (shapes/render item)) - [:g.controls - [:rect {:x x :y y :width width :height height - :style {:stroke "black" :fill "transparent" - :stroke-opacity "0.5"}}] - [:circle.top-left (merge default-selection-props - {:cx x :cy y})] - [:circle.top-right (merge default-selection-props - {:cx (+ x width) :cy y})] - [:circle.bottom-left (merge default-selection-props - {:cx x :cy (+ y height)})] - [:circle.bottom-right (merge default-selection-props - {:cx (+ x width) :cy (+ y height)})]]])))) - -(def selected-shapes - (util/component - {:render selected-shapes-render - :name "selected-shapes" - :mixins [mx/static rum/reactive (mx/local {})]})) - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Select Rect -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn selrect-render - [own] - (when-let [data (rum/react wb/selrect-pos)] - (let [{:keys [x y width height]} (wb/selrect->rect data)] - (html - [:rect.selection-rect - {:x x - :y y - :width width - :height height}])))) - -(def ^:static selrect - (util/component - {:render selrect-render - :name "selrect" - :mixins [mx/static rum/reactive]})) + :mixins [mx/static (mx/local {})]})) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Canvas ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn canvas-render +(defn- canvas-did-mount [own] - (let [page (rum/react wb/page-state) - shapes (rum/react wb/shapes-state) - selected-ids (rum/react wb/selected-state) - selected (filter (comp selected-ids :id) shapes) - nonselected (filter (comp not selected-ids :id) shapes) - page-width (:width page) - page-height (:height page)] + (letfn [(on-mousemove [event page [offset-x offset-y]] + (let [x (.-clientX event) + y (.-clientY event) + event {:id (:id page) + :coords [(- x offset-x) + (- y offset-y)]}] + (rx/push! wb/mouse-b event)))] + (let [[page] (:rum/props own) + canvas (util/get-ref-dom own (str "canvas" (:id page))) + brect (.getBoundingClientRect canvas) + brect [(.-left brect) (.-top brect)] + key (events/listen js/document EventType.MOUSEMOVE + #(on-mousemove % page brect))] + (swap! wb/bounding-rect assoc (:id page) brect) + (assoc own ::eventkey key)))) + +(defn- canvas-will-unmount + [own] + (let [key (::eventkey own) + [page] (:rum/props own)] + (swap! wb/bounding-rect dissoc (:id page)) + (events/unlistenByKey key) + (dissoc own ::eventkey))) + +(defn- canvas-transfer-state + [old-own own] + (let [key (::eventkey old-own)] + (assoc own ::eventkey key))) + +(defn- canvas-render + [own {:keys [width height id] :as page}] + (println "canvas-render" id) + (let [workspace (rum/react wb/workspace-state) + shapes-by-id (rum/react shapes-by-id) + workspace-selected (:selected workspace) + shapes (map #(get shapes-by-id %) (:shapes page)) + shapes-selected (filter (comp workspace-selected :id) shapes) + shapes-notselected (filter (comp not workspace-selected :id) shapes)] (letfn [(on-mouse-down [event] (dom/stop-propagation event) - (when-not (empty? selected-ids) + (when-not (empty? shapes-selected) (rs/emit! (dw/deselect-all))) (reset! wb/selrect-dragging? true)) (on-mouse-up [event] @@ -183,30 +230,34 @@ (reset! wb/shapes-dragging? false) (reset! wb/selrect-dragging? false))] (html - [:svg.page-canvas {:x wb/document-start-x - :y wb/document-start-y - :ref "canvas" - :width page-width - :height page-height - :on-mouse-down on-mouse-down - :on-mouse-up on-mouse-up} + [:svg#page-canvas.page-canvas {:x wb/document-start-x + :y wb/document-start-y + :ref (str "canvas" id) + :width width + :height height + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up} (background) (grid 1) [:svg.page-layout {} - (for [item nonselected] - (rum/with-key (shape item) (str (:id item)))) + (for [item shapes-notselected + :let [component (shape item workspace-selected)]] + (rum/with-key component (str (:id item)))) + (cond - (= (count selected) 1) - (shape (first selected)) - - (> (count selected) 1) - (selected-shapes selected)) + (= (count shapes-selected) 1) + (let [item (first shapes-selected)] + (shape item workspace-selected)) + (> (count shapes-selected) 1) + (selected-shapes shapes-selected)) (selrect)]])))) (def canvas (util/component {:render canvas-render + :did-mount canvas-did-mount + :will-unmount canvas-will-unmount + :transfer-state canvas-transfer-state :name "canvas" - :mixins [rum/reactive wb/mouse-mixin]})) - + :mixins [mx/static rum/reactive]})) diff --git a/frontend/uxbox/ui/workspace/workarea.cljs b/frontend/uxbox/ui/workspace/workarea.cljs index 0b7fba68b..776bbe567 100644 --- a/frontend/uxbox/ui/workspace/workarea.cljs +++ b/frontend/uxbox/ui/workspace/workarea.cljs @@ -33,6 +33,7 @@ (defn viewport-render [own] (let [workspace (rum/react wb/workspace-state) + page (rum/react wb/page-state) drawing? (:drawing workspace) zoom 1] (html @@ -41,8 +42,8 @@ :on-click #(on-click % workspace) :class (when drawing? "drawing")} [:g.zoom {:transform (str "scale(" zoom ", " zoom ")")} - (canvas) - #_(grid zoom)]]))) + (if page + (canvas page))]]))) (def viewport (util/component