mirror of
https://github.com/penpot/penpot.git
synced 2025-03-27 15:11:26 -05:00
✨ Add suport to export/import frames with radius
This commit is contained in:
parent
d4af28c52b
commit
17fc15138a
10 changed files with 386 additions and 505 deletions
|
@ -72,7 +72,8 @@
|
|||
{:frame #{:proportion-lock
|
||||
:width :height
|
||||
:x :y
|
||||
:rx :ry :r1 :r2 :r3 :r4
|
||||
:rx :ry
|
||||
:r1 :r2 :r3 :r4
|
||||
:selrect
|
||||
|
||||
:opacity
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
(s/def ::r3 ::us/safe-number)
|
||||
(s/def ::r4 ::us/safe-number)
|
||||
|
||||
;; Rectangle shapes may define the radius of the corners in two modes:
|
||||
;; There are some shapes that admit border radius, as rectangles
|
||||
;; frames and images. Those shapes may define the radius of the corners in two modes:
|
||||
;; - radius-1 all corners have the same radius (although we store two
|
||||
;; values :rx and :ry because svg uses it this way).
|
||||
;; - radius-4 each corner (top-left, top-right, bottom-right, bottom-left)
|
||||
|
@ -25,14 +26,21 @@
|
|||
|
||||
;; A shape never will have both :rx and :r1 simultaneously
|
||||
|
||||
;; All operations take into account that the shape may not be a rectangle, and so
|
||||
;; it hasn't :rx nor :r1. In this case operations must leave shape untouched.
|
||||
;; All operations take into account that the shape may not be a one of those
|
||||
;; shapes that has border radius, and so it hasn't :rx nor :r1.
|
||||
;; In this case operations must leave shape untouched.
|
||||
|
||||
(defn has-radius?
|
||||
[shape]
|
||||
(#{:rect :image :frame} (:type shape)))
|
||||
|
||||
(defn radius-mode
|
||||
[shape]
|
||||
(cond (:rx shape) :radius-1
|
||||
(:r1 shape) :radius-4
|
||||
:else nil))
|
||||
:else (if (has-radius? shape)
|
||||
:radius-1
|
||||
nil)))
|
||||
|
||||
(defn radius-1?
|
||||
[shape]
|
||||
|
@ -75,7 +83,7 @@
|
|||
(-> (dissoc :r1 :r2 :r3 :r4)
|
||||
(assoc :rx 0 :ry 0))
|
||||
|
||||
(:rx shape)
|
||||
:always
|
||||
(assoc :rx value :ry value)))
|
||||
|
||||
(defn set-radius-4
|
||||
|
@ -85,6 +93,6 @@
|
|||
(-> (dissoc :rx :rx)
|
||||
(assoc :r1 0 :r2 0 :r3 0 :r4 0))
|
||||
|
||||
(attr shape)
|
||||
:always
|
||||
(assoc attr value)))
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
(defonce default-rect {:x 0 :y 0 :width 1 :height 1 :rx 0 :ry 0})
|
||||
(defonce default-circle {:r 0 :cx 0 :cy 0})
|
||||
(defonce default-image {:x 0 :y 0 :width 1 :height 1})
|
||||
(defonce default-image {:x 0 :y 0 :width 1 :height 1 :rx 0 :ry 0})
|
||||
|
||||
(defn- assert-valid-num [attr num]
|
||||
(when (or (nil? num)
|
||||
|
@ -270,8 +270,8 @@
|
|||
:name name
|
||||
:frame-id frame-id}
|
||||
(cond->
|
||||
(contains? attrs :rx) (assoc :rx (d/parse-double (:rx attrs)))
|
||||
(contains? attrs :ry) (assoc :ry (d/parse-double (:ry attrs))))
|
||||
(contains? attrs :rx) (assoc :rx (d/parse-double (:rx attrs 0)))
|
||||
(contains? attrs :ry) (assoc :ry (d/parse-double (:ry attrs 0))))
|
||||
|
||||
(merge metadata)
|
||||
(assoc :svg-viewbox (select-keys rect [:x :y :width :height]))
|
||||
|
|
|
@ -59,8 +59,8 @@
|
|||
(case (ctr/radius-mode shape)
|
||||
|
||||
:radius-1
|
||||
(obj/merge! attrs #js {:rx (:rx shape)
|
||||
:ry (:ry shape)})
|
||||
(obj/merge! attrs #js {:rx (:rx shape 0)
|
||||
:ry (:ry shape 0)})
|
||||
|
||||
:radius-4
|
||||
(let [[r1 r2 r3 r4] (truncate-radius shape)
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
"Adds as metadata properties that we cannot deduce from the exported SVG"
|
||||
[props shape]
|
||||
(let [add! (add-factory shape)
|
||||
frame? (= :frame (:type shape))
|
||||
group? (= :group (:type shape))
|
||||
rect? (= :rect (:type shape))
|
||||
image? (= :image (:type shape))
|
||||
|
@ -94,7 +95,7 @@
|
|||
(add! :constraints-v)
|
||||
(add! :fixed-scroll)
|
||||
|
||||
(cond-> (and (or rect? image?) (some? (:r1 shape)))
|
||||
(cond-> (and (or rect? image? frame?) (some? (:r1 shape)))
|
||||
(-> (add! :r1)
|
||||
(add! :r2)
|
||||
(add! :r3)
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
(when (ctr/radius-1? shape)
|
||||
[:div.attributes-unit-row
|
||||
[:div.attributes-label (t locale "handoff.attributes.layout.radius")]
|
||||
[:div.attributes-value (mth/precision (:rx shape) 2) "px"]
|
||||
[:div.attributes-value (mth/precision (:rx shape 0) 2) "px"]
|
||||
[:& copy-button {:data (copy-data shape :rx)}]])
|
||||
|
||||
(when (ctr/radius-4? shape)
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as math]
|
||||
[app.common.spec.radius :as ctr]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.sidebar.options.menus.radius :refer [border-radius]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
@ -36,10 +38,13 @@
|
|||
(d/coalesce 0)
|
||||
(math/precision 2))))))
|
||||
|
||||
(declare +size-presets+)
|
||||
|
||||
;; -- User/drawing coords
|
||||
(mf/defc measures-menu
|
||||
[{:keys [options ids ids-with-children values] :as props}]
|
||||
[{:keys [options ids ids-with-children values type] :as props}]
|
||||
(let [options (or options #{:size :position :rotation :radius})
|
||||
ids-with-children (or ids-with-children ids)
|
||||
|
||||
old-shapes (deref (refs/objects-by-id ids))
|
||||
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
|
||||
|
@ -60,6 +65,28 @@
|
|||
|
||||
proportion-lock (:proportion-lock values)
|
||||
|
||||
show-presets-dropdown? (mf/use-state false)
|
||||
|
||||
radius-mode (ctr/radius-mode values)
|
||||
all-equal? (ctr/all-equal? values)
|
||||
radius-multi? (mf/use-state nil)
|
||||
radius-input-ref (mf/use-ref nil)
|
||||
|
||||
|
||||
on-preset-selected
|
||||
(fn [width height]
|
||||
(st/emit! (udw/update-dimensions ids :width width)
|
||||
(udw/update-dimensions ids :height height)))
|
||||
|
||||
on-orientation-clicked
|
||||
(fn [orientation]
|
||||
(let [width (:width values)
|
||||
height (:height values)
|
||||
new-width (if (= orientation :horiz) (max width height) (min width height))
|
||||
new-height (if (= orientation :horiz) (min width height) (max width height))]
|
||||
(st/emit! (udw/update-dimensions ids :width new-width)
|
||||
(udw/update-dimensions ids :height new-height))))
|
||||
|
||||
on-size-change
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
|
@ -78,7 +105,7 @@
|
|||
(mf/deps ids)
|
||||
(fn [shape' frame' value attr]
|
||||
(let [to (+ value (attr frame'))]
|
||||
(st/emit! (udw/update-position (:id shape') { attr to })))))
|
||||
(st/emit! (udw/update-position (:id shape') {attr to})))))
|
||||
|
||||
on-position-change
|
||||
(mf/use-callback
|
||||
|
@ -92,16 +119,89 @@
|
|||
(fn [value]
|
||||
(st/emit! (udw/increase-rotation ids value))))
|
||||
|
||||
|
||||
on-switch-to-radius-1
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [_value]
|
||||
(if all-equal?
|
||||
(st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-1))
|
||||
(reset! radius-multi? true))))
|
||||
|
||||
on-switch-to-radius-4
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [_value]
|
||||
(st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-4))
|
||||
(reset! radius-multi? false)))
|
||||
|
||||
on-radius-1-change
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [value]
|
||||
(prn "entro en on radius 1")
|
||||
(st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-1 % value)))))
|
||||
|
||||
on-radius-multi-change
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-value d/parse-integer)]
|
||||
(when (some? value)
|
||||
(st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-1)
|
||||
(dch/update-shapes ids-with-children #(ctr/set-radius-1 % value)))
|
||||
(reset! radius-multi? false)))))
|
||||
|
||||
on-radius-4-change
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [value attr]
|
||||
(st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-4 % attr value)))))
|
||||
|
||||
|
||||
on-width-change #(on-size-change % :width)
|
||||
on-height-change #(on-size-change % :height)
|
||||
on-pos-x-change #(on-position-change % :x)
|
||||
on-pos-y-change #(on-position-change % :y)
|
||||
on-radius-r1-change #(on-radius-4-change % :r1)
|
||||
on-radius-r2-change #(on-radius-4-change % :r2)
|
||||
on-radius-r3-change #(on-radius-4-change % :r3)
|
||||
on-radius-r4-change #(on-radius-4-change % :r4)
|
||||
|
||||
select-all #(-> % (dom/get-target) (.select))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps radius-mode @radius-multi?)
|
||||
(fn []
|
||||
(when (and (= radius-mode :radius-1)
|
||||
(= @radius-multi? false))
|
||||
;; when going back from radius-multi to normal radius-1,
|
||||
;; restore focus to the newly created numeric-input
|
||||
(let [radius-input (mf/ref-val radius-input-ref)]
|
||||
(dom/focus! radius-input)))))
|
||||
|
||||
[:*
|
||||
[:div.element-set
|
||||
[:div.element-set-content
|
||||
;; FRAME PRESETS
|
||||
(when (= type :frame)
|
||||
[:div.row-flex
|
||||
[:div.presets.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)
|
||||
[:li.dropdown-label {:key (:name size-preset)}
|
||||
[:span (:name size-preset)]]
|
||||
[:li {:key (:name size-preset)
|
||||
:on-click #(on-preset-selected (:width size-preset) (:height size-preset))}
|
||||
(:name size-preset)
|
||||
[:span (:width size-preset) " x " (:height size-preset)]]))]]]
|
||||
[:span.orientation-icon {:on-click #(on-orientation-clicked :vert)} i/size-vert]
|
||||
[:span.orientation-icon {:on-click #(on-orientation-clicked :horiz)} i/size-horiz]])
|
||||
|
||||
;; WIDTH & HEIGHT
|
||||
(when (options :size)
|
||||
|
@ -151,7 +251,7 @@
|
|||
:precision 2}]]])
|
||||
|
||||
;; ROTATION
|
||||
(when (options :rotation)
|
||||
(when (and (options :rotation) (not (= type :frame)))
|
||||
[:div.row-flex
|
||||
[:span.element-set-subtitle (tr "workspace.options.rotation")]
|
||||
[:div.input-element.degrees {:title (tr "workspace.options.rotation")}
|
||||
|
@ -174,4 +274,240 @@
|
|||
:value (attr->string :rotation values)}]])
|
||||
|
||||
;; RADIUS
|
||||
[:& border-radius {:options options :ids-with-children ids-with-children :values values :ids ids}]]]]))
|
||||
(when (and (options :radius) (some? radius-mode))
|
||||
[:div.row-flex
|
||||
[:div.radius-options
|
||||
[:div.radius-icon.tooltip.tooltip-bottom
|
||||
{:class (dom/classnames
|
||||
:selected (or (= radius-mode :radius-1) @radius-multi?))
|
||||
:alt (tr "workspace.options.radius.all-corners")
|
||||
:on-click on-switch-to-radius-1}
|
||||
i/radius-1]
|
||||
[:div.radius-icon.tooltip.tooltip-bottom
|
||||
{:class (dom/classnames
|
||||
:selected (and (= radius-mode :radius-4) (not @radius-multi?)))
|
||||
:alt (tr "workspace.options.radius.single-corners")
|
||||
:on-click on-switch-to-radius-4}
|
||||
i/radius-4]]
|
||||
|
||||
(cond
|
||||
(= radius-mode :radius-1)
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:ref radius-input-ref
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-1-change
|
||||
:value (attr->string :rx values)}]]
|
||||
|
||||
@radius-multi?
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:input.input-text
|
||||
{:type "number"
|
||||
:placeholder "--"
|
||||
:on-click select-all
|
||||
:on-change on-radius-multi-change
|
||||
:value ""}]]
|
||||
|
||||
(= radius-mode :radius-4)
|
||||
[:*
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r1-change
|
||||
:value (attr->string :r1 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r2-change
|
||||
:value (attr->string :r2 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r3-change
|
||||
:value (attr->string :r3 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r4-change
|
||||
:value (attr->string :r4 values)}]]])])]]]))
|
||||
|
||||
(def +size-presets+
|
||||
[{:name "APPLE"}
|
||||
{:name "iPhone 12/12 Pro"
|
||||
:width 390
|
||||
:height 844}
|
||||
{:name "iPhone 12 Mini"
|
||||
:width 360
|
||||
:height 780}
|
||||
{:name "iPhone 12 Pro Max"
|
||||
:width 428
|
||||
:height 926}
|
||||
{:name "iPhone X/XS/11 Pro"
|
||||
:width 375
|
||||
:height 812}
|
||||
{:name "iPhone XS Max/XR/11"
|
||||
:width 414
|
||||
:height 896}
|
||||
{:name "iPhone 6/7/8 Plus"
|
||||
:width 414
|
||||
:height 736}
|
||||
{:name "iPhone 6/7/8/SE2"
|
||||
:width 375
|
||||
:height 667}
|
||||
{:name "iPhone 5/SE"
|
||||
:width 320
|
||||
:height 568}
|
||||
{:name "iPad"
|
||||
:width 768
|
||||
:height 1024}
|
||||
{:name "iPad Pro 10.5in"
|
||||
:width 834
|
||||
:height 1112}
|
||||
{:name "iPad Pro 12.9in"
|
||||
:width 1024
|
||||
:height 1366}
|
||||
{:name "Watch 44mm"
|
||||
:width 368
|
||||
:height 448}
|
||||
{:name "Watch 42mm"
|
||||
:width 312
|
||||
:height 390}
|
||||
{:name "Watch 40mm"
|
||||
:width 324
|
||||
:height 394}
|
||||
{:name "Watch 38mm"
|
||||
:width 272
|
||||
:height 340}
|
||||
|
||||
{:name "ANDROID"}
|
||||
{:name "Mobile"
|
||||
:width 360
|
||||
:height 640}
|
||||
{:name "Tablet"
|
||||
:width 768
|
||||
:height 1024}
|
||||
{:name "Google Pixel 4a/5"
|
||||
:width 393
|
||||
:height 851}
|
||||
{:name "Samsung Galaxy S20+"
|
||||
:width 384
|
||||
:height 854}
|
||||
{:name "Samsung Galaxy A71/A51"
|
||||
:width 412
|
||||
:height 914}
|
||||
|
||||
{:name "MICROSOFT"}
|
||||
{:name "Surface Pro 3"
|
||||
:width 1440
|
||||
:height 960}
|
||||
{:name "Surface Pro 4/5/6/7"
|
||||
:width 1368
|
||||
:height 912}
|
||||
|
||||
{:name "ReMarkable"}
|
||||
{:name "Remarkable 2"
|
||||
:width 840
|
||||
:height 1120}
|
||||
|
||||
{:name "WEB"}
|
||||
{:name "Web 1280"
|
||||
:width 1280
|
||||
:height 800}
|
||||
{:name "Web 1366"
|
||||
:width 1366
|
||||
:height 768}
|
||||
{:name "Web 1024"
|
||||
:width 1024
|
||||
:height 768}
|
||||
{:name "Web 1920"
|
||||
:width 1920
|
||||
:height 1080}
|
||||
|
||||
{:name "PRINT (96dpi)"}
|
||||
{:name "A0"
|
||||
:width 3179
|
||||
:height 4494}
|
||||
{:name "A1"
|
||||
:width 2245
|
||||
:height 3179}
|
||||
{:name "A2"
|
||||
:width 1587
|
||||
:height 2245}
|
||||
{:name "A3"
|
||||
:width 1123
|
||||
:height 1587}
|
||||
{:name "A4"
|
||||
:width 794
|
||||
:height 1123}
|
||||
{:name "A5"
|
||||
:width 559
|
||||
:height 794}
|
||||
{:name "A6"
|
||||
:width 397
|
||||
:height 559}
|
||||
{:name "Letter"
|
||||
:width 816
|
||||
:height 1054}
|
||||
{:name "DIN Lang"
|
||||
:width 835
|
||||
:height 413}
|
||||
|
||||
{:name "SOCIAL MEDIA"}
|
||||
{:name "Instagram profile"
|
||||
:width 320
|
||||
:height 320}
|
||||
{:name "Instagram post"
|
||||
:width 1080
|
||||
:height 1080}
|
||||
{:name "Instagram story"
|
||||
:width 1080
|
||||
:height 1920}
|
||||
{:name "Facebook profile"
|
||||
:width 720
|
||||
:height 720}
|
||||
{:name "Facebook cover"
|
||||
:width 820
|
||||
:height 312}
|
||||
{:name "Facebook post"
|
||||
:width 1200
|
||||
:height 630}
|
||||
{:name "LinkedIn profile"
|
||||
:width 400
|
||||
:height 400}
|
||||
{:name "LinkedIn cover"
|
||||
:width 1584
|
||||
:height 396}
|
||||
{:name "LinkedIn post"
|
||||
:width 1200
|
||||
:height 627}
|
||||
{:name "Twitter profile"
|
||||
:width 400
|
||||
:height 400}
|
||||
{:name "Twitter header"
|
||||
:width 1500
|
||||
:height 500}
|
||||
{:name "Twitter post"
|
||||
:width 1024
|
||||
:height 512}
|
||||
{:name "YouTube profile"
|
||||
:width 800
|
||||
:height 800}
|
||||
{:name "YouTube banner"
|
||||
:width 2560
|
||||
:height 1440}
|
||||
{:name "YouTube thumb"
|
||||
:width 1280
|
||||
:height 720}])
|
||||
|
|
|
@ -1,190 +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) UXBOX Labs SL
|
||||
|
||||
;; 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) UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.workspace.sidebar.options.menus.radius
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.math :as math]
|
||||
[app.common.spec.radius :as ctr]
|
||||
[app.main.data.workspace.changes :as dch]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
|
||||
(defn- attr->string [attr values]
|
||||
(let [value (attr values)]
|
||||
(if (= value :multiple)
|
||||
""
|
||||
(str (-> value
|
||||
(d/coalesce 0)
|
||||
(math/precision 2))))))
|
||||
|
||||
(mf/defc border-radius
|
||||
[{:keys [options ids ids-with-children values] :as props}]
|
||||
(let [options (or options #{:size :position :rotation :radius})
|
||||
|
||||
ids-with-children (or ids-with-children ids)
|
||||
|
||||
old-shapes (deref (refs/objects-by-id ids))
|
||||
frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes)
|
||||
|
||||
shapes (as-> old-shapes $
|
||||
(map gsh/transform-shape $)
|
||||
(map gsh/translate-to-frame $ frames))
|
||||
|
||||
values (let [{:keys [x y]} (-> shapes first :points gsh/points->selrect)]
|
||||
(cond-> values
|
||||
(not= (:x values) :multiple) (assoc :x x)
|
||||
(not= (:y values) :multiple) (assoc :y y)))
|
||||
|
||||
values (let [{:keys [width height]} (-> shapes first :selrect)]
|
||||
(cond-> values
|
||||
(not= (:width values) :multiple) (assoc :width width)
|
||||
(not= (:height values) :multiple) (assoc :height height)))
|
||||
|
||||
|
||||
radius-mode (ctr/radius-mode values)
|
||||
all-equal? (ctr/all-equal? values)
|
||||
radius-multi? (mf/use-state nil)
|
||||
radius-input-ref (mf/use-ref nil)
|
||||
|
||||
on-switch-to-radius-1
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [_value]
|
||||
(if all-equal?
|
||||
(st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-1))
|
||||
(reset! radius-multi? true))))
|
||||
|
||||
on-switch-to-radius-4
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [_value]
|
||||
(st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-4))
|
||||
(reset! radius-multi? false)))
|
||||
|
||||
on-radius-1-change
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [value]
|
||||
(st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-1 % value)))))
|
||||
|
||||
on-radius-multi-change
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [event]
|
||||
(let [value (-> event dom/get-target dom/get-value d/parse-integer)]
|
||||
(when (some? value)
|
||||
(st/emit! (dch/update-shapes ids-with-children ctr/switch-to-radius-1)
|
||||
(dch/update-shapes ids-with-children #(ctr/set-radius-1 % value)))
|
||||
(reset! radius-multi? false)))))
|
||||
|
||||
on-radius-4-change
|
||||
(mf/use-callback
|
||||
(mf/deps ids)
|
||||
(fn [value attr]
|
||||
(st/emit! (dch/update-shapes ids-with-children #(ctr/set-radius-4 % attr value)))))
|
||||
|
||||
on-radius-r1-change #(on-radius-4-change % :r1)
|
||||
on-radius-r2-change #(on-radius-4-change % :r2)
|
||||
on-radius-r3-change #(on-radius-4-change % :r3)
|
||||
on-radius-r4-change #(on-radius-4-change % :r4)
|
||||
|
||||
select-all #(-> % (dom/get-target) (.select))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps radius-mode @radius-multi?)
|
||||
(fn []
|
||||
(when (and (= radius-mode :radius-1)
|
||||
(= @radius-multi? false))
|
||||
;; when going back from radius-multi to normal radius-1,
|
||||
;; restore focus to the newly created numeric-input
|
||||
(let [radius-input (mf/ref-val radius-input-ref)]
|
||||
(dom/focus! radius-input)))))
|
||||
|
||||
[:*
|
||||
;; RADIUS
|
||||
(when (and (options :radius) (some? radius-mode))
|
||||
[:div.row-flex
|
||||
[:div.radius-options
|
||||
[:div.radius-icon.tooltip.tooltip-bottom
|
||||
{:class (dom/classnames
|
||||
:selected (or (= radius-mode :radius-1) @radius-multi?))
|
||||
:alt (tr "workspace.options.radius.all-corners")
|
||||
:on-click on-switch-to-radius-1}
|
||||
i/radius-1]
|
||||
[:div.radius-icon.tooltip.tooltip-bottom
|
||||
{:class (dom/classnames
|
||||
:selected (and (= radius-mode :radius-4) (not @radius-multi?)))
|
||||
:alt (tr "workspace.options.radius.single-corners")
|
||||
:on-click on-switch-to-radius-4}
|
||||
i/radius-4]]
|
||||
|
||||
(cond
|
||||
(= radius-mode :radius-1)
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:ref radius-input-ref
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-1-change
|
||||
:value (attr->string :rx values)}]]
|
||||
|
||||
@radius-multi?
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:input.input-text
|
||||
{:type "number"
|
||||
:placeholder "--"
|
||||
:on-click select-all
|
||||
:on-change on-radius-multi-change
|
||||
:value ""}]]
|
||||
|
||||
(= radius-mode :radius-4)
|
||||
[:*
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r1-change
|
||||
:value (attr->string :r1 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r2-change
|
||||
:value (attr->string :r2 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r3-change
|
||||
:value (attr->string :r3 values)}]]
|
||||
|
||||
[:div.input-element.mini {:title (tr "workspace.options.radius")}
|
||||
[:> numeric-input
|
||||
{:placeholder "--"
|
||||
:min 0
|
||||
:on-click select-all
|
||||
:on-change on-radius-r4-change
|
||||
:value (attr->string :r4 values)}]]])])]))
|
|
@ -6,308 +6,27 @@
|
|||
|
||||
(ns app.main.ui.workspace.sidebar.options.shapes.frame
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.math :as math]
|
||||
[app.main.data.workspace :as udw]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input]]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.radius :refer [border-radius]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(declare +size-presets+)
|
||||
|
||||
|
||||
(mf/defc measures-menu
|
||||
[{:keys [shape] :as props}]
|
||||
|
||||
(let [show-presets-dropdown? (mf/use-state false)
|
||||
id (:id shape)
|
||||
|
||||
on-preset-selected
|
||||
(fn [width height]
|
||||
(st/emit! (udw/update-dimensions [(:id shape)] :width width)
|
||||
(udw/update-dimensions [(:id shape)] :height height)))
|
||||
|
||||
on-orientation-clicked
|
||||
(fn [orientation]
|
||||
(let [width (:width shape)
|
||||
height (:height shape)
|
||||
new-width (if (= orientation :horiz) (max width height) (min width height))
|
||||
new-height (if (= orientation :horiz) (min width height) (max width height))]
|
||||
(st/emit! (udw/update-dimensions [(:id shape)] :width new-width)
|
||||
(udw/update-dimensions [(:id shape)] :height new-height))))
|
||||
|
||||
on-size-change
|
||||
(fn [value attr]
|
||||
(st/emit! (udw/update-dimensions [(:id shape)] attr value)))
|
||||
|
||||
on-proportion-lock-change
|
||||
(fn [_]
|
||||
(st/emit! (udw/set-shape-proportion-lock (:id shape) (not (:proportion-lock shape)))))
|
||||
|
||||
on-position-change
|
||||
(fn [value attr]
|
||||
(st/emit! (udw/update-position (:id shape) {attr value})))
|
||||
|
||||
on-width-change #(on-size-change % :width)
|
||||
on-height-change #(on-size-change % :height)
|
||||
on-pos-x-change #(on-position-change % :x)
|
||||
on-pos-y-change #(on-position-change % :y)
|
||||
select-all #(-> % (dom/get-target) (.select))]
|
||||
|
||||
[:div.element-set
|
||||
[:div.element-set-content
|
||||
[:div.row-flex
|
||||
[:div.presets.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)
|
||||
[:li.dropdown-label {:key (:name size-preset)}
|
||||
[:span (:name size-preset)]]
|
||||
[:li {:key (:name size-preset)
|
||||
:on-click #(on-preset-selected (:width size-preset) (:height size-preset))}
|
||||
(:name size-preset)
|
||||
[:span (:width size-preset) " x " (:height size-preset)]]))]]]
|
||||
[:span.orientation-icon {:on-click #(on-orientation-clicked :vert)} i/size-vert]
|
||||
[:span.orientation-icon {:on-click #(on-orientation-clicked :horiz)} i/size-horiz]]
|
||||
|
||||
;; WIDTH & HEIGHT
|
||||
[:div.row-flex
|
||||
[:span.element-set-subtitle (tr "workspace.options.size")]
|
||||
[:div.input-element.pixels {:title (tr "workspace.options.width")}
|
||||
[:> numeric-input {:min 1
|
||||
:on-click select-all
|
||||
:on-change on-width-change
|
||||
:value (-> (:width shape)
|
||||
(math/precision 2)
|
||||
(d/coalesce-str "1"))}]]
|
||||
|
||||
[:div.input-element.pixels {:title (tr "workspace.options.height")}
|
||||
[:> numeric-input {:min 1
|
||||
:on-click select-all
|
||||
:on-change on-height-change
|
||||
:value (-> (:height shape)
|
||||
(math/precision 2)
|
||||
(d/coalesce-str "1"))}]]
|
||||
|
||||
[:div.lock-size {:class (when (:proportion-lock shape) "selected")
|
||||
:on-click on-proportion-lock-change}
|
||||
(if (:proportion-lock shape)
|
||||
i/lock
|
||||
i/unlock)]]
|
||||
|
||||
;; POSITION
|
||||
[:div.row-flex
|
||||
[:span.element-set-subtitle (tr "workspace.options.position")]
|
||||
[:div.input-element.pixels {:title (tr "workspace.options.x")}
|
||||
[:> numeric-input {:placeholder "x"
|
||||
:on-click select-all
|
||||
:on-change on-pos-x-change
|
||||
:value (-> (:x shape)
|
||||
(math/precision 2)
|
||||
(d/coalesce-str "0"))}]]
|
||||
[:div.input-element.pixels {:title (tr "workspace.options.y")}
|
||||
[:> numeric-input {:placeholder "y"
|
||||
:on-click select-all
|
||||
:on-change on-pos-y-change
|
||||
:value (-> (:y shape)
|
||||
(math/precision 2)
|
||||
(d/coalesce-str "0"))}]]]
|
||||
|
||||
;; RADIUS
|
||||
[:& border-radius {:ids [id] :values shape}]]]))
|
||||
|
||||
(def +size-presets+
|
||||
[{:name "APPLE"}
|
||||
{:name "iPhone 12/12 Pro"
|
||||
:width 390
|
||||
:height 844}
|
||||
{:name "iPhone 12 Mini"
|
||||
:width 360
|
||||
:height 780}
|
||||
{:name "iPhone 12 Pro Max"
|
||||
:width 428
|
||||
:height 926}
|
||||
{:name "iPhone X/XS/11 Pro"
|
||||
:width 375
|
||||
:height 812}
|
||||
{:name "iPhone XS Max/XR/11"
|
||||
:width 414
|
||||
:height 896}
|
||||
{:name "iPhone 6/7/8 Plus"
|
||||
:width 414
|
||||
:height 736}
|
||||
{:name "iPhone 6/7/8/SE2"
|
||||
:width 375
|
||||
:height 667}
|
||||
{:name "iPhone 5/SE"
|
||||
:width 320
|
||||
:height 568}
|
||||
{:name "iPad"
|
||||
:width 768
|
||||
:height 1024}
|
||||
{:name "iPad Pro 10.5in"
|
||||
:width 834
|
||||
:height 1112}
|
||||
{:name "iPad Pro 12.9in"
|
||||
:width 1024
|
||||
:height 1366}
|
||||
{:name "Watch 44mm"
|
||||
:width 368
|
||||
:height 448}
|
||||
{:name "Watch 42mm"
|
||||
:width 312
|
||||
:height 390}
|
||||
{:name "Watch 40mm"
|
||||
:width 324
|
||||
:height 394}
|
||||
{:name "Watch 38mm"
|
||||
:width 272
|
||||
:height 340}
|
||||
|
||||
{:name "ANDROID"}
|
||||
{:name "Mobile"
|
||||
:width 360
|
||||
:height 640}
|
||||
{:name "Tablet"
|
||||
:width 768
|
||||
:height 1024}
|
||||
{:name "Google Pixel 4a/5"
|
||||
:width 393
|
||||
:height 851}
|
||||
{:name "Samsung Galaxy S20+"
|
||||
:width 384
|
||||
:height 854}
|
||||
{:name "Samsung Galaxy A71/A51"
|
||||
:width 412
|
||||
:height 914}
|
||||
|
||||
{:name "MICROSOFT"}
|
||||
{:name "Surface Pro 3"
|
||||
:width 1440
|
||||
:height 960}
|
||||
{:name "Surface Pro 4/5/6/7"
|
||||
:width 1368
|
||||
:height 912}
|
||||
|
||||
{:name "ReMarkable"}
|
||||
{:name "Remarkable 2"
|
||||
:width 840
|
||||
:height 1120}
|
||||
|
||||
{:name "WEB"}
|
||||
{:name "Web 1280"
|
||||
:width 1280
|
||||
:height 800}
|
||||
{:name "Web 1366"
|
||||
:width 1366
|
||||
:height 768}
|
||||
{:name "Web 1024"
|
||||
:width 1024
|
||||
:height 768}
|
||||
{:name "Web 1920"
|
||||
:width 1920
|
||||
:height 1080}
|
||||
|
||||
{:name "PRINT (96dpi)"}
|
||||
{:name "A0"
|
||||
:width 3179
|
||||
:height 4494}
|
||||
{:name "A1"
|
||||
:width 2245
|
||||
:height 3179}
|
||||
{:name "A2"
|
||||
:width 1587
|
||||
:height 2245}
|
||||
{:name "A3"
|
||||
:width 1123
|
||||
:height 1587}
|
||||
{:name "A4"
|
||||
:width 794
|
||||
:height 1123}
|
||||
{:name "A5"
|
||||
:width 559
|
||||
:height 794}
|
||||
{:name "A6"
|
||||
:width 397
|
||||
:height 559}
|
||||
{:name "Letter"
|
||||
:width 816
|
||||
:height 1054}
|
||||
{:name "DIN Lang"
|
||||
:width 835
|
||||
:height 413}
|
||||
|
||||
{:name "SOCIAL MEDIA"}
|
||||
{:name "Instagram profile"
|
||||
:width 320
|
||||
:height 320}
|
||||
{:name "Instagram post"
|
||||
:width 1080
|
||||
:height 1080}
|
||||
{:name "Instagram story"
|
||||
:width 1080
|
||||
:height 1920}
|
||||
{:name "Facebook profile"
|
||||
:width 720
|
||||
:height 720}
|
||||
{:name "Facebook cover"
|
||||
:width 820
|
||||
:height 312}
|
||||
{:name "Facebook post"
|
||||
:width 1200
|
||||
:height 630}
|
||||
{:name "LinkedIn profile"
|
||||
:width 400
|
||||
:height 400}
|
||||
{:name "LinkedIn cover"
|
||||
:width 1584
|
||||
:height 396}
|
||||
{:name "LinkedIn post"
|
||||
:width 1200
|
||||
:height 627}
|
||||
{:name "Twitter profile"
|
||||
:width 400
|
||||
:height 400}
|
||||
{:name "Twitter header"
|
||||
:width 1500
|
||||
:height 500}
|
||||
{:name "Twitter post"
|
||||
:width 1024
|
||||
:height 512}
|
||||
{:name "YouTube profile"
|
||||
:width 800
|
||||
:height 800}
|
||||
{:name "YouTube banner"
|
||||
:width 2560
|
||||
:height 1440}
|
||||
{:name "YouTube thumb"
|
||||
:width 1280
|
||||
:height 720}
|
||||
])
|
||||
|
||||
(mf/defc options
|
||||
[{:keys [shape] :as props}]
|
||||
(let [ids [(:id shape)]
|
||||
type (:type shape)
|
||||
stroke-values (select-keys shape stroke-attrs)
|
||||
layer-values (select-keys shape layer-attrs)]
|
||||
layer-values (select-keys shape layer-attrs)
|
||||
measure-values (select-keys shape measure-attrs)]
|
||||
[:*
|
||||
[:& measures-menu {:shape shape}]
|
||||
[:& measures-menu {:ids [(:id shape)]
|
||||
:values measure-values
|
||||
:type type
|
||||
:options #{:size :position :rotation :presets :radius}}]
|
||||
[:& layer-menu {:ids ids
|
||||
:type type
|
||||
:values layer-values}]
|
||||
|
|
|
@ -51,7 +51,12 @@
|
|||
(defn find-all-nodes
|
||||
[node tag]
|
||||
(when (some? node)
|
||||
(->> node :content (filterv #(= (:tag %) tag)))))
|
||||
(let [predicate?
|
||||
(if (set? tag)
|
||||
;; We can pass a tag set or a single tag
|
||||
#(contains? tag (:tag %))
|
||||
#(= (:tag %) tag))]
|
||||
(->> node :content (filterv predicate?)))))
|
||||
|
||||
(defn get-data
|
||||
([node]
|
||||
|
@ -211,7 +216,8 @@
|
|||
(let [;; The nodes with the "frame-background" class can have some anidation depending on the strokes they have
|
||||
g-nodes (find-all-nodes node :g)
|
||||
defs-nodes (flatten (map #(find-all-nodes % :defs) g-nodes))
|
||||
rect-nodes (flatten [(map #(find-all-nodes % :rect) defs-nodes) (map #(find-all-nodes % :rect) g-nodes)])
|
||||
rect-nodes (flatten [(map #(find-all-nodes % #{:rect :path}) defs-nodes)
|
||||
(map #(find-all-nodes % #{:rect :path}) g-nodes)])
|
||||
svg-node (d/seek #(= "frame-background" (get-in % [:attrs :class])) rect-nodes)]
|
||||
(merge (add-attrs {} (:attrs svg-node)) node-attrs))
|
||||
|
||||
|
@ -458,15 +464,15 @@
|
|||
(some? stroke-cap-end)
|
||||
(assoc :stroke-cap-end stroke-cap-end))))
|
||||
|
||||
(defn add-rect-data
|
||||
(defn add-radius-data
|
||||
[props node svg-data]
|
||||
(let [r1 (get-meta node :r1 d/parse-double)
|
||||
r2 (get-meta node :r2 d/parse-double)
|
||||
r3 (get-meta node :r3 d/parse-double)
|
||||
r4 (get-meta node :r4 d/parse-double)
|
||||
|
||||
rx (-> (get svg-data :rx) d/parse-double)
|
||||
ry (-> (get svg-data :ry) d/parse-double)]
|
||||
rx (-> (get svg-data :rx 0) d/parse-double)
|
||||
ry (-> (get svg-data :ry 0) d/parse-double)]
|
||||
|
||||
(cond-> props
|
||||
(some? r1)
|
||||
|
@ -817,12 +823,12 @@
|
|||
(cond-> (= :group type)
|
||||
(add-group-data node))
|
||||
|
||||
(cond-> (= :rect type)
|
||||
(add-rect-data node svg-data))
|
||||
(cond-> (or (= :frame type) (= :rect type))
|
||||
(add-radius-data node svg-data))
|
||||
|
||||
(cond-> (some? (get-in node [:attrs :penpot:media-id]))
|
||||
(->
|
||||
(add-rect-data node svg-data)
|
||||
(add-radius-data node svg-data)
|
||||
(add-image-data type node)))
|
||||
|
||||
(cond-> (= :text type)
|
||||
|
|
Loading…
Add table
Reference in a new issue