mirror of
https://github.com/penpot/penpot.git
synced 2025-05-25 01:36:11 +02: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])
|
(:refer-clojure :exclude [concat read-string])
|
||||||
(:require [clojure.set :as set]
|
(:require [clojure.set :as set]
|
||||||
#?(:cljs [cljs.reader :as r]
|
#?(: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
|
;; Data Structures Manipulation
|
||||||
|
@ -94,6 +96,12 @@
|
||||||
(persistent!
|
(persistent!
|
||||||
(reduce #(dissoc! %1 %2) (transient data) keys)))
|
(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
|
;; Data Parsing / Conversion
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
|
@ -366,6 +366,9 @@ ul.slider-dots {
|
||||||
// Input amounts
|
// Input amounts
|
||||||
|
|
||||||
&.pixels {
|
&.pixels {
|
||||||
|
& input {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: "px";
|
content: "px";
|
||||||
|
|
|
@ -263,6 +263,11 @@
|
||||||
padding: $x-small $big $x-small $x-small;
|
padding: $x-small $big $x-small $x-small;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
& hr {
|
||||||
|
margin: 0;
|
||||||
|
border-color: $color-gray-20;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-button {
|
.dropdown-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: $x-small;
|
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 {
|
.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
|
(rx/of (update-shape shape-id
|
||||||
{:interactions []}))))))))
|
{: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
|
;; Exports
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
:onChangeComplete on-change-complete
|
:onChangeComplete on-change-complete
|
||||||
:style {:box-shadow "none"}}]))
|
:style {:box-shadow "none"}}]))
|
||||||
|
|
||||||
|
|
||||||
(def most-used-colors
|
(def most-used-colors
|
||||||
(letfn [(selector [{:keys [objects]}]
|
(letfn [(selector [{:keys [objects]}]
|
||||||
(as-> {} $
|
(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
|
(ns uxbox.main.ui.components.context-menu
|
||||||
(:require
|
(:require
|
||||||
[rumext.alpha :as mf]
|
[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
|
(ns uxbox.main.ui.components.editable-label
|
||||||
(:require
|
(:require
|
||||||
[rumext.alpha :as mf]
|
[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
|
(ns uxbox.main.ui.components.tab-container
|
||||||
(:require [rumext.alpha :as mf]))
|
(:require [rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,7 @@
|
||||||
(def unlock (icon-xref :unlock))
|
(def unlock (icon-xref :unlock))
|
||||||
(def uppercase (icon-xref :uppercase))
|
(def uppercase (icon-xref :uppercase))
|
||||||
(def user (icon-xref :user))
|
(def user (icon-xref :user))
|
||||||
|
(def tick (icon-xref :tick))
|
||||||
|
|
||||||
(def loader-pencil
|
(def loader-pencil
|
||||||
(mf/html
|
(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.geom.shapes :as geom]
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.main.streams :as ms]
|
[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?
|
(defn- frame-wrapper-factory-equals?
|
||||||
[np op]
|
[np op]
|
||||||
|
@ -64,15 +65,14 @@
|
||||||
on-context-menu (mf/use-callback (mf/deps shape)
|
on-context-menu (mf/use-callback (mf/deps shape)
|
||||||
#(common/on-context-menu % shape))
|
#(common/on-context-menu % shape))
|
||||||
|
|
||||||
|
shape (geom/transform-shape shape)
|
||||||
{:keys [x y width height]} shape
|
{:keys [x y width height]} shape
|
||||||
|
|
||||||
inv-zoom (/ 1 zoom)
|
inv-zoom (/ 1 zoom)
|
||||||
childs (mapv #(get objects %) (:shapes shape))
|
childs (mapv #(get objects %) (:shapes shape))
|
||||||
ds-modifier (get-in shape [:modifiers :displacement])
|
ds-modifier (get-in shape [:modifiers :displacement])
|
||||||
|
|
||||||
label-pos (cond-> (gpt/point x (- y 10))
|
label-pos (gpt/point x (- y 10))
|
||||||
(gmt/matrix? ds-modifier) (gpt/transform ds-modifier))
|
|
||||||
|
|
||||||
on-double-click
|
on-double-click
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
:on-click on-double-click}
|
:on-click on-double-click}
|
||||||
(:name shape)]
|
(:name shape)]
|
||||||
[:& frame-shape
|
[:& frame-shape
|
||||||
{:shape (geom/transform-shape shape)
|
{:shape shape
|
||||||
:childs childs}]])))))
|
:childs childs}]
|
||||||
|
[:& layout-display {:frame shape}]])))))
|
||||||
|
|
||||||
|
|
|
@ -12,16 +12,17 @@
|
||||||
(:require
|
(:require
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[uxbox.common.data :as d]
|
[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.dom :as dom]
|
||||||
[uxbox.util.geom.point :as gpt]
|
[uxbox.util.geom.point :as gpt]
|
||||||
[uxbox.util.i18n :refer [tr]]
|
[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+)
|
(declare +size-presets+)
|
||||||
|
|
||||||
|
@ -82,11 +83,11 @@
|
||||||
[:div.custom-select.flex-grow {:on-click #(reset! show-presets-dropdown? true)}
|
[:div.custom-select.flex-grow {:on-click #(reset! show-presets-dropdown? true)}
|
||||||
[:span (tr "workspace.options.size-presets")]
|
[:span (tr "workspace.options.size-presets")]
|
||||||
[:span.dropdown-button i/arrow-down]
|
[:span.dropdown-button i/arrow-down]
|
||||||
[:& dropdown {:show @show-presets-dropdown?
|
[:& dropdown {:show @show-presets-dropdown?
|
||||||
:on-close #(reset! show-presets-dropdown? false)}
|
:on-close #(reset! show-presets-dropdown? false)}
|
||||||
[:ul.custom-select-dropdown
|
[:ul.custom-select-dropdown
|
||||||
(for [size-preset +size-presets+]
|
(for [size-preset +size-presets+]
|
||||||
(if-not (:width size-preset)
|
(if-not (:width size-preset)
|
||||||
[:li.dropdown-label {:key (:name size-preset)}
|
[:li.dropdown-label {:key (:name size-preset)}
|
||||||
[:span (:name size-preset)]]
|
[:span (:name size-preset)]]
|
||||||
[:li {:key (:name size-preset)
|
[:li {:key (:name size-preset)
|
||||||
|
@ -202,4 +203,5 @@
|
||||||
[:div
|
[:div
|
||||||
[:& measures-menu {:shape shape}]
|
[:& measures-menu {:shape shape}]
|
||||||
[:& fill-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
Add a link
Reference in a new issue