Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2025-07-07 09:37:55 +02:00
commit 92d708d52c
15 changed files with 187 additions and 100 deletions

View file

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

View file

@ -12,7 +12,6 @@
[app.binfile.common :as bfc] [app.binfile.common :as bfc]
[app.binfile.migrations :as bfm] [app.binfile.migrations :as bfm]
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.features :as cfeat] [app.common.features :as cfeat]
[app.common.files.migrations :as-alias fmg] [app.common.files.migrations :as-alias fmg]
@ -54,7 +53,7 @@
[:map {:title "Manifest"} [:map {:title "Manifest"}
[:version ::sm/int] [:version ::sm/int]
[:type :string] [:type :string]
[:referer {:optional true} :string]
[:generated-by {:optional true} :string] [:generated-by {:optional true} :string]
[:files [:files
@ -373,6 +372,7 @@
params {:type "penpot/export-files" params {:type "penpot/export-files"
:version 1 :version 1
:generated-by (str "penpot/" (:full cf/version)) :generated-by (str "penpot/" (:full cf/version))
:refer "penpot"
:files (vec (vals files)) :files (vec (vals files))
:relations rels}] :relations rels}]
(write-entry! output "manifest.json" params)))) (write-entry! output "manifest.json" params))))
@ -878,13 +878,8 @@
(defn- import-files (defn- import-files
[{:keys [::bfc/timestamp ::bfc/input ::bfc/name] :or {timestamp (dt/now)} :as cfg}] [{:keys [::bfc/timestamp ::bfc/input ::bfc/name] :or {timestamp (dt/now)} :as cfg}]
(dm/assert! (assert (instance? ZipFile input) "expected zip file")
"expected zip file" (assert (dt/instant? timestamp) "expected valid instant")
(instance? ZipFile input))
(dm/assert!
"expected valid instant"
(dt/instant? timestamp))
(let [manifest (-> (read-manifest input) (let [manifest (-> (read-manifest input)
(validate-manifest)) (validate-manifest))
@ -896,6 +891,7 @@
:hint "unexpected type on manifest" :hint "unexpected type on manifest"
:manifest manifest)) :manifest manifest))
;; Check if all files referenced on manifest are present ;; Check if all files referenced on manifest are present
(doseq [{file-id :id features :features} (:files manifest)] (doseq [{file-id :id features :features} (:files manifest)]
(let [path (str "files/" file-id ".json")] (let [path (str "files/" file-id ".json")]
@ -956,14 +952,13 @@
[{:keys [::bfc/ids] :as cfg} output] [{:keys [::bfc/ids] :as cfg} output]
(dm/assert! (assert
"expected a set of uuid's for `::bfc/ids` parameter" (and (set? ids) (every? uuid? ids))
(and (set? ids) "expected a set of uuid's for `::bfc/ids` parameter")
(every? uuid? ids)))
(dm/assert! (assert
"expected instance of jio/IOFactory for `input`" (satisfies? jio/IOFactory output)
(satisfies? jio/IOFactory output)) "expected instance of jio/IOFactory for `input`")
(let [id (uuid/next) (let [id (uuid/next)
tp (dt/tpoint) tp (dt/tpoint)
@ -1002,14 +997,14 @@
(defn import-files! (defn import-files!
[{:keys [::bfc/input] :as cfg}] [{:keys [::bfc/input] :as cfg}]
(dm/assert! (assert
"expected valid profile-id and project-id on `cfg`"
(and (uuid? (::bfc/profile-id cfg)) (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! (assert
"expected instance of jio/IOFactory for `input`" (io/coercible? input)
(io/coercible? input)) "expected instance of jio/IOFactory for `input`")
(let [id (uuid/next) (let [id (uuid/next)
tp (dt/tpoint) tp (dt/tpoint)
@ -1029,3 +1024,9 @@
:id (str id) :id (str id)
:elapsed (dt/format-duration (tp)) :elapsed (dt/format-duration (tp))
:error? (some? @cs)))))) :error? (some? @cs))))))
(defn get-manifest
[path]
(with-open [input (ZipFile. (fs/file path))]
(-> (read-manifest input)
(validate-manifest))))

View file

@ -67,7 +67,7 @@
(some-> cause (ex/format-throwable :data? true :explain? false :header? false :summary? false)))} (some-> cause (ex/format-throwable :data? true :explain? false :header? false :summary? false)))}
(when-let [params (or (:request/params context) (:params context))] (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)] (when-let [value (:value context)]
{:value (pp/pprint-str value :length 30 :level 13)}) {:value (pp/pprint-str value :length 30 :level 13)})

View file

@ -134,11 +134,18 @@
::webhooks/event? true ::webhooks/event? true
::sse/stream? true ::sse/stream? true
::sm/params schema:import-binfile} ::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) (projects/check-edition-permissions! pool profile-id project-id)
(let [params (-> params (let [version (or version 1)
(assoc :profile-id profile-id) params (-> params
(assoc :version (or version 1)))] (assoc :profile-id profile-id)
(assoc :version version))
manifest (case (int version)
1 nil
3 (bf.v3/get-manifest (:path file)))]
(with-meta (with-meta
(sse/response (partial import-binfile cfg params)) (sse/response (partial import-binfile cfg params))
{::audit/props {:file nil}}))) {::audit/props {:file nil
:generated-by (:generated-by manifest)
:referer (:referer manifest)}})))

