mirror of
https://github.com/penpot/penpot.git
synced 2025-04-14 16:01:24 -05:00
🎉 Grid and layout UI
This commit is contained in:
parent
8c77ea463d
commit
0b4996b31a
16 changed files with 700 additions and 22 deletions
|
@ -9,7 +9,9 @@
|
|||
(:refer-clojure :exclude [concat read-string])
|
||||
(:require [clojure.set :as set]
|
||||
#?(:cljs [cljs.reader :as r]
|
||||
:clj [clojure.edn :as r])))
|
||||
:clj [clojure.edn :as r])
|
||||
#?(:cljs [cljs.core :as core]
|
||||
:clj [clojure.core :as core])))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Structures Manipulation
|
||||
|
@ -94,6 +96,12 @@
|
|||
(persistent!
|
||||
(reduce #(dissoc! %1 %2) (transient data) keys)))
|
||||
|
||||
(defn remove-at-index
|
||||
[v index]
|
||||
(vec (core/concat
|
||||
(subvec v 0 index)
|
||||
(subvec v (inc index)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Data Parsing / Conversion
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -366,6 +366,9 @@ ul.slider-dots {
|
|||
// Input amounts
|
||||
|
||||
&.pixels {
|
||||
& input {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "px";
|
||||
|
|
|
@ -263,6 +263,11 @@
|
|||
padding: $x-small $big $x-small $x-small;
|
||||
position: relative;
|
||||
|
||||
& hr {
|
||||
margin: 0;
|
||||
border-color: $color-gray-20;
|
||||
}
|
||||
|
||||
.dropdown-button {
|
||||
position: absolute;
|
||||
right: $x-small;
|
||||
|
@ -321,6 +326,30 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
& li.checked-element {
|
||||
padding-left: 0;
|
||||
|
||||
& span {
|
||||
margin: 0;
|
||||
color: $color-black;
|
||||
}
|
||||
|
||||
& svg {
|
||||
visibility: hidden;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: none;
|
||||
margin: 0.25rem;
|
||||
fill: $color-black;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
& svg {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editable-select {
|
||||
|
@ -535,3 +564,113 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
& svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
fill: $color-gray-20;
|
||||
}
|
||||
}
|
||||
|
||||
.element-set-content .input-row {
|
||||
& .element-set-subtitle {
|
||||
width: 5.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-option {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.element-set-content .grid-option-main {
|
||||
display: flex;
|
||||
padding: 0.5rem 0;
|
||||
border: 1px solid $color-black;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #1F1F1F;
|
||||
}
|
||||
|
||||
& .custom-select {
|
||||
height: 2rem;
|
||||
border: none;
|
||||
border-bottom: 1px solid #65666A;
|
||||
}
|
||||
|
||||
& .input-element {
|
||||
width: 50px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
& .custom-select-dropdown {
|
||||
width: 96px;
|
||||
}
|
||||
|
||||
& .input-option {
|
||||
margin-left: 0.5rem;
|
||||
|
||||
& .custom-select-dropdown {
|
||||
width: 56px;
|
||||
min-width: 56px;
|
||||
max-height: 10rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.grid-option-main-actions {
|
||||
display: flex;
|
||||
visibility: hidden;
|
||||
|
||||
.grid-option:hover & {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.focus-overlay {
|
||||
background: $color-black;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.element-set-content .advanced-options {
|
||||
background-color: #303236;
|
||||
border-radius: 4px;
|
||||
left: -8px;
|
||||
padding: 0.5rem;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
width: calc(100% + 16px);
|
||||
}
|
||||
|
||||
.btn-options {
|
||||
cursor: pointer;
|
||||
border: 1px solid $color-black;
|
||||
background: #1F1F1F;
|
||||
border-radius: 2px;
|
||||
color: #B1B2B5;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
flex-grow: 1;
|
||||
padding: 0.25rem 0;
|
||||
|
||||
&:first-child {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $color-primary;
|
||||
color: $color-black;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1487,6 +1487,44 @@
|
|||
(rx/of (update-shape shape-id
|
||||
{:interactions []}))))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Layouts
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(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}}
|
||||
prop-path [:workspace-data pid :objects frame-id :layouts]
|
||||
layout {:type :square
|
||||
:params default-params
|
||||
:display true}]
|
||||
(-> state
|
||||
(update-in prop-path #(if (nil? %) [layout] (conj % layout))))))))
|
||||
|
||||
(defn remove-frame-layout [frame-id index]
|
||||
(ptk/reify ::set-frame-layout
|
||||
dwc/IBatchedChange
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (:current-page-id state)]
|
||||
(-> state
|
||||
(update-in [:workspace-data pid :objects frame-id :layouts] #(d/remove-at-index % index)))))))
|
||||
|
||||
(defn set-frame-layout [frame-id index data]
|
||||
(ptk/reify ::set-frame-layout
|
||||
dwc/IBatchedChange
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [pid (:current-page-id state)]
|
||||
(->
|
||||
state
|
||||
(assoc-in [:workspace-data pid :objects frame-id :layouts index] data))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Exports
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
:onChangeComplete on-change-complete
|
||||
:style {:box-shadow "none"}}]))
|
||||
|
||||
|
||||
(def most-used-colors
|
||||
(letfn [(selector [{:keys [objects]}]
|
||||
(as-> {} $
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
;; 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.main.ui.components.context-menu
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
;; 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.main.ui.components.editable-label
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
|
|
51
frontend/src/uxbox/main/ui/components/select.cljs
Normal file
51
frontend/src/uxbox/main/ui/components/select.cljs
Normal file
|
@ -0,0 +1,51 @@
|
|||
;; 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.main.ui.components.select
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]))
|
||||
|
||||
(mf/defc select [{:keys [default-value options class on-change]}]
|
||||
(let [state (mf/use-state {:id (uuid/next)
|
||||
:is-open? false
|
||||
:current-value default-value})
|
||||
open-dropdown #(swap! state assoc :is-open? true)
|
||||
close-dropdown #(swap! state assoc :is-open? false)
|
||||
select-item (fn [value] (fn [event]
|
||||
(swap! state assoc :current-value value)
|
||||
(when on-change (on-change value))))
|
||||
as-key-value (fn [item] (if (map? item) [(:value item) (:label item)] [item item]))
|
||||
value->label (into {} (->> options
|
||||
(map as-key-value))) ]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps options)
|
||||
#(reset! state {:is-open? false
|
||||
:current-value default-value}))
|
||||
|
||||
[:div.custom-select {:on-click open-dropdown
|
||||
:class class}
|
||||
[:span (-> @state :current-value value->label)]
|
||||
[:span.dropdown-button i/arrow-down]
|
||||
[:& dropdown {:show (:is-open? @state)
|
||||
:on-close close-dropdown}
|
||||
[:ul.custom-select-dropdown
|
||||
(for [[index item] (map-indexed vector options)]
|
||||
(cond
|
||||
(= :separator item) [:hr {:key (str (:id @state) "-" index)}]
|
||||
:else (let [[value label] (as-key-value item)]
|
||||
[:li.checked-element
|
||||
{:key (str (:id @state) "-" index)
|
||||
:class (when (= value (-> @state :current-value)) "is-selected")
|
||||
:on-click (select-item value)}
|
||||
[:span.check-icon i/tick]
|
||||
[:span label]])))]]]))
|
|
@ -1,3 +1,12 @@
|
|||
;; 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.main.ui.components.tab-container
|
||||
(:require [rumext.alpha :as mf]))
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@
|
|||
(def unlock (icon-xref :unlock))
|
||||
(def uppercase (icon-xref :uppercase))
|
||||
(def user (icon-xref :user))
|
||||
(def tick (icon-xref :tick))
|
||||
|
||||
(def loader-pencil
|
||||
(mf/html
|
||||
|
|
102
frontend/src/uxbox/main/ui/workspace/layout_display.cljs
Normal file
102
frontend/src/uxbox/main/ui/workspace/layout_display.cljs
Normal file
|
@ -0,0 +1,102 @@
|
|||
;; 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.main.ui.workspace.layout-display
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.refs :as refs]))
|
||||
|
||||
(mf/defc grid-layout [{:keys [frame zoom params] :as props}]
|
||||
(let [{:keys [color size]} params
|
||||
{color-value :value color-opacity :opacity} (:color params)
|
||||
{frame-width :width frame-height :height :keys [x y]} frame]
|
||||
[:g.grid
|
||||
[:*
|
||||
(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))}}])]]))
|
||||
|
||||
(defn calculate-column-layout [frame size gutter margin item-width layout-type]
|
||||
(let [{:keys [width height x y]} frame
|
||||
parts (/ width size)
|
||||
item-width (or item-width (+ parts (- gutter) (/ gutter size) (- (/ (* margin 2) size))))
|
||||
item-height height
|
||||
initial-offset (case layout-type
|
||||
:right (- width (* item-width size) (* gutter (dec size)) margin)
|
||||
:center (/ (- width (* item-width size) (* gutter (dec size))) 2)
|
||||
margin)
|
||||
gutter (if (= :stretch layout-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)]
|
||||
[parts item-width item-height next-x next-y]))
|
||||
|
||||
(defn calculate-row-layout [frame size gutter margin item-height layout-type]
|
||||
(let [{:keys [width height x y]} frame
|
||||
parts (/ height size)
|
||||
item-width width
|
||||
item-height (or item-height (+ parts (- gutter) (/ gutter size) (- (/ (* margin 2) size))))
|
||||
initial-offset (case layout-type
|
||||
:right (- height (* item-height size) (* gutter (dec size)) margin)
|
||||
:center (/ (- height (* item-height size) (* gutter (dec size))) 2)
|
||||
margin)
|
||||
gutter (if (= :stretch layout-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)))]
|
||||
[parts item-width item-height next-x next-y]))
|
||||
|
||||
(mf/defc flex-layout [{:keys [frame zoom params orientation]}]
|
||||
(let [{:keys [color size type gutter margin item-width item-height]} params
|
||||
{color-value :value color-opacity :opacity} (:color params)
|
||||
|
||||
;; calculates the layout configuration
|
||||
[parts item-width item-height next-x next-y]
|
||||
(if (= orientation :column)
|
||||
(calculate-column-layout frame size gutter margin item-width type)
|
||||
(calculate-row-layout frame size gutter margin item-height type))]
|
||||
|
||||
(for [cur-val (range 0 size)]
|
||||
[:rect {:x (next-x cur-val)
|
||||
:y (next-y cur-val)
|
||||
:width item-width
|
||||
:height item-height
|
||||
:style {:pointer-events "none"
|
||||
:fill color-value
|
||||
:opacity color-opacity}}])))
|
||||
|
||||
(mf/defc layout-display [{:keys [frame]}]
|
||||
(let [zoom (mf/deref refs/selected-zoom)
|
||||
layouts (:layouts frame)]
|
||||
(for [[index {:keys [type display params]}] (map-indexed vector layouts)]
|
||||
(let [props #js {:key (str (:id frame) "-layout-" index)
|
||||
:frame frame
|
||||
:zoom zoom
|
||||
:params params
|
||||
:orientation (cond (= type :column) :column
|
||||
(= type :row) :row
|
||||
:else nil) }]
|
||||
(when display
|
||||
(case type
|
||||
:square [:> grid-layout props]
|
||||
:column [:> flex-layout props]
|
||||
:row [:> flex-layout props]))))))
|
|
@ -22,7 +22,8 @@
|
|||
[uxbox.util.geom.shapes :as geom]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.streams :as ms]
|
||||
[uxbox.util.timers :as ts]))
|
||||
[uxbox.util.timers :as ts]
|
||||
[uxbox.main.ui.workspace.layout-display :refer [layout-display]]))
|
||||
|
||||
(defn- frame-wrapper-factory-equals?
|
||||
[np op]
|
||||
|
@ -64,15 +65,14 @@
|
|||
on-context-menu (mf/use-callback (mf/deps shape)
|
||||
#(common/on-context-menu % shape))
|
||||
|
||||
|
||||
shape (geom/transform-shape shape)
|
||||
{:keys [x y width height]} shape
|
||||
|
||||
inv-zoom (/ 1 zoom)
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
ds-modifier (get-in shape [:modifiers :displacement])
|
||||
|
||||
label-pos (cond-> (gpt/point x (- y 10))
|
||||
(gmt/matrix? ds-modifier) (gpt/transform ds-modifier))
|
||||
label-pos (gpt/point x (- y 10))
|
||||
|
||||
on-double-click
|
||||
(mf/use-callback
|
||||
|
@ -104,7 +104,7 @@
|
|||
:on-click on-double-click}
|
||||
(:name shape)]
|
||||
[:& frame-shape
|
||||
{:shape (geom/transform-shape shape)
|
||||
:childs childs}]])))))
|
||||
|
||||
{:shape shape
|
||||
:childs childs}]
|
||||
[:& layout-display {:frame shape}]])))))
|
||||
|
||||
|
|
|
@ -12,16 +12,17 @@
|
|||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.math :as math]))
|
||||
[uxbox.util.math :as math]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.fill :refer [fill-menu]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.grid-options :refer [grid-options]]))
|
||||
|
||||
(declare +size-presets+)
|
||||
|
||||
|
@ -82,11 +83,11 @@
|
|||
[:div.custom-select.flex-grow {:on-click #(reset! show-presets-dropdown? true)}
|
||||
[:span (tr "workspace.options.size-presets")]
|
||||
[:span.dropdown-button i/arrow-down]
|
||||
[:& dropdown {:show @show-presets-dropdown?
|
||||
:on-close #(reset! show-presets-dropdown? false)}
|
||||
[:ul.custom-select-dropdown
|
||||
(for [size-preset +size-presets+]
|
||||
(if-not (:width size-preset)
|
||||
[:& dropdown {:show @show-presets-dropdown?
|
||||
:on-close #(reset! show-presets-dropdown? false)}
|
||||
[:ul.custom-select-dropdown
|
||||
(for [size-preset +size-presets+]
|
||||
(if-not (:width size-preset)
|
||||
[:li.dropdown-label {:key (:name size-preset)}
|
||||
[:span (:name size-preset)]]
|
||||
[:li {:key (:name size-preset)
|
||||
|
@ -202,4 +203,5 @@
|
|||
[:div
|
||||
[:& measures-menu {:shape shape}]
|
||||
[:& fill-menu {:shape shape}]
|
||||
[:& stroke-menu {:shape shape}]])
|
||||
[:& stroke-menu {:shape shape}]
|
||||
[:& grid-options {:shape shape}]])
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
;; 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.main.ui.workspace.sidebar.options.grid-options
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.data :as d]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.workspace :as dw]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
|
||||
[uxbox.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]]
|
||||
[uxbox.main.ui.components.select :refer [select]]
|
||||
[uxbox.main.ui.components.dropdown :refer [dropdown]]))
|
||||
|
||||
(mf/defc advanced-options [{:keys [visible? on-close children]}]
|
||||
(when visible?
|
||||
[:*
|
||||
[:div.focus-overlay {:on-click #(when on-close (do
|
||||
(dom/stop-propagation %)
|
||||
(on-close)))}]
|
||||
[: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 grid-option [{:keys [layout on-change on-remove]}]
|
||||
(let [state (mf/use-state {:show-advanced-options false
|
||||
:changes {}})
|
||||
{:keys [type display params] :as layout} (d/deep-merge layout (:changes @state))
|
||||
|
||||
toggle-advanced-options #(swap! state update :show-advanced-options not)
|
||||
|
||||
size-options [{:value :auto :label "Auto"}
|
||||
:separator
|
||||
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)))))
|
||||
|
||||
handle-toggle-visibility (fn [event]
|
||||
(emit-changes! #(update % :display not)))
|
||||
|
||||
handle-remove-layout (fn [event]
|
||||
(when on-remove (on-remove)))
|
||||
|
||||
handle-change-type (fn [type]
|
||||
(let [defaults (type default-params)
|
||||
params (merge
|
||||
defaults
|
||||
(select-keys (keys defaults) (-> @state :changes params)))
|
||||
to-merge {:type type :params params}]
|
||||
(emit-changes! #(d/deep-merge % to-merge))))
|
||||
|
||||
handle-change (fn [& keys]
|
||||
(fn [value]
|
||||
(emit-changes! #(assoc-in % keys value))))
|
||||
|
||||
handle-change-event (fn [& keys]
|
||||
(fn [event]
|
||||
(let [change-fn (apply handle-change keys)]
|
||||
(-> event dom/get-target dom/get-value change-fn))))
|
||||
]
|
||||
|
||||
[:div.grid-option
|
||||
[:div.grid-option-main
|
||||
[:button.custom-button {:class (when (:show-advanced-options @state) "is-active")
|
||||
:on-click toggle-advanced-options} i/actions]
|
||||
|
||||
[:& select {:class "flex-grow"
|
||||
:default-value type
|
||||
:options [{:value :square :label "Square"}
|
||||
{:value :column :label "Columns"}
|
||||
{:value :row :label "Rows"}]
|
||||
:on-change handle-change-type}]
|
||||
|
||||
(if (= type :square)
|
||||
[:div.input-element.pixels
|
||||
[:input.input-text {:type "number"
|
||||
:min "0"
|
||||
:no-validate true
|
||||
:value (:size params)
|
||||
:on-change (handle-change-event :params :size)}]]
|
||||
[:& select {:default-value (:size params)
|
||||
:class "input-option"
|
||||
:options size-options
|
||||
:on-change (handle-change :params :size)}])
|
||||
|
||||
[:div.grid-option-main-actions
|
||||
[:button.custom-button {:on-click handle-toggle-visibility} (if display i/eye i/eye-closed)]
|
||||
[:button.custom-button {:on-click handle-remove-layout} i/trash]]]
|
||||
|
||||
[:& advanced-options {:visible? (:show-advanced-options @state)
|
||||
:on-close toggle-advanced-options}
|
||||
(when (= :square type)
|
||||
[:& input-row {:label "Size"
|
||||
:value (:size params)
|
||||
:on-change (handle-change :params :size)}])
|
||||
|
||||
(when (= :row type)
|
||||
[:& input-row {:label "Rows"
|
||||
:options size-options
|
||||
:value (:size params)
|
||||
:on-change (handle-change :params :size)}])
|
||||
|
||||
(when (= :column type)
|
||||
[:& input-row {:label "Columns"
|
||||
:options size-options
|
||||
:value (:size params)
|
||||
:on-change (handle-change :params :size)}])
|
||||
|
||||
(when (#{:row :column} type)
|
||||
[:& input-row {:label "Type"
|
||||
:options [{:value :stretch :label "Stretch"}
|
||||
{:value :left :label "Left"}
|
||||
{:value :center :label "Center"}
|
||||
{:value :right :label "Right"}]
|
||||
:value (:type params)
|
||||
:on-change (handle-change :params :type)}])
|
||||
|
||||
(when (= :row type)
|
||||
[:& input-row {:label "Height"
|
||||
:value (or (:item-height params) "")
|
||||
:on-change (handle-change :params :item-height)}])
|
||||
|
||||
(when (= :column type)
|
||||
[:& input-row {:label "Width"
|
||||
:value (or (:item-width params) "")
|
||||
:on-change (handle-change :params :item-width)}])
|
||||
|
||||
(when (#{:row :column} type)
|
||||
[:*
|
||||
[:& input-row {:label "Gutter"
|
||||
:value (:gutter params)
|
||||
:on-change (handle-change :params :gutter)}]
|
||||
[:& input-row {:label "Margin"
|
||||
: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"]]]]))
|
||||
|
||||
(mf/defc grid-options [{:keys [shape]}]
|
||||
(let [id (:id shape)
|
||||
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 %)))]
|
||||
[: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))]
|
||||
[:& grid-option {:key (str (:id shape) "-" index)
|
||||
:layout layout
|
||||
:on-change (handle-edit-layout index)
|
||||
:on-remove (handle-remove-layout index)}])]]))
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
;; 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.main.ui.workspace.sidebar.options.rows.color-row
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.util.math :as math]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
|
||||
[uxbox.common.data :as d]))
|
||||
|
||||
(defn color-picker-callback [color handle-change-color]
|
||||
(fn [event]
|
||||
(let [x (.-clientX event)
|
||||
y (.-clientY event)
|
||||
props {:x x
|
||||
:y y
|
||||
:on-change handle-change-color
|
||||
:value (:value color)
|
||||
:transparent? true}]
|
||||
(modal/show! colorpicker-modal props))))
|
||||
|
||||
(defn opacity->string [opacity]
|
||||
(str (-> opacity
|
||||
(d/coalesce 1)
|
||||
(* 100)
|
||||
(math/round))))
|
||||
|
||||
(defn string->opacity [opacity-str]
|
||||
(-> opacity-str
|
||||
(d/parse-integer 1)
|
||||
(/ 100)))
|
||||
|
||||
(mf/defc color-row [{:keys [value on-change]}]
|
||||
(let [state (mf/use-state value)
|
||||
change-color (fn [color]
|
||||
(let [update-color (fn [state] (assoc state :value color))]
|
||||
(swap! state update-color)
|
||||
(when on-change (on-change (update-color @state)))))
|
||||
|
||||
change-opacity (fn [opacity]
|
||||
(let [update-opacity (fn [state] (assoc state :opacity opacity))]
|
||||
(swap! state update-opacity)
|
||||
(when on-change (on-change (update-opacity @state)))))
|
||||
|
||||
handle-pick-color (fn [color]
|
||||
(change-color color))
|
||||
|
||||
handle-input-color-change (fn [event]
|
||||
(let [target (dom/get-target event)
|
||||
value (dom/get-value target)]
|
||||
(when (dom/valid? target)
|
||||
(change-color value))))
|
||||
handle-opacity-change (fn [event]
|
||||
(-> event
|
||||
dom/get-target
|
||||
dom/get-value
|
||||
string->opacity
|
||||
change-opacity))]
|
||||
|
||||
[:div.row-flex.color-data
|
||||
[:span.color-th
|
||||
{:style {:background-color (-> @state :value)}
|
||||
:on-click (color-picker-callback @state handle-pick-color)}]
|
||||
|
||||
[:div.color-info
|
||||
[:input {:value (-> @state :value)
|
||||
:pattern "^#(?:[0-9a-fA-F]{3}){1,2}$"
|
||||
:on-change handle-input-color-change}]]
|
||||
|
||||
[:div.input-element.percentail
|
||||
[:input.input-text {:type "number"
|
||||
:value (-> @state :opacity opacity->string)
|
||||
:on-change handle-opacity-change
|
||||
:min "0"
|
||||
:max "100"}]]
|
||||
|
||||
[:input.slidebar {:type "range"
|
||||
:min "0"
|
||||
:max "100"
|
||||
:value (-> @state :opacity opacity->string)
|
||||
:step "1"
|
||||
:on-change handle-opacity-change}]]))
|
|
@ -0,0 +1,30 @@
|
|||
;; 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.main.ui.workspace.sidebar.options.rows.input-row
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.data :as d]
|
||||
[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}])]])
|
Loading…
Add table
Reference in a new issue