From 17fc15138a997b00d80f919d8721e237ad749f1d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 28 Feb 2022 18:00:48 +0100 Subject: [PATCH] :sparkles: Add suport to export/import frames with radius --- common/src/app/common/pages/common.cljc | 3 +- common/src/app/common/spec/radius.cljc | 20 +- .../app/main/data/workspace/svg_upload.cljs | 6 +- frontend/src/app/main/ui/shapes/attrs.cljs | 4 +- frontend/src/app/main/ui/shapes/export.cljs | 3 +- .../ui/viewer/handoff/attributes/layout.cljs | 2 +- .../sidebar/options/menus/measures.cljs | 346 +++++++++++++++++- .../sidebar/options/menus/radius.cljs | 190 ---------- .../sidebar/options/shapes/frame.cljs | 295 +-------------- frontend/src/app/util/import/parser.cljs | 22 +- 10 files changed, 386 insertions(+), 505 deletions(-) delete mode 100644 frontend/src/app/main/ui/workspace/sidebar/options/menus/radius.cljs diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index 2b4e9cbc5..1339a9b6c 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -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 diff --git a/common/src/app/common/spec/radius.cljc b/common/src/app/common/spec/radius.cljc index 91f0eb78b..e6754f321 100644 --- a/common/src/app/common/spec/radius.cljc +++ b/common/src/app/common/spec/radius.cljc @@ -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))) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index b90258b1f..021da3dee 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -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])) diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index e1c01012f..b6e18d862 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -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) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 4a7180cdd..c5b548ccf 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -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) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs index abf9fc37b..b2da8eba3 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs @@ -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) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index b7daf4886..968a69840 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -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}]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/radius.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/radius.cljs deleted file mode 100644 index ff2920157..000000000 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/radius.cljs +++ /dev/null @@ -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)}]]])])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 97b16654a..96e298cfc 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -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}] diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index 123856b2b..05995df32 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -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)