View file

@ -170,19 +170,6 @@
item)) item))
root))) 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 (defn update-text-content
[shape pred-fn update-fn attrs] [shape pred-fn update-fn attrs]
(let [update-attrs-fn #(update-fn % attrs) (let [update-attrs-fn #(update-fn % attrs)

View file

@ -209,25 +209,16 @@
:code :invalid-path-content :code :invalid-path-content
:hint (str "unable to calculate bool content for shape " (:id shape)) :hint (str "unable to calculate bool content for shape " (:id shape))
:shapes (:shapes shape) :shapes (:shapes shape)
:content (mapv str contents) :type (:bool-type shape)
:content (vec contents)
:cause cause))))) :cause cause)))))
(defn calc-bool-content (defn calc-bool-content
"Calculate the boolean content from shape and objects. Returns a "Calculate the boolean content from shape and objects. Returns a
packed PathData instance" packed PathData instance"
[shape objects] [shape objects]
(ex/try! (-> (calc-bool-content* shape objects)
(-> (calc-bool-content* shape objects) (impl/path-data)))
(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))))
(defn update-bool-shape (defn update-bool-shape
"Calculates the selrect+points for the boolean shape" "Calculates the selrect+points for the boolean shape"

View file

@ -27,13 +27,11 @@
(defn make-move-to [to] (defn make-move-to [to]
{:command :move-to {:command :move-to
:relative false
:params {:x (:x to) :params {:x (:x to)
:y (:y to)}}) :y (:y to)}})
(defn make-line-to [to] (defn make-line-to [to]
{:command :line-to {:command :line-to
:relative false
:params {:x (:x to) :params {:x (:x to)
:y (:y to)}}) :y (:y to)}})
@ -65,7 +63,6 @@
(defn make-curve-to (defn make-curve-to
[to h1 h2] [to h1 h2]
{:command :curve-to {:command :curve-to
:relative false
:params (make-curve-params to h1 h2)}) :params (make-curve-params to h1 h2)})
(defn prefix->coords [prefix] (defn prefix->coords [prefix]
@ -98,7 +95,7 @@
(defn segment->point (defn segment->point
([segment] (segment->point segment :x)) ([segment] (segment->point segment :x))
([segment coord] ([segment coord]
(when-let [params (get segment :params)] (when-let [params (not-empty (get segment :params))]
(case coord (case coord
:c1 (gpt/point (get params :c1x) :c1 (gpt/point (get params :c1x)
(get params :c1y)) (get params :c1y))

View file

@ -14,6 +14,7 @@
[app.common.pprint :as pp] [app.common.pprint :as pp]
[app.common.transit :as trans] [app.common.transit :as trans]
[app.common.types.path :as path] [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.helpers :as path.helpers]
[app.common.types.path.impl :as path.impl] [app.common.types.path.impl :as path.impl]
[app.common.types.path.segment :as path.segment] [app.common.types.path.segment :as path.segment]
@ -414,3 +415,89 @@
result1 (get-handlers sample-content-large) result1 (get-handlers sample-content-large)
result2 (path.segment/get-handlers content)] result2 (path.segment/get-handlers content)]
(t/is (= result1 result2)))) (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))))

View file

@ -673,41 +673,35 @@
(defn paste-shapes (defn paste-shapes
[{in-viewport? :in-viewport :as pdata}] [{in-viewport? :in-viewport :as pdata}]
(letfn [(translate-media [mdata media-idx attr-path] (letfn [(translate-media [mdata media-idx attr]
(let [id (-> (get-in mdata attr-path) (let [id (-> (get mdata attr) :id)
(:id))
mobj (get media-idx id)] mobj (get media-idx id)]
(if mobj (if mobj
(if (empty? attr-path) (update mdata attr assoc :id (:id mobj))
(assoc mdata :id (:id mobj))
(update-in mdata attr-path assoc :id (:id mobj)))
mdata))) mdata)))
(add-obj? [chg] (add-obj? [chg]
(= (:type chg) :add-obj)) (= (: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 ;; Analyze the rchange and replace staled media and
;; references to the new uploaded media-objects. ;; references to the new uploaded media-objects.
(process-rchange [media-idx change] (process-rchange [media-idx change]
(let [;; Texts can have different fills for pieces of the text (if (add-obj? change)
tr-fill-xf (map #(translate-media % media-idx [:fill-image])) (update change :obj process-rchange-shape media-idx)
tr-stroke-xf (map #(translate-media % media-idx [:stroke-image]))] change))
(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)))
(calculate-paste-position [state pobjects selected position] (calculate-paste-position [state pobjects selected position]
(let [page-objects (dsh/lookup-page-objects state) (let [page-objects (dsh/lookup-page-objects state)

View file

@ -1,5 +1,16 @@
# CHANGELOG # 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 ## 1.0.6
- Fix unexpected issue on library color decoding - Fix unexpected issue on library color decoding

View file

@ -21,11 +21,10 @@
:dev :dev
{:extra-paths ["dev"] {:extra-paths ["dev"]
:extra-deps :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"} com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"} org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"} criterium/criterium {:mvn/version "RELEASE"}}}
cider/cider-nrepl {:mvn/version "0.48.0"}}}
:shadow-cljs :shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"] {:main-opts ["-m" "shadow.cljs.devtools.cli"]

View file

@ -1,6 +1,6 @@
{ {
"name": "@penpot/library", "name": "@penpot/library",
"version": "1.0.6", "version": "1.0.7",
"license": "MPL-2.0", "license": "MPL-2.0",
"author": "Kaleidos INC", "author": "Kaleidos INC",
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538", "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", "@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", "concurrently": "^9.1.2",
"luxon": "^3.6.1", "luxon": "^3.6.1",
"nodemon": "^3.1.9", "nodemon": "^3.1.9"
"shadow-cljs": "3.1.4"
}, },
"dependencies": { "dependencies": {
"source-map-support": "^0.5.21" "source-map-support": "^0.5.21"

View file

@ -6,7 +6,7 @@ import { Writable } from "stream";
// console.log(penpot); // console.log(penpot);
(async function () { (async function () {
const context = penpot.createBuildContext(); const context = penpot.createBuildContext({referer:"playground"});
{ {
context.addFile({ name: "Test File 1" }); context.addFile({ name: "Test File 1" });

View file

@ -271,11 +271,19 @@
(fn [] (fn []
(json/->js @state)))) (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 (defn create-build-context
"Create an empty builder state context." "Create an empty builder state context."
[] [options]
(let [state (atom {}) (let [options (some-> options decode-params decode-context-options)
api (create-builder-api state)] state (atom {:options options})
api (create-builder-api state)]
(specify! api (specify! api
cljs.core/IDeref cljs.core/IDeref

View file

@ -183,17 +183,22 @@
(defn- generate-manifest-procs (defn- generate-manifest-procs
[state] [state]
(let [files (->> (get state ::fb/files) (let [opts (get state :options)
(mapv (fn [[file-id file]] files (->> (get state ::fb/files)
{:id file-id (mapv (fn [[file-id file]]
:name (:name file) {:id file-id
:features (:features file)}))) :name (:name file)
:features (:features file)})))
params {:type "penpot/export-files" params {:type "penpot/export-files"
:version 1 :version 1
:generated-by "penpot-library/%version%" :generated-by "penpot-library/%version%"
:referer (get opts :referer)
:files files :files files
:relations []}] :relations []}
["manifest.json" (delay (json/encode params))])) params (d/without-nils params)]
["manifest.json"
(delay (json/encode params))]))
(defn- generate-procs (defn- generate-procs
[state] [state]