🎉 Add option to save the layouts

This commit is contained in:
alonso.torres 2020-05-18 15:30:40 +02:00 committed by Andrés Moya
parent 8d9e772dca
commit 1d2ae6d5eb
17 changed files with 310 additions and 271 deletions

View file

@ -67,7 +67,8 @@
:element-options
:rules
:dynamic-alignment
:layouts})
:display-grid
:snap-grid})
(s/def ::options-mode #{:design :prototype})
@ -1493,13 +1494,35 @@
;; Layouts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defonce 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}}})
(defn add-frame-layout [frame-id]
(ptk/reify ::set-frame-layout
dwc/IBatchedChange
ptk/UpdateEvent
(update [_ state]
(let [pid (:current-page-id state)
default-params {:size 16 :color {:value "#59B9E2" :opacity 0.9}}
default-params (or
(get-in state [:workspace-data pid :options :saved-layouts :square])
(:square default-layout-params))
prop-path [:workspace-data pid :objects frame-id :layouts]
layout {:type :square
:params default-params
@ -1528,14 +1551,13 @@
(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)))))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (dwc/commit-changes [{:type :set-option
:option [:saved-layouts type]
:value params}]
[]
{:commit-local? true})))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exports

View file

@ -42,12 +42,6 @@
(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))
@ -74,6 +68,13 @@
(get-in % [:workspace-data page-id]))
(l/derived st/state)))
(def workspace-page-options
(l/derived :options workspace-data))
(def workspace-saved-layouts
(l/derived :saved-layouts workspace-page-options))
(def workspace-objects
(l/derived :objects workspace-data))

View file

