diff --git a/CHANGES.md b/CHANGES.md index 455b60007f..49c2a820ea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -83,6 +83,7 @@ on-premises instances** that want to keep up to date. - Fix entering long project name [Taiga #11417](https://tree.taiga.io/project/penpot/issue/11417) - Fix slow color picker [Taiga #11019](https://tree.taiga.io/project/penpot/issue/11019) - Fix tooltip position after click [Taiga #11405](https://tree.taiga.io/project/penpot/issue/11405) +- Fix incorrect media translation on paste text with fill images [Github #6845](https://github.com/penpot/penpot/pull/6845) ## 2.7.2 diff --git a/backend/src/app/binfile/v3.clj b/backend/src/app/binfile/v3.clj index 70a9960e69..7208a54f39 100644 --- a/backend/src/app/binfile/v3.clj +++ b/backend/src/app/binfile/v3.clj @@ -12,7 +12,6 @@ [app.binfile.common :as bfc] [app.binfile.migrations :as bfm] [app.common.data :as d] - [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.features :as cfeat] [app.common.files.migrations :as-alias fmg] @@ -54,7 +53,7 @@ [:map {:title "Manifest"} [:version ::sm/int] [:type :string] - + [:referer {:optional true} :string] [:generated-by {:optional true} :string] [:files @@ -373,6 +372,7 @@ params {:type "penpot/export-files" :version 1 :generated-by (str "penpot/" (:full cf/version)) + :refer "penpot" :files (vec (vals files)) :relations rels}] (write-entry! output "manifest.json" params)))) @@ -878,13 +878,8 @@ (defn- import-files [{:keys [::bfc/timestamp ::bfc/input ::bfc/name] :or {timestamp (dt/now)} :as cfg}] - (dm/assert! - "expected zip file" - (instance? ZipFile input)) - - (dm/assert! - "expected valid instant" - (dt/instant? timestamp)) + (assert (instance? ZipFile input) "expected zip file") + (assert (dt/instant? timestamp) "expected valid instant") (let [manifest (-> (read-manifest input) (validate-manifest)) @@ -896,6 +891,7 @@ :hint "unexpected type on manifest" :manifest manifest)) + ;; Check if all files referenced on manifest are present (doseq [{file-id :id features :features} (:files manifest)] (let [path (str "files/" file-id ".json")] @@ -956,14 +952,13 @@ [{:keys [::bfc/ids] :as cfg} output] - (dm/assert! - "expected a set of uuid's for `::bfc/ids` parameter" - (and (set? ids) - (every? uuid? ids))) + (assert + (and (set? ids) (every? uuid? ids)) + "expected a set of uuid's for `::bfc/ids` parameter") - (dm/assert! - "expected instance of jio/IOFactory for `input`" - (satisfies? jio/IOFactory output)) + (assert + (satisfies? jio/IOFactory output) + "expected instance of jio/IOFactory for `input`") (let [id (uuid/next) tp (dt/tpoint) @@ -1002,14 +997,14 @@ (defn import-files! [{:keys [::bfc/input] :as cfg}] - (dm/assert! - "expected valid profile-id and project-id on `cfg`" + (assert (and (uuid? (::bfc/profile-id cfg)) - (uuid? (::bfc/project-id cfg)))) + (uuid? (::bfc/project-id cfg))) + "expected valid profile-id and project-id on `cfg`") - (dm/assert! - "expected instance of jio/IOFactory for `input`" - (io/coercible? input)) + (assert + (io/coercible? input) + "expected instance of jio/IOFactory for `input`") (let [id (uuid/next) tp (dt/tpoint) @@ -1029,3 +1024,9 @@ :id (str id) :elapsed (dt/format-duration (tp)) :error? (some? @cs)))))) + +(defn get-manifest + [path] + (with-open [input (ZipFile. (fs/file path))] + (-> (read-manifest input) + (validate-manifest)))) diff --git a/backend/src/app/loggers/database.clj b/backend/src/app/loggers/database.clj index d15d5704f1..3bddbbe628 100644 --- a/backend/src/app/loggers/database.clj +++ b/backend/src/app/loggers/database.clj @@ -67,7 +67,7 @@ (some-> cause (ex/format-throwable :data? true :explain? false :header? false :summary? false)))} (when-let [params (or (:request/params context) (:params context))] - {:params (pp/pprint-str params :length 20 :level 15)}) + {:params (pp/pprint-str params :length 20 :level 20)}) (when-let [value (:value context)] {:value (pp/pprint-str value :length 30 :level 13)}) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 60b524ee38..0dc301c71e 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -134,11 +134,18 @@ ::webhooks/event? true ::sse/stream? true ::sm/params schema:import-binfile} - [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id project-id version] :as params}] + [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id project-id version file] :as params}] (projects/check-edition-permissions! pool profile-id project-id) - (let [params (-> params - (assoc :profile-id profile-id) - (assoc :version (or version 1)))] + (let [version (or version 1) + params (-> params + (assoc :profile-id profile-id) + (assoc :version version)) + manifest (case (int version) + 1 nil + 3 (bf.v3/get-manifest (:path file)))] + (with-meta (sse/response (partial import-binfile cfg params)) - {::audit/props {:file nil}}))) + {::audit/props {:file nil + :generated-by (:generated-by manifest) + :referer (:referer manifest)}}))) diff --git a/common/src/app/common/text.cljc b/common/src/app/common/text.cljc index 9e635f6a03..469fda6eb3 100644 --- a/common/src/app/common/text.cljc +++ b/common/src/app/common/text.cljc @@ -170,19 +170,6 @@ item)) root))) -(defn xform-nodes - "The same as transform but instead of receiving a funcion, receives - a transducer." - [xf root] - (let [rf (fn [_ v] v)] - (walk/postwalk - (fn [item] - (let [rf (xf rf)] - (if (is-node? item) - (d/nilv (rf nil item) item) - item))) - root))) - (defn update-text-content [shape pred-fn update-fn attrs] (let [update-attrs-fn #(update-fn % attrs) diff --git a/common/src/app/common/types/path.cljc b/common/src/app/common/types/path.cljc index 017a8df15d..bcfc69fbcb 100644 --- a/common/src/app/common/types/path.cljc +++ b/common/src/app/common/types/path.cljc @@ -209,25 +209,16 @@ :code :invalid-path-content :hint (str "unable to calculate bool content for shape " (:id shape)) :shapes (:shapes shape) - :content (mapv str contents) + :type (:bool-type shape) + :content (vec contents) :cause cause))))) (defn calc-bool-content "Calculate the boolean content from shape and objects. Returns a packed PathData instance" [shape objects] - (ex/try! - (-> (calc-bool-content* shape objects) - (impl/path-data)) - - :on-exception - (fn [cause] - (ex/raise :type :internal - :code :invalid-path-content - :hint (str "unable to create bool content for shape " (:id shape)) - :content (str (:content shape)) - :shape-id (:id shape) - :cause cause)))) + (-> (calc-bool-content* shape objects) + (impl/path-data))) (defn update-bool-shape "Calculates the selrect+points for the boolean shape" diff --git a/common/src/app/common/types/path/helpers.cljc b/common/src/app/common/types/path/helpers.cljc index 389b6cf8e1..1f1218efd9 100644 --- a/common/src/app/common/types/path/helpers.cljc +++ b/common/src/app/common/types/path/helpers.cljc @@ -27,13 +27,11 @@ (defn make-move-to [to] {:command :move-to - :relative false :params {:x (:x to) :y (:y to)}}) (defn make-line-to [to] {:command :line-to - :relative false :params {:x (:x to) :y (:y to)}}) @@ -65,7 +63,6 @@ (defn make-curve-to [to h1 h2] {:command :curve-to - :relative false :params (make-curve-params to h1 h2)}) (defn prefix->coords [prefix] @@ -98,7 +95,7 @@ (defn segment->point ([segment] (segment->point segment :x)) ([segment coord] - (when-let [params (get segment :params)] + (when-let [params (not-empty (get segment :params))] (case coord :c1 (gpt/point (get params :c1x) (get params :c1y)) diff --git a/common/test/common_tests/types/path_data_test.cljc b/common/test/common_tests/types/path_data_test.cljc index aef996608a..b371b17ef8 100644 --- a/common/test/common_tests/types/path_data_test.cljc +++ b/common/test/common_tests/types/path_data_test.cljc @@ -14,6 +14,7 @@ [app.common.pprint :as pp] [app.common.transit :as trans] [app.common.types.path :as path] + [app.common.types.path.bool :as path.bool] [app.common.types.path.helpers :as path.helpers] [app.common.types.path.impl :as path.impl] [app.common.types.path.segment :as path.segment] @@ -414,3 +415,89 @@ result1 (get-handlers sample-content-large) result2 (path.segment/get-handlers content)] (t/is (= result1 result2)))) + + +(def contents-for-bool + [[{:command :move-to, :params {:x 1682.9000244140625, :y 48.0}} + {:command :line-to, :params {:x 1682.9000244140625, :y 44.0}} + {:command :curve-to, :params {:x 1683.9000244140625, :y 43.0, :c1x 1682.9000244140625, :c1y 43.400001525878906, :c2x 1683.300048828125, :c2y 43.0}} + {:command :line-to, :params {:x 1687.9000244140625, :y 43.0}} + {:command :curve-to, :params {:x 1688.9000244140625, :y 44.0, :c1x 1688.5, :c1y 43.0, :c2x 1688.9000244140625, :c2y 43.400001525878906}} + {:command :line-to, :params {:x 1688.9000244140625, :y 48.0}} + {:command :curve-to, :params {:x 1687.9000244140625, :y 49.0, :c1x 1688.9000244140625, :c1y 48.599998474121094, :c2x 1688.5, :c2y 49.0}} + {:command :line-to, :params {:x 1683.9000244140625, :y 49.0}} + {:command :curve-to, :params {:x 1682.9000244140625, :y 48.0, :c1x 1683.300048828125, :c1y 49.0, :c2x 1682.9000244140625, :c2y 48.599998474121094}} + {:command :close-path, :params {}} + {:command :close-path, :params {}} + {:command :move-to, :params {:x 1684.9000244140625, :y 45.0}} + {:command :line-to, :params {:x 1684.9000244140625, :y 47.0}} + {:command :line-to, :params {:x 1686.9000244140625, :y 47.0}} + {:command :line-to, :params {:x 1686.9000244140625, :y 45.0}} + {:command :line-to, :params {:x 1684.9000244140625, :y 45.0}} + {:command :close-path, :params {}} + {:command :close-path, :params {}}] + + [{:command :move-to, :params {:x 1672.9000244140625, :y 48.0}} + {:command :line-to, :params {:x 1672.9000244140625, :y 44.0}} + {:command :curve-to, :params {:x 1673.9000244140625, :y 43.0, :c1x 1672.9000244140625, :c1y 43.400001525878906, :c2x 1673.300048828125, :c2y 43.0}} + {:command :line-to, :params {:x 1677.9000244140625, :y 43.0}} + {:command :curve-to, :params {:x 1678.9000244140625, :y 44.0, :c1x 1678.5, :c1y 43.0, :c2x 1678.9000244140625, :c2y 43.400001525878906}} + {:command :line-to, :params {:x 1678.9000244140625, :y 48.0}} + {:command :curve-to, :params {:x 1677.9000244140625, :y 49.0, :c1x 1678.9000244140625, :c1y 48.599998474121094, :c2x 1678.5, :c2y 49.0}} + {:command :line-to, :params {:x 1673.9000244140625, :y 49.0}} + {:command :curve-to, :params {:x 1672.9000244140625, :y 48.0, :c1x 1673.300048828125, :c1y 49.0, :c2x 1672.9000244140625, :c2y 48.599998474121094}} + {:command :close-path, :params {}} + {:command :close-path, :params {}} + {:command :move-to, :params {:x 1674.9000244140625, :y 45.0}} + {:command :line-to, :params {:x 1674.9000244140625, :y 47.0}} + {:command :line-to, :params {:x 1676.9000244140625, :y 47.0}} + {:command :line-to, :params {:x 1676.9000244140625, :y 45.0}} + {:command :line-to, :params {:x 1674.9000244140625, :y 45.0}} + {:command :close-path, :params {}} + {:command :close-path, :params {}}]]) + +(def bool-result + [{:command :move-to, :params {:x 1682.9000244140625, :y 48.0}} + {:command :line-to, :params {:x 1682.9000244140625, :y 44.0}} + {:command :curve-to, + :params + {:x 1683.9000244140625, :y 43.0, :c1x 1682.9000244140625, :c1y 43.400001525878906, :c2x 1683.300048828125, :c2y 43.0}} + {:command :line-to, :params {:x 1687.9000244140625, :y 43.0}} + {:command :curve-to, + :params {:x 1688.9000244140625, :y 44.0, :c1x 1688.5, :c1y 43.0, :c2x 1688.9000244140625, :c2y 43.400001525878906}} + {:command :line-to, :params {:x 1688.9000244140625, :y 48.0}} + {:command :curve-to, + :params {:x 1687.9000244140625, :y 49.0, :c1x 1688.9000244140625, :c1y 48.599998474121094, :c2x 1688.5, :c2y 49.0}} + {:command :line-to, :params {:x 1683.9000244140625, :y 49.0}} + {:command :curve-to, + :params + {:x 1682.9000244140625, :y 48.0, :c1x 1683.300048828125, :c1y 49.0, :c2x 1682.9000244140625, :c2y 48.599998474121094}} + {:command :move-to, :params {:x 1684.9000244140625, :y 45.0}} + {:command :line-to, :params {:x 1684.9000244140625, :y 47.0}} + {:command :line-to, :params {:x 1686.9000244140625, :y 47.0}} + {:command :line-to, :params {:x 1686.9000244140625, :y 45.0}} + {:command :line-to, :params {:x 1684.9000244140625, :y 45.0}} + {:command :move-to, :params {:x 1672.9000244140625, :y 48.0}} + {:command :line-to, :params {:x 1672.9000244140625, :y 44.0}} + {:command :curve-to, + :params + {:x 1673.9000244140625, :y 43.0, :c1x 1672.9000244140625, :c1y 43.400001525878906, :c2x 1673.300048828125, :c2y 43.0}} + {:command :line-to, :params {:x 1677.9000244140625, :y 43.0}} + {:command :curve-to, + :params {:x 1678.9000244140625, :y 44.0, :c1x 1678.5, :c1y 43.0, :c2x 1678.9000244140625, :c2y 43.400001525878906}} + {:command :line-to, :params {:x 1678.9000244140625, :y 48.0}} + {:command :curve-to, + :params {:x 1677.9000244140625, :y 49.0, :c1x 1678.9000244140625, :c1y 48.599998474121094, :c2x 1678.5, :c2y 49.0}} + {:command :line-to, :params {:x 1673.9000244140625, :y 49.0}} + {:command :curve-to, + :params + {:x 1672.9000244140625, :y 48.0, :c1x 1673.300048828125, :c1y 49.0, :c2x 1672.9000244140625, :c2y 48.599998474121094}} + {:command :move-to, :params {:x 1674.9000244140625, :y 45.0}} + {:command :line-to, :params {:x 1674.9000244140625, :y 47.0}} + {:command :line-to, :params {:x 1676.9000244140625, :y 47.0}} + {:command :line-to, :params {:x 1676.9000244140625, :y 45.0}} + {:command :line-to, :params {:x 1674.9000244140625, :y 45.0}}]) + +(t/deftest calculate-bool-content + (let [result (path.bool/calculate-content :union contents-for-bool)] + (t/is (= result bool-result)))) diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index 39efb0e616..1a40e0ed3b 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -673,41 +673,35 @@ (defn paste-shapes [{in-viewport? :in-viewport :as pdata}] - (letfn [(translate-media [mdata media-idx attr-path] - (let [id (-> (get-in mdata attr-path) - (:id)) + (letfn [(translate-media [mdata media-idx attr] + (let [id (-> (get mdata attr) :id) mobj (get media-idx id)] (if mobj - (if (empty? attr-path) - (assoc mdata :id (:id mobj)) - (update-in mdata attr-path assoc :id (:id mobj))) + (update mdata attr assoc :id (:id mobj)) mdata))) (add-obj? [chg] (= (:type chg) :add-obj)) + (process-rchange-shape [obj media-idx] + (let [translate-fill-image #(translate-media % media-idx :fill-image) + translate-stroke-image #(translate-media % media-idx :stroke-image) + translate-fills #(mapv translate-fill-image %) + translate-strokes #(mapv translate-stroke-image %) + process-text-node #(d/update-when % :fills translate-fills)] + + (-> obj + (update :fills translate-fills) + (update :strokes translate-strokes) + (d/update-when :content #(txt/transform-nodes process-text-node %)) + (d/update-when :position-data #(mapv process-text-node %))))) + ;; Analyze the rchange and replace staled media and ;; references to the new uploaded media-objects. (process-rchange [media-idx change] - (let [;; Texts can have different fills for pieces of the text - tr-fill-xf (map #(translate-media % media-idx [:fill-image])) - tr-stroke-xf (map #(translate-media % media-idx [:stroke-image]))] - (if (add-obj? change) - (update change :obj (fn [obj] - (-> obj - (update :fills #(into [] tr-fill-xf %)) - (update :strokes #(into [] tr-stroke-xf %)) - (d/update-when :metadata translate-media media-idx []) - (d/update-when :fill-image translate-media media-idx []) - (d/update-when :content - (fn [content] - (txt/xform-nodes tr-fill-xf content))) - (d/update-when :position-data - (fn [position-data] - (mapv (fn [pos-data] - (update pos-data :fills #(into [] tr-fill-xf %))) - position-data)))))) - change))) + (if (add-obj? change) + (update change :obj process-rchange-shape media-idx) + change)) (calculate-paste-position [state pobjects selected position] (let [page-objects (dsh/lookup-page-objects state) diff --git a/library/CHANGES.md b/library/CHANGES.md index 1e805c23b8..6f6a732e86 100644 --- a/library/CHANGES.md +++ b/library/CHANGES.md @@ -1,5 +1,16 @@ # CHANGELOG +## 1.0.7 + +- Add the ability to provide refereron creating build context + +```js +const context = penpot.createBuildContext({referer:"my-referer"}); +``` + +The referer will be added as an additional field on the manifest.json + + ## 1.0.6 - Fix unexpected issue on library color decoding diff --git a/library/deps.edn b/library/deps.edn index 0c59e66107..0b2473bf81 100644 --- a/library/deps.edn +++ b/library/deps.edn @@ -21,11 +21,10 @@ :dev {:extra-paths ["dev"] :extra-deps - {thheller/shadow-cljs {:mvn/version "3.1.4"} + {thheller/shadow-cljs {:mvn/version "3.1.7"} com.bhauman/rebel-readline {:mvn/version "RELEASE"} org.clojure/tools.namespace {:mvn/version "RELEASE"} - criterium/criterium {:mvn/version "RELEASE"} - cider/cider-nrepl {:mvn/version "0.48.0"}}} + criterium/criterium {:mvn/version "RELEASE"}}} :shadow-cljs {:main-opts ["-m" "shadow.cljs.devtools.cli"] diff --git a/library/package.json b/library/package.json index b58bfff5da..fa14bf7d46 100644 --- a/library/package.json +++ b/library/package.json @@ -1,6 +1,6 @@ { "name": "@penpot/library", - "version": "1.0.6", + "version": "1.0.7", "license": "MPL-2.0", "author": "Kaleidos INC", "packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538", @@ -40,8 +40,7 @@ "@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch", "concurrently": "^9.1.2", "luxon": "^3.6.1", - "nodemon": "^3.1.9", - "shadow-cljs": "3.1.4" + "nodemon": "^3.1.9" }, "dependencies": { "source-map-support": "^0.5.21" diff --git a/library/playground/sample-fill-stroke-and-media.js b/library/playground/sample-fill-stroke-and-media.js index dbc90cdce2..7bd952265d 100644 --- a/library/playground/sample-fill-stroke-and-media.js +++ b/library/playground/sample-fill-stroke-and-media.js @@ -6,7 +6,7 @@ import { Writable } from "stream"; // console.log(penpot); (async function () { - const context = penpot.createBuildContext(); + const context = penpot.createBuildContext({referer:"playground"}); { context.addFile({ name: "Test File 1" }); diff --git a/library/src/lib/builder.cljs b/library/src/lib/builder.cljs index f77f6f8ae5..c7b87d32c3 100644 --- a/library/src/lib/builder.cljs +++ b/library/src/lib/builder.cljs @@ -271,11 +271,19 @@ (fn [] (json/->js @state)))) +(def ^:private schema:context-options + [:map {:title "ContextOptions"} + [:referer {:optional true} ::sm/text]]) + +(def ^:private decode-context-options + (sm/decoder schema:context-options sm/json-transformer)) + (defn create-build-context "Create an empty builder state context." - [] - (let [state (atom {}) - api (create-builder-api state)] + [options] + (let [options (some-> options decode-params decode-context-options) + state (atom {:options options}) + api (create-builder-api state)] (specify! api cljs.core/IDeref diff --git a/library/src/lib/export.cljs b/library/src/lib/export.cljs index 7fee373440..1d5d5cfcb6 100644 --- a/library/src/lib/export.cljs +++ b/library/src/lib/export.cljs @@ -183,17 +183,22 @@ (defn- generate-manifest-procs [state] - (let [files (->> (get state ::fb/files) - (mapv (fn [[file-id file]] - {:id file-id - :name (:name file) - :features (:features file)}))) + (let [opts (get state :options) + files (->> (get state ::fb/files) + (mapv (fn [[file-id file]] + {:id file-id + :name (:name file) + :features (:features file)}))) params {:type "penpot/export-files" :version 1 :generated-by "penpot-library/%version%" + :referer (get opts :referer) :files files - :relations []}] - ["manifest.json" (delay (json/encode params))])) + :relations []} + params (d/without-nils params)] + + ["manifest.json" + (delay (json/encode params))])) (defn- generate-procs [state]