diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index e40ab994b7..42756c248c 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -575,6 +575,10 @@ height: 1rem; fill: $color-gray-20; } + + &:hover svg, &.is-active svg { + fill: $color-primary; + } } .element-set-content .input-row { diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 55c94cf262..699283f89e 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -66,7 +66,8 @@ :layers :element-options :rules - :dynamic-alignment}) + :dynamic-alignment + :layouts}) (s/def ::options-mode #{:design :prototype}) @@ -1525,6 +1526,17 @@ state (assoc-in [:workspace-data pid :objects frame-id :layouts index] data)))))) +(defn set-default-layout [type params] + (ptk/reify ::set-default-layout + dwc/IBatchedChange + + ;; TODO: Save into the backend + ptk/UpdateEvent + (update [_ state] + (-> + state + (assoc-in [:workspace-page :options :saved-layouts type] params))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Exports ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index f0e7aab192..155e0ad813 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -42,6 +42,12 @@ (def workspace-page (l/derived :workspace-page st/state)) +(def workspace-page-options + (l/derived :options workspace-page)) + +(def workspace-saved-layouts + (l/derived :saved-layouts workspace-page-options)) + (def workspace-page-id (l/derived :id workspace-page)) @@ -71,6 +77,9 @@ (def workspace-objects (l/derived :objects workspace-data)) +(def workspace-frames + (l/derived cp/select-frames workspace-objects)) + (defn object-by-id [id] (letfn [(selector [state] diff --git a/frontend/src/uxbox/main/ui/workspace.cljs b/frontend/src/uxbox/main/ui/workspace.cljs index ac12a0887a..f33c86e769 100644 --- a/frontend/src/uxbox/main/ui/workspace.cljs +++ b/frontend/src/uxbox/main/ui/workspace.cljs @@ -73,7 +73,8 @@ [:& viewport {:page page :key (:id page) :file file - :local local}]]] + :local local + :layout layout}]]] [:& left-toolbar {:page page :layout layout}] diff --git a/frontend/src/uxbox/main/ui/workspace/layout_display.cljs b/frontend/src/uxbox/main/ui/workspace/layout_display.cljs index 9702b0e1ec..f04918b480 100644 --- a/frontend/src/uxbox/main/ui/workspace/layout_display.cljs +++ b/frontend/src/uxbox/main/ui/workspace/layout_display.cljs @@ -11,13 +11,15 @@ (:require [rumext.alpha :as mf] [uxbox.main.refs :as refs] + [uxbox.common.pages :as cp] + [uxbox.util.geom.shapes :as gsh] [uxbox.util.geom.layout :as ula])) (mf/defc grid-layout [{:keys [frame zoom layout] :as props}] (let [{:keys [color size] :as params} (-> layout :params) - {color-value :value color-opacity :opacity} color + {color-value :value color-opacity :opacity} (-> layout :params :color) {frame-width :width frame-height :height :keys [x y]} frame] - [:g.grid + [:g.layout [:* (for [xs (range size frame-width size)] [:line {:key (str (:id frame) "-y-" xs) @@ -38,20 +40,20 @@ :stroke-opacity color-opacity :stroke-width (str (/ 1 zoom))}}])]])) -(mf/defc flex-layout [{:keys [frame zoom layout]}] +(mf/defc flex-layout [{:keys [key frame zoom layout]}] (let [{color-value :value color-opacity :opacity} (-> layout :params :color)] - (for [{:keys [x y width height]} (ula/layout-rects frame layout)] - [:rect {:x x - :y y - :width width - :height height - :style {:pointer-events "none" - :fill color-value - :opacity color-opacity}}]))) + [:g.layout + (for [{:keys [x y width height]} (ula/layout-rects frame layout)] + [:rect {:key (str key "-" x "-" y) + :x x + :y y + :width width + :height height + :style {:fill color-value + :opacity color-opacity}}])])) -(mf/defc layout-display [{:keys [frame]}] - (let [zoom (mf/deref refs/selected-zoom) - layouts (:layouts frame)] +(mf/defc layout-display-frame [{:keys [frame zoom]}] + (let [layouts (:layouts frame)] (for [[index {:keys [type display] :as layout}] (map-indexed vector layouts)] (let [props #js {:key (str (:id frame) "-layout-" index) :frame frame @@ -62,3 +64,12 @@ :square [:> grid-layout props] :column [:> flex-layout props] :row [:> flex-layout props])))))) + + +(mf/defc layout-display [{:keys [zoom]}] + (let [frames (mf/deref refs/workspace-frames)] + [:g.layout-display {:style {:pointer-events "none"}} + (for [frame frames] + [:& layout-display-frame {:key (str "layout-" (:id frame)) + :zoom zoom + :frame (gsh/transform-shape frame)}])])) diff --git a/frontend/src/uxbox/main/ui/workspace/ruler.cljs b/frontend/src/uxbox/main/ui/workspace/ruler.cljs deleted file mode 100644 index e4ec96db03..0000000000 --- a/frontend/src/uxbox/main/ui/workspace/ruler.cljs +++ /dev/null @@ -1,79 +0,0 @@ -;; 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-2017 Juan de la Cruz -;; Copyright (c) 2015-2019 Andrey Antukh - -(ns uxbox.main.ui.workspace.ruler - (:require - [rumext.alpha :as mf] - [uxbox.main.constants :as c] - [uxbox.main.data.workspace :as udw] - [uxbox.main.store :as st] - [uxbox.util.dom :as dom] - [uxbox.util.geom.point :as gpt] - [uxbox.util.math :as mth])) - -(mf/defc ruler-text - [{:keys [zoom ruler] :as props}] - #_(let [{:keys [start end]} ruler - distance (-> (gpt/distance (gpt/divide end zoom) - (gpt/divide start zoom)) - (mth/precision 2)) - angle (-> (gpt/angle end start) - (mth/precision 2)) - transform1 (str "translate(" (+ (:x end) 35) "," (- (:y end) 10) ")") - transform2 (str "translate(" (+ (:x end) 25) "," (- (:y end) 30) ")")] - [:g - [:rect {:fill "black" - :fill-opacity "0.4" - :rx "3" - :ry "3" - :width "90" - :height "50" - :transform transform2}] - [:text {:transform transform1 - :fill "white"} - [:tspan {:x "0"} - (str distance " px")] - [:tspan {:x "0" :y "20"} - (str angle "°")]]])) - -(mf/defc ruler-line - [{:keys [zoom ruler] :as props}] - #_(let [{:keys [start end]} ruler] - [:line {:x1 (:x start) - :y1 (:y start) - :x2 (:x end) - :y2 (:y end) - :style {:cursor "cell"} - :stroke-width "1" - :stroke "red"}])) - -(mf/defc ruler - [{:keys [ruler zoom] :as props}] - #_(letfn [(on-mouse-down [event] - (dom/stop-propagation event) - (st/emit! :interrupt - (udw/assign-cursor-tooltip nil) - (udw/start-ruler))) - (on-mouse-up [event] - (dom/stop-propagation event) - (st/emit! :interrupt)) - (on-unmount [] - (st/emit! :interrupt - (udw/clear-ruler)))] - (mf/use-effect (constantly on-unmount)) - [:svg {:on-mouse-down on-mouse-down - :on-mouse-up on-mouse-up} - [:rect {:style {:fill "transparent" - :stroke "transparent" - :cursor "cell"} - :width c/viewport-width - :height c/viewport-height}] - (when ruler - [:g - [:& ruler-line {:ruler ruler}] - [:& ruler-text {:ruler ruler :zoom zoom}]])])) - diff --git a/frontend/src/uxbox/main/ui/workspace/shapes/frame.cljs b/frontend/src/uxbox/main/ui/workspace/shapes/frame.cljs index 1cd176ea77..28cadea2c0 100644 --- a/frontend/src/uxbox/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/uxbox/main/ui/workspace/shapes/frame.cljs @@ -22,8 +22,7 @@ [uxbox.util.geom.shapes :as geom] [uxbox.util.dom :as dom] [uxbox.main.streams :as ms] - [uxbox.util.timers :as ts] - [uxbox.main.ui.workspace.layout-display :refer [layout-display]])) + [uxbox.util.timers :as ts])) (defn- frame-wrapper-factory-equals? [np op] @@ -105,6 +104,5 @@ (:name shape)] [:& frame-shape {:shape shape - :childs childs}] - [:& layout-display {:frame shape}]]))))) + :childs childs}]]))))) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs index 9f8ea3125b..8c3b772a1a 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/options/frame_layouts.cljs @@ -12,7 +12,9 @@ [rumext.alpha :as mf] [uxbox.util.dom :as dom] [uxbox.util.data :as d] + [uxbox.common.data :refer [parse-integer]] [uxbox.main.store :as st] + [uxbox.main.refs :as refs] [uxbox.main.data.workspace :as dw] [uxbox.main.ui.icons :as i] [uxbox.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] @@ -29,27 +31,8 @@ [:div.advanced-options {} children]])) -(defonce ^:private default-params - {:square {:size 16 - :color {:value "#59B9E2" - :opacity 0.9}} - - :column {:size 12 - :type :stretch - :item-width nil - :gutter 8 - :margin 0 - :color {:value "#DE4762" - :opacity 0.1}} - :row {:size 12 - :type :stretch - :item-height nil - :gutter 8 - :margin 0 - :color {:value "#DE4762" - :opacity 0.1}}}) - -(mf/defc layout-options [{:keys [layout on-change on-remove]}] +(mf/defc layout-options [{:keys [layout default-layout-params on-change on-remove]}] + (prn "(render) layout" layout) (let [state (mf/use-state {:show-advanced-options false :changes {}}) {:keys [type display params] :as layout} (d/deep-merge layout (:changes @state)) @@ -61,17 +44,18 @@ 18 12 10 8 6 4 3 2] emit-changes! (fn [update-fn] - (swap! state update :changes update-fn) - (when on-change (on-change (d/deep-merge layout (-> @state :changes update-fn))))) + (swap! state update :changes update-fn) + (prn "(event) layout" (d/deep-merge layout (-> @state :changes update-fn))) + (when on-change (on-change (d/deep-merge layout (-> @state :changes update-fn))))) handle-toggle-visibility (fn [event] - (emit-changes! #(update % :display not))) + (emit-changes! (fn [changes] (update changes :display #(if (nil? %) false (not %)))))) handle-remove-layout (fn [event] (when on-remove (on-remove))) handle-change-type (fn [type] - (let [defaults (type default-params) + (let [defaults (type default-layout-params) params (merge defaults (select-keys (keys defaults) (-> @state :changes params))) @@ -85,7 +69,7 @@ handle-change-event (fn [& keys] (fn [event] (let [change-fn (apply handle-change keys)] - (-> event dom/get-target dom/get-value change-fn)))) + (-> event dom/get-target dom/get-value parse-integer change-fn)))) ] [:div.grid-option @@ -169,20 +153,45 @@ [:button.btn-options "Use default"] [:button.btn-options "Set as default"]]]])) +(defonce ^:private default-layout-params + {:square {:size 16 + :color {:value "#59B9E2" + :opacity 0.9}} + + :column {:size 12 + :type :stretch + :item-width nil + :gutter 8 + :margin 0 + :color {:value "#DE4762" + :opacity 0.1}} + :row {:size 12 + :type :stretch + :item-height nil + :gutter 8 + :margin 0 + :color {:value "#DE4762" + :opacity 0.1}}}) + (mf/defc frame-layouts [{:keys [shape]}] (let [id (:id shape) + default-layout-params (merge default-layout-params (mf/deref refs/workspace-saved-layouts)) handle-create-layout #(st/emit! (dw/add-frame-layout id)) handle-remove-layout (fn [index] #(st/emit! (dw/remove-frame-layout id index))) - handle-edit-layout (fn [index] #(st/emit! (dw/set-frame-layout id index %)))] + handle-edit-layout (fn [index] #(st/emit! (dw/set-frame-layout id index %))) + handle-save-layout (fn [layout] (st/emit! (dw/set-default-layout (:type layout) (:params layout))))] [:div.element-set [:div.element-set-title [:span "Grid & Layout"] [:div.add-page {:on-click handle-create-layout} i/close]] - [:div.element-set-content - (for [[index layout] (map-indexed vector (:layouts shape))] - [:& layout-options {:key (str (:id shape) "-" index) - :layout layout - :on-change (handle-edit-layout index) - :on-remove (handle-remove-layout index)}])]])) + (when (not (empty? (:layouts shape))) + [:div.element-set-content + (for [[index layout] (map-indexed vector (:layouts shape))] + [:& layout-options {:key (str (:id shape) "-" index) + :layout layout + :default-layout-params default-layout-params + :on-change (handle-edit-layout index) + :on-remove (handle-remove-layout index) + :on-save-layout handle-save-layout}])])])) diff --git a/frontend/src/uxbox/main/ui/workspace/viewport.cljs b/frontend/src/uxbox/main/ui/workspace/viewport.cljs index 4baa5c6fc1..9819ea115c 100644 --- a/frontend/src/uxbox/main/ui/workspace/viewport.cljs +++ b/frontend/src/uxbox/main/ui/workspace/viewport.cljs @@ -26,11 +26,10 @@ [uxbox.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]] [uxbox.main.ui.workspace.shapes.interactions :refer [interactions]] [uxbox.main.ui.workspace.drawarea :refer [draw-area start-drawing]] - [uxbox.main.ui.workspace.grid :refer [grid]] - [uxbox.main.ui.workspace.ruler :refer [ruler]] [uxbox.main.ui.workspace.selection :refer [selection-handlers]] [uxbox.main.ui.workspace.presence :as presence] [uxbox.main.ui.workspace.snap-feedback :refer [snap-feedback]] + [uxbox.main.ui.workspace.layout-display :refer [layout-display]] [uxbox.util.math :as mth] [uxbox.util.dom :as dom] [uxbox.util.object :as obj] @@ -127,7 +126,7 @@ :key (:id item)}]))])) (mf/defc viewport - [{:keys [page local] :as props}] + [{:keys [page local layout] :as props}] (let [{:keys [drawing-tool options-mode zoom @@ -354,6 +353,7 @@ ] (mf/use-effect on-mount) + [:svg.viewport {:preserveAspectRatio "xMidYMid meet" :width (:width vport 0) @@ -381,22 +381,18 @@ :zoom zoom :edition edition}]) - (when-let [drawing-shape (:drawing local)] [:& draw-area {:shape drawing-shape :zoom zoom :modifiers (:modifiers local)}]) + (when (contains? layout :layouts) + [:& layout-display {:zoom zoom}]) + [:& snap-feedback] - (when (contains? flags :grid) - [:& grid])] - - (when tooltip - [:& cursor-tooltip {:zoom zoom :tooltip tooltip}]) - - (when (contains? flags :ruler) - [:& ruler {:zoom zoom :ruler (:ruler local)}]) + (when tooltip + [:& cursor-tooltip {:zoom zoom :tooltip tooltip}])] [:& presence/active-cursors {:page page}] [:& selection-rect {:data (:selrect local)}] diff --git a/frontend/src/uxbox/util/geom/layout.cljs b/frontend/src/uxbox/util/geom/layout.cljs index 164edea37d..0e6c232757 100644 --- a/frontend/src/uxbox/util/geom/layout.cljs +++ b/frontend/src/uxbox/util/geom/layout.cljs @@ -7,7 +7,9 @@ ;; ;; Copyright (c) 2020 UXBOX Labs SL -(ns uxbox.util.geom.layout) +(ns uxbox.util.geom.layout + (:require + [uxbox.util.geom.point :as gpt])) (defn calculate-column-layout [{:keys [width height x y] :as frame} {:keys [size gutter margin item-width type] :as params}] (let [parts (/ width size) @@ -20,7 +22,7 @@ gutter (if (= :stretch type) (/ (- width (* item-width size) (* margin 2)) (dec size)) gutter) next-x (fn [cur-val] (+ initial-offset x (* (+ item-width gutter) cur-val))) next-y (fn [cur-val] y)] - [item-width item-height next-x next-y])) + [size item-width item-height next-x next-y])) (defn calculate-row-layout [{:keys [width height x y] :as frame} {:keys [size gutter margin item-height type] :as params}] (let [{:keys [width height x y]} frame @@ -34,16 +36,51 @@ gutter (if (= :stretch type) (/ (- height (* item-height size) (* margin 2)) (dec size)) gutter) next-x (fn [cur-val] x) next-y (fn [cur-val] (+ initial-offset y (* (+ item-height gutter) cur-val)))] - [item-width item-height next-x next-y])) + [size item-width item-height next-x next-y])) + +(defn calculate-grid-layout [{:keys [width height x y] :as frame} {:keys [size] :as params}] + (let [col-size (quot width size) + row-size (quot height size) + as-row-col (fn [value] [(quot value col-size) (rem value col-size)]) + next-x (fn [cur-val] + (let [[_ col] (as-row-col cur-val)] (+ x (* col size)))) + next-y (fn [cur-val] + (let [[row _] (as-row-col cur-val)] (+ y (* row size))))] + [(* col-size row-size) size size next-x next-y])) (defn layout-rects [frame layout] - (let [[item-width item-height next-x next-y] - (case (-> layout :type) - :column (calculate-column-layout frame (-> layout :params)) - :row (calculate-row-layout frame (-> layout :params)))] + (let [layout-fn (case (-> layout :type) + :column calculate-column-layout + :row calculate-row-layout + :square calculate-grid-layout) + [num-items item-width item-height next-x next-y] (layout-fn frame (-> layout :params))] (->> - (range 0 (-> layout :params :size)) + (range 0 num-items) (map #(hash-map :x (next-x %) :y (next-y %) :width item-width :height item-height))))) + +(defn- layout-rect-points [{:keys [x y width height]}] + [(gpt/point x y) + (gpt/point (+ x width) y) + (gpt/point (+ x width) (+ y height)) + (gpt/point x (+ y height))]) + +(defn- layout-snap-points + ([shape coord] (mapcat #(layout-snap-points shape % coord) (:layouts shape))) + ([shape {:keys [type display params] :as layout} coord] + + (case type + :square (let [{:keys [x y width height]} shape + size (-> params :size)] + (if (= coord :x) + (mapcat #(vector (gpt/point (+ x %) y) + (gpt/point (+ x %) (+ y height))) (range size width size)) + (mapcat #(vector (gpt/point x (+ y %)) + (gpt/point (+ x width) (+ y %))) (range size height size)))) + :column (when (= coord :x) (->> (layout-rects shape layout) + (mapcat layout-rect-points))) + + :row (when (= coord :y) (->> (layout-rects shape layout) + (mapcat layout-rect-points)))))) diff --git a/frontend/src/uxbox/util/geom/snap_points.cljs b/frontend/src/uxbox/util/geom/snap_points.cljs index 609b06cc95..6adbf3dfce 100644 --- a/frontend/src/uxbox/util/geom/snap_points.cljs +++ b/frontend/src/uxbox/util/geom/snap_points.cljs @@ -12,17 +12,7 @@ [cljs.spec.alpha :as s] [clojure.set :as set] [uxbox.util.geom.shapes :as gsh] - [uxbox.util.geom.point :as gpt] - [uxbox.util.geom.layout :as gla])) - -(defn- layout-rect-snaps [{:keys [x y width height]}] - #{(gpt/point x y) - (gpt/point (+ x width) y) - (gpt/point (+ x width) (+ y height)) - (gpt/point x (+ y height))}) - -(defn- layout-snap-points [frame {:keys [type] :as layout}] - (mapcat layout-rect-snaps (gla/layout-rects frame layout))) + [uxbox.util.geom.point :as gpt])) (defn- frame-snap-points [{:keys [x y width height layouts] :as frame}] (into #{(gpt/point x y) @@ -32,11 +22,7 @@ (gpt/point (+ x width) (+ y height)) (gpt/point (+ x (/ width 2)) (+ y height)) (gpt/point x (+ y height)) - (gpt/point x (+ y (/ height 2)))} - (->> - layouts - (filter #(and (not= :grid (:type %)) (:display %))) - (mapcat #(layout-snap-points frame %))))) + (gpt/point x (+ y (/ height 2)))})) (defn shape-snap-points [shape] diff --git a/frontend/src/uxbox/worker/snaps.cljs b/frontend/src/uxbox/worker/snaps.cljs index b1f7c70e1f..e14883860b 100644 --- a/frontend/src/uxbox/worker/snaps.cljs +++ b/frontend/src/uxbox/worker/snaps.cljs @@ -14,7 +14,8 @@ [uxbox.common.pages :as cp] [uxbox.worker.impl :as impl] [uxbox.util.range-tree :as rt] - [uxbox.util.geom.snap-points :as snap])) + [uxbox.util.geom.snap-points :as snap] + [uxbox.util.geom.layout :as gla])) (defonce state (l/atom {})) @@ -23,8 +24,11 @@ [shapes coord] (let [process-shape (fn [coord] (fn [shape] - (let [points (snap/shape-snap-points shape)] - (map #(vector % (:id shape)) points)))) + (concat + (let [points (snap/shape-snap-points shape)] + (map #(vector % (:id shape)) points)) + (let [points (gla/layout-snap-points shape coord)] + (map #(vector % :layout) points))))) into-tree (fn [tree [point _ :as data]] (rt/insert tree (coord point) data))] (->> shapes