@ -18,10 +18,10 @@
(def ^:private snap-accuracy 5)
(defn- remove-from-snap-points [ids-to-remove]
(defn- remove-from-snap-points [remove-id?]
(fn [query-result]
(->> query-result
(map (fn [[value data]] [value (remove (comp ids-to-remove second) data)]))
(map (fn [[value data]] [value (remove (comp remove-id? second) data)]))
(filter (fn [[_ data]] (not (empty? data)))))))
(defn- flatten-to-points
@ -90,24 +90,32 @@
(defn closest-snap-point
[page-id shapes layout point]
(if (layout :dynamic-alignment)
(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 %))))
(rx/of point)))
(let [frame-id (snap-frame-id shapes)
filter-shapes (into #{} (map :id shapes))
filter-shapes (fn [id] (if (= id :layout)
(or (not (contains? layout :display-grid))
(not (contains? layout :snap-grid)))
(or (filter-shapes id)
(not (contains? layout :dynamic-alignment)))))]
(->> (closest-snap page-id frame-id [point] filter-shapes)
(rx/map #(gpt/add point %))
(rx/map gpt/round))))
(defn closest-snap-move
[page-id shapes layout movev]
(if (layout :dynamic-alignment)
(let [frame-id (snap-frame-id shapes)
filter-shapes (into #{} (map :id shapes))
shapes-points (->> shapes
;; Unroll all the possible snap-points
(mapcat (partial sp/shape-snap-points))
(let [frame-id (snap-frame-id shapes)
filter-shapes (into #{} (map :id shapes))
filter-shapes (fn [id] (if (= id :layout)
(or (not (contains? layout :display-grid))
(not (contains? layout :snap-grid)))
(or (filter-shapes id)
(not (contains? layout :dynamic-alignment)))))
shapes-points (->> shapes
;; Unroll all the possible snap-points
(mapcat (partial sp/shape-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 %))))
(rx/of movev)))
;; 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 %))
(rx/map gpt/round))))

View file

@ -72,42 +72,42 @@
:on-close #(reset! show-menu? false)}
[:ul.menu
[:li {:on-click #(st/emit! (dw/toggle-layout-flag :rules))}
[:span i/ruler]
[:span
(if (contains? layout :rules)
(t locale "workspace.header.menu.hide-rules")
(t locale "workspace.header.menu.show-rules"))]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flag :grid))}
[:span i/grid]
[:li {:on-click #(st/emit! (dw/toggle-layout-flag :display-grid))}
[:span
(if (contains? layout :grid)
(if (contains? layout :display-grid)
(t locale "workspace.header.menu.hide-grid")
(t locale "workspace.header.menu.show-grid"))]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flag :snap-grid))}
[:span
(if (contains? layout :snap-grid)
(t locale "workspace.header.menu.disable-snap-grid")
(t locale "workspace.header.menu.enable-snap-grid"))]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flag :sitemap :layers))}
[:span i/layers]
[:span
(if (or (contains? layout :sitemap) (contains? layout :layers))
(t locale "workspace.header.menu.hide-layers")
(t locale "workspace.header.menu.show-layers"))]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flag :colorpalette))}
[:span i/palette]
[:span
(if (contains? layout :colorpalette)
(t locale "workspace.header.menu.hide-palette")
(t locale "workspace.header.menu.show-palette"))]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flag :libraries))}
[:span i/icon-set]
[:span
(if (contains? layout :libraries)
(t locale "workspace.header.menu.hide-libraries")
(t locale "workspace.header.menu.show-libraries"))]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flag :dynamic-alignment))}
[:span i/shape-halign-left]
[:span
(if (contains? layout :dynamic-alignment)
(t locale "workspace.header.menu.disable-dynamic-alignment")

View file

@ -19,26 +19,27 @@
(let [{:keys [color size] :as params} (-> layout :params)
{color-value :value color-opacity :opacity} (-> layout :params :color)
{frame-width :width frame-height :height :keys [x y]} frame]
[:g.layout
[:*
(for [xs (range size frame-width size)]
[:line {:key (str (:id frame) "-y-" xs)
:x1 (+ x xs)
:y1 y
:x2 (+ x xs)
:y2 (+ y frame-height)
:style {:stroke color-value
:stroke-opacity color-opacity
:stroke-width (str (/ 1 zoom))}}])
(for [ys (range size frame-height size)]
[:line {:key (str (:id frame) "-x-" ys)
:x1 x
:y1 (+ y ys)
:x2 (+ x frame-width)
:y2 (+ y ys)
:style {:stroke color-value
:stroke-opacity color-opacity
:stroke-width (str (/ 1 zoom))}}])]]))
(when (> size 0)
[:g.layout
[:*
(for [xs (range size frame-width size)]
[:line {:key (str (:id frame) "-y-" xs)
:x1 (+ x xs)
:y1 y
:x2 (+ x xs)
:y2 (+ y frame-height)
:style {:stroke color-value
:stroke-opacity color-opacity
:stroke-width (str (/ 1 zoom))}}])
(for [ys (range size frame-height size)]
[:line {:key (str (:id frame) "-x-" ys)
:x1 x
:y1 (+ y ys)
:x2 (+ x frame-width)
:y2 (+ y ys)
:style {:stroke color-value
:stroke-opacity color-opacity
:stroke-width (str (/ 1 zoom))}}])]])))
(mf/defc flex-layout [{:keys [key frame zoom layout]}]
(let [{color-value :value color-opacity :opacity} (-> layout :params :color)]

View file

@ -31,7 +31,7 @@
[:div.advanced-options {}
children]]))
(mf/defc layout-options [{:keys [layout default-layout-params on-change on-remove]}]
(mf/defc layout-options [{:keys [layout default-layout-params on-change on-remove on-save-layout]}]
(let [state (mf/use-state {:show-advanced-options false
:changes {}})
{:keys [type display params] :as layout} (d/deep-merge layout (:changes @state))
@ -68,7 +68,15 @@
(fn [event]
(let [change-fn (apply handle-change keys)]
(-> event dom/get-target dom/get-value parse-integer change-fn))))
]
handle-use-default (fn []
(emit-changes! #(hash-map :params ((:type layout) default-layout-params))))
handle-set-as-default (fn []
(let [current-layout (d/deep-merge layout (-> @state :changes))]
(on-save-layout current-layout)))
is-default (= (->> @state :changes (d/deep-merge layout) :params)
(->> layout :type default-layout-params))]
[:div.grid-option
[:div.grid-option-main
@ -85,7 +93,7 @@
(if (= type :square)
[:div.input-element.pixels
[:input.input-text {:type "number"
:min "0"
:min "1"
:no-validate true
:value (:size params)
:on-change (handle-change-event :params :size)}]]
@ -102,6 +110,8 @@
:on-close toggle-advanced-options}
(when (= :square type)
[:& input-row {:label "Size"
:class "pixels"
:min 1
:value (:size params)
:on-change (handle-change :params :size)}])
@ -128,52 +138,38 @@
(when (= :row type)
[:& input-row {:label "Height"
:class "pixels"
:value (or (:item-height params) "")
:on-change (handle-change :params :item-height)}])
(when (= :column type)
[:& input-row {:label "Width"
:class "pixels"
:value (or (:item-width params) "")
:on-change (handle-change :params :item-width)}])
(when (#{:row :column} type)
[:*
[:& input-row {:label "Gutter"
:class "pixels"
:value (:gutter params)
:on-change (handle-change :params :gutter)}]
[:& input-row {:label "Margin"
:class "pixels"
:value (:margin params)
:on-change (handle-change :params :margin)}]])
[:& color-row {:value (:color params)
:on-change (handle-change :params :color)}]
[:div.row-flex
[: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}}})
[:button.btn-options {:disabled is-default
:on-click handle-use-default} "Use default"]
[:button.btn-options {:disabled is-default
:on-click handle-set-as-default} "Set as default"]]]]))
(mf/defc frame-layouts [{:keys [shape]}]
(let [id (:id shape)
default-layout-params (merge default-layout-params (mf/deref refs/workspace-saved-layouts))
default-layout-params (merge dw/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 %)))

View file

@ -10,95 +10,14 @@
(ns uxbox.main.ui.workspace.sidebar.options.page
"Page options menu entries."
(:require
[cuerdas.core :as str]
[rumext.alpha :as mf]
[okulary.core :as l]
[uxbox.common.data :as d]
[uxbox.main.ui.icons :as i]
[uxbox.main.data.workspace :as dw]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.modal :as modal]
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :refer [tr]]))
(def default-options
"Default data for page metadata."
{:grid-x 10
:grid-y 10
:grid-color "#cccccc"})
[uxbox.main.refs :as refs]))
(def options-iref
(l/derived :options refs/workspace-data))
(mf/defc grid-options
{:wrap [mf/memo]}
[props]
(let [options (->> (mf/deref options-iref)
(merge default-options))
on-x-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-integer 0))]
(st/emit! (dw/update-options {:grid-x value}))))
on-y-change
(fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/parse-integer 0))]
(st/emit! (dw/update-options {:grid-y value}))))
change-color
(fn [color]
(st/emit! (dw/update-options {:grid-color color})))
on-color-input-change
(fn [event]
(let [input (dom/get-target event)
value (dom/get-value input)]
(when (dom/valid? input)
(change-color value))))
show-color-picker
(fn [event]
(let [x (.-clientX event)
y (.-clientY event)
props {:x x :y y
:transparent? true
:default "#cccccc"
:attr :grid-color
:on-change change-color}]
(modal/show! colorpicker-modal props)))]
[:div.element-set
[:div.element-set-title (tr "workspace.options.grid-options")]
[:div.element-set-content
[:div.row-flex
[:span.element-set-subtitle (tr "workspace.options.size")]
[:div.input-element.pixels
[:input.input-text {:type "number"
:value (:grid-x options)
:on-change on-x-change}]]
[:div.input-element.pixels
[:input.input-text {:type "number"
:value (:grid-y options)
:on-change on-y-change}]]]
[:div.row-flex.color-data
[:span.element-set-subtitle (tr "workspace.options.color")]
[:span.color-th {:style {:background-color (:grid-color options)}
:on-click show-color-picker}]
[:div.color-info
[:input {:default-value (:grid-color options)
:ref (fn [el]
(when el
(set! (.-value el) (:grid-color options))))
:pattern "^#(?:[0-9a-fA-F]{3}){1,2}$"
:on-change on-color-input-change}]]]]]))
(mf/defc options
[{:keys [page] :as props}]
[:div
[:& grid-options {:page page}]])
;; TODO: Define properties for page
[{:keys [page] :as props}])

