mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
Signed-off-by: RenzoMXD <170978465+RenzoMXD@users.noreply.github.com>
This commit is contained in:
parent
3fd976c551
commit
5bbb2c5cff
@ -50,6 +50,7 @@
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix Copy as SVG: emit a single valid SVG document when multiple shapes are selected, and publish `image/svg+xml` to the clipboard so the paste target works in Inkscape and other SVG-native tools [Github #838](https://github.com/penpot/penpot/issues/838)
|
||||
- Reset profile submenu state when the account menu closes (by @eureka0928) [Github #8947](https://github.com/penpot/penpot/issues/8947)
|
||||
- Add export panel to inspect styles tab [Taiga #13582](https://tree.taiga.io/project/penpot/issue/13582)
|
||||
- Fix styles between grid layout inputs [Taiga #13526](https://tree.taiga.io/project/penpot/issue/13526)
|
||||
|
||||
@ -358,7 +358,9 @@
|
||||
|
||||
shapes (mapv maybe-translate selected)
|
||||
svg-formatted (svg/generate-formatted-markup objects shapes)]
|
||||
(clipboard/to-clipboard svg-formatted)))))
|
||||
(clipboard/to-clipboard-multi
|
||||
{"image/svg+xml" svg-formatted
|
||||
"text/plain" svg-formatted})))))
|
||||
|
||||
(defn copy-selected-css
|
||||
[]
|
||||
|
||||
@ -484,6 +484,46 @@
|
||||
[:& ff/fontfaces-style {:fonts fonts}]
|
||||
[:& shape-wrapper {:shape object}]]]]))
|
||||
|
||||
(mf/defc objects-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [objects object-ids embed] :or {embed false} :as props}]
|
||||
(let [shapes
|
||||
(->> object-ids
|
||||
(keep #(get objects %))
|
||||
(mapv (fn [object]
|
||||
(cond-> object
|
||||
(:hide-fill-on-export object)
|
||||
(assoc :fills [])))))
|
||||
|
||||
bounds
|
||||
(->> shapes
|
||||
(map #(gsb/get-object-bounds objects % {:ignore-margin? false}))
|
||||
(grc/join-rects))
|
||||
|
||||
{:keys [width height]} bounds
|
||||
vbox (format-viewbox bounds)
|
||||
fonts (->> shapes
|
||||
(mapcat #(ff/shape->fonts % objects))
|
||||
(distinct))
|
||||
|
||||
shape-wrapper
|
||||
(mf/with-memo [objects]
|
||||
(shape-wrapper-factory objects))]
|
||||
|
||||
[:& (mf/provider export/include-metadata-ctx) {:value false}
|
||||
[:& (mf/provider embed/context) {:value embed}
|
||||
[:svg {:view-box vbox
|
||||
:width (ust/format-precision width viewbox-decimal-precision)
|
||||
:height (ust/format-precision height viewbox-decimal-precision)
|
||||
:version "1.1"
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:style {:-webkit-print-color-adjust :exact}
|
||||
:fill "none"}
|
||||
[:& ff/fontfaces-style {:fonts fonts}]
|
||||
(for [shape shapes]
|
||||
[:& shape-wrapper {:key (dm/str (:id shape)) :shape shape}])]]]))
|
||||
|
||||
(defn render-to-canvas
|
||||
[objects canvas bounds scale object-id on-render]
|
||||
(let [width (.-width canvas)
|
||||
|
||||
@ -88,3 +88,22 @@
|
||||
(let [clipboard (unchecked-get js/navigator "clipboard")
|
||||
data (create-clipboard-item mimetype promise)]
|
||||
(.write ^js clipboard #js [data])))
|
||||
|
||||
(defn to-clipboard-multi
|
||||
"Write multiple MIME representations as a single ClipboardItem.
|
||||
`items` is a map of mime-type (string) -> string payload.
|
||||
Falls back to `writeText` when the async Clipboard API is unavailable."
|
||||
[items]
|
||||
(let [clipboard (unchecked-get js/navigator "clipboard")]
|
||||
(if (and clipboard (unchecked-get clipboard "write"))
|
||||
(let [obj (reduce-kv
|
||||
(fn [acc mime payload]
|
||||
(let [blob (js/Blob. #js [payload] #js {:type mime})]
|
||||
(unchecked-set acc mime (js/Promise.resolve blob))
|
||||
acc))
|
||||
#js {} items)
|
||||
item (js/ClipboardItem. obj)]
|
||||
(.write ^js clipboard #js [item]))
|
||||
(when-let [text (or (get items "text/plain")
|
||||
(first (vals items)))]
|
||||
(.writeText ^js clipboard text)))))
|
||||
|
||||
@ -9,10 +9,9 @@
|
||||
["react-dom/server" :as rds]
|
||||
[app.main.render :as render]
|
||||
[app.util.code-beautify :as cb]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn generate-svg
|
||||
(defn- generate-single-svg
|
||||
[objects shape]
|
||||
(rds/renderToStaticMarkup
|
||||
(mf/element
|
||||
@ -20,13 +19,26 @@
|
||||
#js {:objects objects
|
||||
:object-id (-> shape :id)})))
|
||||
|
||||
(defn- generate-multi-svg
|
||||
[objects shapes]
|
||||
(rds/renderToStaticMarkup
|
||||
(mf/element
|
||||
render/objects-svg
|
||||
#js {:objects objects
|
||||
:object-ids (mapv :id shapes)})))
|
||||
|
||||
(defn generate-svg
|
||||
[objects shape]
|
||||
(generate-single-svg objects shape))
|
||||
|
||||
(defn generate-markup
|
||||
[objects shapes]
|
||||
(->> shapes
|
||||
(map #(generate-svg objects %))
|
||||
(str/join "\n")))
|
||||
(case (count shapes)
|
||||
0 ""
|
||||
1 (generate-single-svg objects (first shapes))
|
||||
(generate-multi-svg objects shapes)))
|
||||
|
||||
(defn generate-formatted-markup
|
||||
[objects shapes]
|
||||
(let [markup (generate-markup objects shapes)]
|
||||
(cb/format-code markup "svg")))
|
||||
(-> (generate-markup objects shapes)
|
||||
(cb/format-code "svg")))
|
||||
|
||||
61
frontend/test/frontend_tests/copy_as_svg_test.cljs
Normal file
61
frontend/test/frontend_tests/copy_as_svg_test.cljs
Normal file
@ -0,0 +1,61 @@
|
||||
;; 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) KALEIDOS INC
|
||||
|
||||
(ns frontend-tests.copy-as-svg-test
|
||||
"Regression tests for the Copy as SVG action (issue #838).
|
||||
|
||||
The bug: when multiple shapes were selected, `generate-markup` emitted
|
||||
several sibling `<svg>` roots concatenated with newlines. External SVG
|
||||
parsers (Inkscape, browsers) only read the first root, so multi-shape
|
||||
selection appeared to copy only one shape. The fix wraps 2+ shapes in a
|
||||
single `<svg>` root with a combined viewBox."
|
||||
(:require
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.util.code-gen.markup-svg :as svg]
|
||||
[cljs.test :refer [deftest is testing] :include-macros true]))
|
||||
|
||||
(defn- setup-shapes
|
||||
"Build a file with `n` sample rectangles on the current page.
|
||||
Returns a map with `:objects` and `:shapes` keys, mirroring the inputs
|
||||
that `copy-selected-svg` feeds into `generate-markup`."
|
||||
[labels]
|
||||
(let [file (reduce (fn [f label]
|
||||
(cths/add-sample-shape f label))
|
||||
(cthf/sample-file :file1 :page-label :page1)
|
||||
labels)
|
||||
page (cthf/current-page file)
|
||||
objects (:objects page)
|
||||
shapes (mapv #(get objects (cthi/id %)) labels)]
|
||||
{:objects objects :shapes shapes}))
|
||||
|
||||
(defn- count-matches
|
||||
[re s]
|
||||
(count (re-seq re s)))
|
||||
|
||||
(deftest empty-selection-yields-empty-string
|
||||
(is (= "" (svg/generate-markup {} []))))
|
||||
|
||||
(deftest single-shape-produces-one-svg-root
|
||||
(testing "Regression guard: the single-shape path stays unchanged"
|
||||
(let [{:keys [objects shapes]} (setup-shapes [:rect-1])
|
||||
markup (svg/generate-markup objects shapes)]
|
||||
(is (string? markup))
|
||||
(is (pos? (count markup)))
|
||||
(is (= 1 (count-matches #"<svg\b" markup))
|
||||
"single shape should produce exactly one <svg> root"))))
|
||||
|
||||
(deftest multi-shape-produces-single-svg-root
|
||||
(testing "Fix for #838: multiple shapes share one outer <svg>"
|
||||
(let [{:keys [objects shapes]} (setup-shapes [:rect-1 :rect-2 :rect-3])
|
||||
markup (svg/generate-markup objects shapes)]
|
||||
(is (string? markup))
|
||||
(is (pos? (count markup)))
|
||||
(is (= 1 (count-matches #"<svg\b" markup))
|
||||
"multi-select must NOT emit multiple <svg> roots")
|
||||
(is (= 1 (count-matches #"</svg>" markup))
|
||||
"multi-select must NOT emit multiple </svg> closing tags"))))
|
||||
@ -2,6 +2,7 @@
|
||||
(:require
|
||||
[cljs.test :as t]
|
||||
[frontend-tests.basic-shapes-test]
|
||||
[frontend-tests.copy-as-svg-test]
|
||||
[frontend-tests.data.repo-test]
|
||||
[frontend-tests.data.uploads-test]
|
||||
[frontend-tests.data.viewer-test]
|
||||
@ -44,6 +45,7 @@
|
||||
[]
|
||||
(t/run-tests
|
||||
'frontend-tests.basic-shapes-test
|
||||
'frontend-tests.copy-as-svg-test
|
||||
'frontend-tests.data.repo-test
|
||||
'frontend-tests.errors-test
|
||||
'frontend-tests.main-errors-test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user