0
Fork 0
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:
alonso.torres 2022-02-28 18:00:48 +01:00 committed by Alonso Torres
parent d4af28c52b
commit 17fc15138a
10 changed files with 386 additions and 505 deletions

View file

@ -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

View file

@ -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)))

View file

@ -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]))

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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}])

View file

@ -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)}]]])])]))

View file

@ -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}]

View file

@ -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)