mirror of
https://github.com/penpot/penpot.git
synced 2025-06-06 00:21:39 +02:00
263 lines
8.6 KiB
Clojure
263 lines
8.6 KiB
Clojure
(ns uxbox.ui.workspace.options
|
|
(:require [sablono.core :as html :refer-macros [html]]
|
|
[rum.core :as rum]
|
|
[uxbox.rstore :as rs]
|
|
[uxbox.state :as st]
|
|
[uxbox.shapes :as sh]
|
|
[uxbox.data.workspace :as dw]
|
|
[uxbox.ui.icons :as i]
|
|
[uxbox.ui.mixins :as mx]
|
|
[uxbox.ui.dom :as dom]
|
|
[uxbox.ui.colorpicker :refer (colorpicker)]
|
|
[uxbox.ui.workspace.recent-colors :refer (recent-colors)]
|
|
[uxbox.ui.workspace.base :as wb]
|
|
[uxbox.util.data :refer (parse-int parse-float)]))
|
|
|
|
(def +menus-map+
|
|
{:builtin/icon [:menu/measures :menu/fill :menu/stroke]
|
|
:builtin/rect [:menu/measures :menu/fill :menu/stroke]
|
|
:builtin/group [:menu/measures]})
|
|
|
|
(def +menus-by-id+
|
|
{:menu/measures
|
|
{:name "Size & position"
|
|
:icon i/infocard
|
|
:id :menu/measures}
|
|
|
|
:menu/fill
|
|
{:name "Fill"
|
|
:icon i/fill
|
|
:id :menu/fill}
|
|
|
|
:menu/stroke
|
|
{:name "Stroke"
|
|
:icon i/stroke
|
|
:id :menu/stroke}})
|
|
|
|
(defn- viewportcoord->clientcoord
|
|
[pageid viewport-x viewport-y]
|
|
(let [[offset-x offset-y] (get @wb/bounding-rect pageid)
|
|
new-x (+ viewport-x offset-x)
|
|
new-y (+ viewport-y offset-y)]
|
|
[new-x new-y]))
|
|
|
|
(defn- get-position
|
|
[{:keys [page width] :as shape}]
|
|
(let [{:keys [x y]} (sh/resolve-position shape)
|
|
vx (+ x width 50)
|
|
vy (- y 50)]
|
|
(viewportcoord->clientcoord page vx vy)))
|
|
|
|
(defmulti -render-menu
|
|
(fn [menu own shape] (:id menu)))
|
|
|
|
(defmethod -render-menu :menu/stroke
|
|
[menu own shape]
|
|
(letfn [(change-stroke [value]
|
|
(let [sid (:id shape)]
|
|
(rs/emit! (dw/update-shape-stroke sid value))))
|
|
(on-width-change [event]
|
|
(let [value (dom/event->value event)
|
|
value (parse-float value 1)]
|
|
(change-stroke {:width value})))
|
|
(on-opacity-change [event]
|
|
(let [value (dom/event->value event)
|
|
value (parse-float value 1)]
|
|
(change-stroke {:opacity value})))
|
|
(on-color-change [event]
|
|
(let [value (dom/event->value event)]
|
|
(change-stroke {:color value})))]
|
|
(html
|
|
[:div.element-set {:key (str (:id menu))}
|
|
[:div.element-set-title (:name menu)]
|
|
[:div.element-set-content
|
|
[:span "Style"]
|
|
[:div.row-flex
|
|
[:input#width.input-text
|
|
{:placeholder "Width"
|
|
:type "number"
|
|
:min "0"
|
|
:value (:stroke-width shape "")
|
|
:on-change on-width-change}]
|
|
[:select#style {:placeholder "Style"}
|
|
[:option {:value "none"} "None"]
|
|
[:option {:value "none"} "Solid"]
|
|
[:option {:value "none"} "Dotted"]
|
|
[:option {:value "none"} "Dashed"]]]
|
|
|
|
;; SLIDEBAR FOR ROTATION AND OPACITY
|
|
[:span "Color"]
|
|
(colorpicker {:picker {:width 165 :height 165}
|
|
:bar {:width 15 :height 165}
|
|
:callback #(change-stroke {:color (:hex %)})})
|
|
|
|
[:div.row-flex
|
|
[:input#width.input-text
|
|
{:placeholder "#"
|
|
:type "text"
|
|
:value (:stroke shape "")
|
|
:on-change on-color-change}]]
|
|
|
|
(recent-colors shape #(change-stroke {:color %}))
|
|
[:span "Border radius"]
|
|
[:div.row-flex
|
|
[:div.border-element.top-left
|
|
i/radius
|
|
[:input.input-text {:type "text" :placeholder "px"}]]
|
|
[:div.border-element.top-right
|
|
i/radius
|
|
[:input.input-text {:type "text" :placeholder "px"}]]
|
|
[:span.lock-size i/lock]
|
|
[:div.border-element.bottom-left
|
|
i/radius
|
|
[:input.input-text {:type "text" :placeholder "px"}]]
|
|
[:div.border-element.bottom-right
|
|
i/radius
|
|
[:input.input-text {:type "text" :placeholder "px"}]]]
|
|
|
|
;; SLIDEBAR FOR ROTATION AND OPACITY
|
|
[:span "Opacity"]
|
|
[:div.row-flex
|
|
[:input.slidebar
|
|
{:type "range"
|
|
:min "0"
|
|
:max "1"
|
|
:value (:stroke-opacity shape "1")
|
|
:step "0.0001"
|
|
:on-change on-opacity-change}]]]])))
|
|
|
|
(defmethod -render-menu :menu/fill
|
|
[menu own shape]
|
|
(letfn [(change-fill [value]
|
|
(let [sid (:id shape)]
|
|
(-> (dw/update-shape-fill sid value)
|
|
(rs/emit!))))
|
|
(on-color-change [event]
|
|
(let [value (dom/event->value event)]
|
|
(change-fill {:fill value})))
|
|
(on-opacity-change [event]
|
|
(let [value (dom/event->value event)
|
|
value (parse-float value 1)]
|
|
(change-fill {:opacity value})))
|
|
(on-color-picker-event [{:keys [hex]}]
|
|
(change-fill {:fill hex}))]
|
|
(html
|
|
[:div.element-set {:key (str (:id menu))}
|
|
[:div.element-set-title (:name menu)]
|
|
[:div.element-set-content
|
|
;; SLIDEBAR FOR ROTATION AND OPACITY
|
|
[:span "Color"]
|
|
(colorpicker {:picker {:width 165 :height 165}
|
|
:bar {:width 15 :height 165}
|
|
:callback on-color-picker-event})
|
|
[:div.row-flex
|
|
[:input#width.input-text
|
|
{:placeholder "#"
|
|
:type "text"
|
|
:value (:fill shape "")
|
|
:on-change on-color-change}]]
|
|
|
|
(recent-colors shape #(change-fill {:fill %}))
|
|
|
|
;; SLIDEBAR FOR ROTATION AND OPACITY
|
|
[:span "Opacity"]
|
|
[:div.row-flex
|
|
[:input.slidebar
|
|
{:type "range"
|
|
:min "0"
|
|
:max "1"
|
|
:value (:opacity shape "1")
|
|
:step "0.0001"
|
|
:on-change on-opacity-change}]]]])))
|
|
|
|
(defmethod -render-menu :menu/measures
|
|
[menu own shape]
|
|
(letfn [(on-size-change [attr event]
|
|
(let [value (dom/event->value event)
|
|
value (parse-int value 0)
|
|
sid (:id shape)]
|
|
(-> (dw/update-shape-size sid {attr value})
|
|
(rs/emit!))))
|
|
(on-rotation-change [event]
|
|
(let [value (dom/event->value event)
|
|
value (parse-int value 0)
|
|
sid (:id shape)]
|
|
(-> (dw/update-shape-rotation sid value)
|
|
(rs/emit!))))
|
|
(on-pos-change [attr event]
|
|
(let [value (dom/event->value event)
|
|
value (parse-int value nil)
|
|
sid (:id shape)]
|
|
(-> (dw/update-shape-position sid {attr value})
|
|
(rs/emit!))))]
|
|
(html
|
|
[:div.element-set {:key (str (:id menu))}
|
|
[:div.element-set-title (:name menu)]
|
|
[:div.element-set-content
|
|
;; SLIDEBAR FOR ROTATION AND OPACITY
|
|
[:span "Size"]
|
|
[:div.row-flex
|
|
[:input#width.input-text
|
|
{:placeholder "Width"
|
|
:type "number"
|
|
:min "0"
|
|
:value (:width shape)
|
|
:on-change (partial on-size-change :width)}]
|
|
[:div.lock-size i/lock]
|
|
[:input#width.input-text
|
|
{:placeholder "Height"
|
|
:type "number"
|
|
:min "0"
|
|
:value (:height shape)
|
|
:on-change (partial on-size-change :height)}]]
|
|
|
|
[:span "Position"]
|
|
[:div.row-flex
|
|
[:input#width.input-text
|
|
{:placeholder "x"
|
|
:type "number"
|
|
:value (:x shape "")
|
|
:on-change (partial on-pos-change :x)}]
|
|
[:input#width.input-text
|
|
{:placeholder "y"
|
|
:type "number"
|
|
:value (:y shape "")
|
|
:on-change (partial on-pos-change :y)}]]
|
|
|
|
[:span "Rotation"]
|
|
[:div.row-flex
|
|
[:input.slidebar
|
|
{:type "range"
|
|
:min 0
|
|
:max 360
|
|
:value (:rotation shape 0)
|
|
:on-change on-rotation-change}]]]])))
|
|
|
|
(defn element-opts-render
|
|
[own shape]
|
|
(let [local (:rum/local own)
|
|
shape (rum/react shape)
|
|
[popup-x popup-y] (get-position shape)
|
|
scroll (or (rum/react wb/scroll-top) 0)
|
|
zoom 1
|
|
menus (get +menus-map+ (:type shape))
|
|
active-menu (:menu @local (first menus))]
|
|
(html
|
|
[:div#element-options.element-options
|
|
{:style {:left (* popup-x zoom) :top (- (* popup-y zoom) scroll)}}
|
|
[:ul.element-icons
|
|
(for [menu-id (get +menus-map+ (:type shape))
|
|
:let [menu (get +menus-by-id+ menu-id)
|
|
selected? (= active-menu menu-id)]]
|
|
[:li#e-info {:on-click #(swap! local assoc :menu menu-id)
|
|
:key (str "menu-" (:id menu))
|
|
:class (when selected? "selected")}
|
|
(:icon menu)])]
|
|
(let [menu (get +menus-by-id+ active-menu)]
|
|
(-render-menu menu own shape))])))
|
|
|
|
(def ^:static element-opts
|
|
(mx/component
|
|
{:render element-opts-render
|
|
:name "element-opts"
|
|
:mixins [rum/reactive (mx/local {})]}))
|