View file

@ -39,7 +39,8 @@
(/ 100)))
(mf/defc color-row [{:keys [value on-change]}]
(let [state (mf/use-state value)
(let [value (or value {:value "#FFFFFF" :opacity 1})
state (mf/use-state value)
change-color (fn [color]
(let [update-color (fn [state] (assoc state :value color))]
(swap! state update-color)
@ -65,6 +66,10 @@
string->opacity
change-opacity))]
(mf/use-effect
(mf/deps value)
#(reset! state value))
[:div.row-flex.color-data
[:span.color-th
{:style {:background-color (-> @state :value)}
@ -88,3 +93,4 @@
:value (-> @state :opacity opacity->string)
:step "1"
:on-change handle-opacity-change}]]))

View file

@ -14,17 +14,19 @@
[uxbox.main.ui.components.select :refer [select]]
[uxbox.util.dom :as dom]))
(mf/defc input-row [{:keys [label options value on-change]}]
[:div.row-flex.input-row
[:span.element-set-subtitle label]
[:div.input-element
(if options
[:& select {:default-value value
:class "input-option"
:options options
:on-change on-change}]
[:input.input-text
{:placeholder label
:type "number"
:on-change #(-> % dom/get-target dom/get-value d/parse-integer on-change)
:value value}])]])
(mf/defc input-row [{:keys [label options value class min max on-change]}]
(let [handle-change (fn [value] (when (and (or (not min) (>= value min)) (or (not max) (<= value max)))
(on-change value)))]
[:div.row-flex.input-row
[:span.element-set-subtitle label]
[:div.input-element {:class class}
(if options
[:& select {:default-value value
:class "input-option"
:options options
:on-change on-change}]
[:input.input-text
{:placeholder label
:type "number"
:on-change #(-> % dom/get-target dom/get-value d/parse-integer handle-change)
:value value}])]]))

View file

@ -80,12 +80,17 @@
:point point
:zoom zoom}])]))]))
(mf/defc snap-feedback [{:keys []}]
(mf/defc snap-feedback [{:keys [layout]}]
(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)
filter-shapes (fn [id] (if (= id :layout)
(or (not (contains? layout :display-grid))
(not (contains? layout :snap-grid)))
(or (filter-shapes id)
(not (contains? layout :dynamic-alignment)))))
current-transform (mf/deref refs/current-transform)
snap-data (mf/deref refs/workspace-snap-data)
shapes (if drawing [drawing] selected-shapes)

View file

@ -386,10 +386,10 @@
:zoom zoom
:modifiers (:modifiers local)}])
(when (contains? layout :layouts)
(when (contains? layout :display-grid)
[:& layout-display {:zoom zoom}])
[:& snap-feedback]
[:& snap-feedback {:layout layout}]
(when tooltip
[:& cursor-tooltip {:zoom zoom :tooltip tooltip}])]

View file

@ -74,11 +74,12 @@
(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))))
(when (> size 0)
(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)))

View file

@ -151,11 +151,12 @@
(defn round
"Change the precision of the point coordinates."
[{:keys [x y] :as p} decimanls]
(assert (point? p))
(assert (number? decimanls))
(Point. (mth/precision x decimanls)
(mth/precision y decimanls)))
([point] (round point 0))
([{:keys [x y] :as p} decimanls]
(assert (point? p))
(assert (number? decimanls))
(Point. (mth/precision x decimanls)
(mth/precision y decimanls))))
(defn transform
"Transform a point applying a matrix transfomation."