From f22aa606ce8da768010e182a32425361bfb4ef2d Mon Sep 17 00:00:00 2001 From: Francis Santiago Date: Tue, 22 Jul 2025 14:05:02 +0200 Subject: [PATCH 01/12] :books: Clarify OpenShift requirements (#6937) * :books: Clarify OpenShift requirements * :books: Remove the click for expanding --- docs/technical-guide/getting-started/kubernetes.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/technical-guide/getting-started/kubernetes.md b/docs/technical-guide/getting-started/kubernetes.md index 142f498947..a3fac318e3 100644 --- a/docs/technical-guide/getting-started/kubernetes.md +++ b/docs/technical-guide/getting-started/kubernetes.md @@ -97,6 +97,11 @@ file itself, which you can use as a basis for creating your own settings. You can also consult the list of parameters on the ArtifactHub page of the project. +### Using OpenShift? +If you are deploying Penpot on OpenShift, we recommend following the specific guidelines provided in our Penpot-helm documentation: +`Installing the chart with OpenShift requirements` + +Make sure to review the section **OpenShift Requirements** for important security and compatibility considerations. ## Upgrade Penpot From 35f3125fff88f1109fed18e45700d57b1759163e Mon Sep 17 00:00:00 2001 From: Xaviju Date: Tue, 22 Jul 2025 14:06:06 +0200 Subject: [PATCH 02/12] :bug: Fix null when copying shadow color on inspect tab (#6923) Co-authored-by: Xavier Julian --- CHANGES.md | 1 + .../main/ui/inspect/attributes/shadow.cljs | 16 +++++++-- .../app/main/ui/inspect/attributes/text.cljs | 34 ++----------------- frontend/src/app/util/code_gen/style_css.cljs | 1 + .../app/util/code_gen/style_css_formats.cljs | 1 + 5 files changed, 19 insertions(+), 34 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 340df7330c..24d02dc4e6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -42,6 +42,7 @@ - Fix main component receives focus and is selected when using 'Show Main Component' [Taiga #11402](https://tree.taiga.io/project/penpot/issue/11402) - Fix duplicating pages with mainInstance shapes nested inside groups [Taiga #10774](https://tree.taiga.io/project/penpot/issue/10774) - Fix ESC key not closing Add/Manage Libraries modal [Taiga #11523](https://tree.taiga.io/project/penpot/issue/11523) +- Fix copying a shadow color from info tab [Taiga #11211](https://tree.taiga.io/project/penpot/issue/11211) ## 2.8.1 (Unreleased) diff --git a/frontend/src/app/main/ui/inspect/attributes/shadow.cljs b/frontend/src/app/main/ui/inspect/attributes/shadow.cljs index 212f384795..ebda4bcd3b 100644 --- a/frontend/src/app/main/ui/inspect/attributes/shadow.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/shadow.cljs @@ -13,6 +13,7 @@ [app.main.ui.components.title-bar :refer [inspect-title-bar*]] [app.main.ui.inspect.attributes.common :refer [color-row]] [app.util.code-gen.style-css :as css] + [app.util.code-gen.style-css-formats :refer [format-color]] [app.util.i18n :refer [tr]] [rumext.v2 :as mf])) @@ -22,8 +23,18 @@ (defn- shadow-copy-data [shadow] (css/shadow->css shadow)) +(defn- copy-color-data + "Converts a fill object to CSS color string in the specified format." + [color format] + (format-color color {:format format})) + (mf/defc shadow-block [{:keys [shadow]}] - (let [color-format (mf/use-state :hex)] + (let [color-format (mf/use-state :hex) + color-format* (deref color-format) + on-change-format + (mf/use-fn + (fn [format] + (reset! color-format format)))] [:div {:class (stl/css :attributes-shadow-block)} [:div {:class (stl/css :shadow-row)} [:div {:class (stl/css :global/attr-label)} (->> shadow :style d/name (str "workspace.options.shadow-options.") (tr))] @@ -42,7 +53,8 @@ [:& color-row {:color (:color shadow) :format @color-format - :on-change-format #(reset! color-format %)}]])) + :copy-data (copy-color-data (:color shadow) color-format*) + :on-change-format on-change-format}]])) (mf/defc shadow-panel [{:keys [shapes]}] (let [shapes (->> shapes (filter has-shadow?))] diff --git a/frontend/src/app/main/ui/inspect/attributes/text.cljs b/frontend/src/app/main/ui/inspect/attributes/text.cljs index 90239b12b3..f4a527058d 100644 --- a/frontend/src/app/main/ui/inspect/attributes/text.cljs +++ b/frontend/src/app/main/ui/inspect/attributes/text.cljs @@ -7,10 +7,8 @@ (ns app.main.ui.inspect.attributes.text (:require-macros [app.main.style :as stl]) (:require - [app.common.colors :as cc] [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.math :as mth] [app.common.text :as txt] [app.common.types.color :as ctc] [app.main.fonts :as fonts] @@ -20,6 +18,7 @@ [app.main.ui.components.title-bar :refer [inspect-title-bar*]] [app.main.ui.formats :as fmt] [app.main.ui.inspect.attributes.common :refer [color-row]] + [app.util.code-gen.style-css-formats :refer [format-color]] [app.util.color :as uc] [app.util.i18n :refer [tr]] [cuerdas.core :as str] @@ -38,13 +37,6 @@ (get-in state [:viewer-libraries file-id :data :typographies]))] #(l/derived get-library st/state))) -(defn alpha->hex [alpha] - (-> (mth/round (* 255 alpha)) - (js/Number) - (.toString 16) - (.toUpperCase) - (.padStart 2 "0"))) - (defn- copy-style-data [style & properties] (->> properties @@ -58,35 +50,13 @@ "background-clip: text;" "color: transparent;")) -(defn- format-solid-color - "returns a CSS color string based on the provided color and format." - [color format] - (let [color-value (:color color) - opacity (:opacity color 1) - has-opacity? (not (= 1 opacity))] - (case format - :rgba - (let [[r g b a] (cc/hex->rgba color-value opacity)] - (str "color: rgba(" (cc/format-rgba [r g b a]) ");")) - - :hex - (str "color: " color-value - (when has-opacity? (alpha->hex opacity)) ";") - - :hsla - (let [[h s l a] (cc/hex->hsla color-value opacity)] - (str "color: hsla(" (cc/format-hsla [h s l a]) ");")) - - ;; Default fallback - (str "color: " color-value ";")))) - (defn- copy-color-data "Converts a fill object to CSS color string in the specified format." [fill format] (let [color (ctc/fill->color fill)] (if-let [gradient (:gradient color)] (format-gradient-css gradient) - (format-solid-color color format)))) + (format-color color {:format format})))) (mf/defc typography-block [{:keys [text style]}] diff --git a/frontend/src/app/util/code_gen/style_css.cljs b/frontend/src/app/util/code_gen/style_css.cljs index 5cef9d456a..8845441723 100644 --- a/frontend/src/app/util/code_gen/style_css.cljs +++ b/frontend/src/app/util/code_gen/style_css.cljs @@ -172,6 +172,7 @@ body { (format-value property value options)))) (defn format-css-property + "Format a single CSS property in the format 'property: value;'." [[property value] options] (when (some? value) (let [formatted-value (format-css-value property value options) diff --git a/frontend/src/app/util/code_gen/style_css_formats.cljs b/frontend/src/app/util/code_gen/style_css_formats.cljs index 170ac280d2..c73593474e 100644 --- a/frontend/src/app/util/code_gen/style_css_formats.cljs +++ b/frontend/src/app/util/code_gen/style_css_formats.cljs @@ -53,6 +53,7 @@ :else value)) (defn format-color + "Format a color value to a CSS compatible string based on the given format." [value options] (let [format (get options :format :hex)] (cond From 3df557b370465e5478cd5f4feb47ebaca2a9b4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20L=C3=B3pez?= Date: Wed, 23 Jul 2025 08:10:20 +0200 Subject: [PATCH 03/12] :recycle: Remove the workaround for updating the subscription after subscribing (#6938) --- frontend/src/app/main/ui/settings/subscription.cljs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/frontend/src/app/main/ui/settings/subscription.cljs b/frontend/src/app/main/ui/settings/subscription.cljs index 2f2819c896..650e20e0df 100644 --- a/frontend/src/app/main/ui/settings/subscription.cljs +++ b/frontend/src/app/main/ui/settings/subscription.cljs @@ -6,7 +6,6 @@ [app.main.data.auth :as da] [app.main.data.event :as ev] [app.main.data.modal :as modal] - [app.main.data.profile :as du] [app.main.refs :as refs] [app.main.repo :as rp] [app.main.router :as rt] @@ -319,12 +318,6 @@ (if (= success-modal-is-trial? "true") (tr "subscription.settings.enterprise-trial") (tr "subscription.settings.enterprise")))}) - (du/update-profile-props {:subscription - (-> subscription - (assoc :type (if (= params-subscription "subscribed-to-penpot-unlimited") - "unlimited" - "enterprise")) - (assoc :status "trialing"))}) (rt/nav :settings-subscription {} {::rt/replace true}))))) [:section {:class (stl/css :dashboard-section)} From fbf63b98c30a8cd67dd44f38b96a77fa2c053c42 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 8 Jul 2025 08:50:43 +0200 Subject: [PATCH 04/12] :sparkles: Reuse file data checkers on file validate ns --- common/src/app/common/files/validate.cljc | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc index 5bdb410669..0fe15a067c 100644 --- a/common/src/app/common/files/validate.cljc +++ b/common/src/app/common/files/validate.cljc @@ -650,26 +650,12 @@ (check-component component file) (deref *errors*))) -(def ^:private valid-fdata? - "Structural validation of file data using defined schema" - (sm/lazy-validator ::ctf/data)) - -(def ^:private get-fdata-explain - "Get schema explain data for file data" - (sm/lazy-explainer ::ctf/data)) - (defn validate-file-schema! "Validates the file itself, without external dependencies, it performs the schema checking and some semantical validation of the content." - [{:keys [id data] :as file}] - (when-not (valid-fdata? data) - (ex/raise :type :validation - :code :schema-validation - :hint (str/ffmt "invalid file data structure found on file '%'" id) - :file-id id - ::sm/explain (get-fdata-explain data))) - file) + [file] + (update file :data ctf/check-file-data)) (defn validate-file! "Validate full referential integrity and semantic coherence on file data. From 4e753dc4743fa549358bdb4f5727115e40ab7762 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 8 Jul 2025 08:51:19 +0200 Subject: [PATCH 05/12] :lipstick: Use resolved schemas instead of references For several schemas on common types --- common/src/app/common/files/validate.cljc | 1 - common/src/app/common/types/container.cljc | 29 +++++++++++----------- common/src/app/common/types/file.cljc | 14 +++++------ common/src/app/common/types/page.cljc | 4 +-- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc index 0fe15a067c..75650ca97d 100644 --- a/common/src/app/common/files/validate.cljc +++ b/common/src/app/common/files/validate.cljc @@ -669,7 +669,6 @@ :file-id (:id file) :details errors))) - (declare compare-slots) ;; Optional check to look for missing swap slots. diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 89e210d871..6c7e468314 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -15,7 +15,7 @@ [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.pages-list :as ctpl] - [app.common.types.plugins :as ctpg] + [app.common.types.plugins :refer [schema:plugin-data]] [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] [app.common.types.text :as cttx] @@ -30,21 +30,22 @@ (def valid-container-types #{:page :component}) -(sm/register! - ^{::sm/type ::container} - [:map - [:id ::sm/uuid] - [:type {:optional true} - [::sm/one-of valid-container-types]] - [:name :string] - [:path {:optional true} [:maybe :string]] - [:modified-at {:optional true} ::sm/inst] - [:objects {:optional true} - [:map-of {:gen/max 10} ::sm/uuid :map]] - [:plugin-data {:optional true} ::ctpg/plugin-data]]) +(def schema:container + (sm/register! + ^{::sm/type ::container} + [:map + [:id ::sm/uuid] + [:type {:optional true} + [::sm/one-of valid-container-types]] + [:name :string] + [:path {:optional true} [:maybe :string]] + [:modified-at {:optional true} ::sm/inst] + [:objects {:optional true} + [:map-of {:gen/max 10} ::sm/uuid :map]] + [:plugin-data {:optional true} schema:plugin-data]])) (def check-container - (sm/check-fn ::container)) + (sm/check-fn schema:container)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; HELPERS diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 521915da96..651b1b58c4 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -23,9 +23,9 @@ [app.common.types.container :as ctn] [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] - [app.common.types.plugins :as ctpg] + [app.common.types.plugins :refer [schema:plugin-data]] [app.common.types.shape-tree :as ctst] - [app.common.types.tokens-lib :as ctl] + [app.common.types.tokens-lib :refer [schema:tokens-lib]] [app.common.types.typographies-list :as ctyl] [app.common.types.typography :as cty] [app.common.uuid :as uuid] @@ -61,13 +61,13 @@ [:map-of {:gen/max 5} ::sm/uuid ctc/schema:library-color]) (def schema:components - [:map-of {:gen/max 5} ::sm/uuid ::ctn/container]) + [:map-of {:gen/max 5} ::sm/uuid ctn/schema:container]) (def schema:typographies - [:map-of {:gen/max 2} ::sm/uuid ::cty/typography]) + [:map-of {:gen/max 2} ::sm/uuid cty/schema:typography]) (def schema:pages-index - [:map-of {:gen/max 5} ::sm/uuid ::ctp/page]) + [:map-of {:gen/max 5} ::sm/uuid ctp/schema:page]) (def schema:options [:map {:title "FileOptions"} @@ -82,8 +82,8 @@ [:colors {:optional true} schema:colors] [:components {:optional true} schema:components] [:typographies {:optional true} schema:typographies] - [:plugin-data {:optional true} ::ctpg/plugin-data] - [:tokens-lib {:optional true} ::ctl/tokens-lib]]) + [:plugin-data {:optional true} schema:plugin-data] + [:tokens-lib {:optional true} schema:tokens-lib]]) (def schema:file "A schema for validate a file data structure; data is optional diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 230f7460d7..ee6084bd66 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -53,10 +53,10 @@ [:name :string] [:index {:optional true} ::sm/int] [:objects schema:objects] - [:default-grids {:optional true} ::ctg/default-grids] + [:default-grids {:optional true} ctg/schema:default-grids] [:flows {:optional true} schema:flows] [:guides {:optional true} schema:guides] - [:plugin-data {:optional true} ::ctpg/plugin-data] + [:plugin-data {:optional true} ctpg/schema:plugin-data] [:background {:optional true} ctc/schema:hex-color] [:comment-thread-positions {:optional true} From 4effd375a98a2fdb4247f07bc119fe946013c40a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 8 Jul 2025 13:17:03 +0200 Subject: [PATCH 06/12] :sparkles: Add several improvements to admin pannel --- backend/resources/app/templates/debug.tmpl | 158 ++++------ .../resources/app/templates/error-list.tmpl | 4 +- backend/src/app/binfile/common.clj | 4 +- backend/src/app/features/file_migrations.clj | 6 + backend/src/app/http/debug.clj | 278 +++++++++--------- backend/src/app/rpc/commands/files_create.clj | 5 +- backend/src/app/rpc/commands/teams.clj | 16 +- common/src/app/common/data.cljc | 14 +- 8 files changed, 233 insertions(+), 252 deletions(-) diff --git a/backend/resources/app/templates/debug.tmpl b/backend/resources/app/templates/debug.tmpl index a630e8fbd2..65d3d7614c 100644 --- a/backend/resources/app/templates/debug.tmpl +++ b/backend/resources/app/templates/debug.tmpl @@ -17,38 +17,6 @@ Debug Main Page CLICK HERE TO SEE THE ERROR REPORTS -
- Download file data: - Given an FILE-ID, downloads the file data as file. The file data is encoded using transit. -
-
- -
-
- - -
-
-
- -
- Upload File Data: - Create a new file on your draft projects using the file downloaded from the previous section. -
-
- -
-
- - -
- -
- -
-
-
-
Profile Management
@@ -81,6 +49,50 @@ Debug Main Page +
+ +
+ Download RAW file data: + Given an FILE-ID, downloads the file AS-IS (no validation + checks, just exports the file data and related objects in raw) + +
+
+ WARNING: this operation does not performs any checks +
+ +
+ +
+
+ + +
+ +
+ +
+ Upload File Data: + Create a new file on your draft projects using the file downloaded from the previous section. +
+
+ WARNING: this operation does not performs any checks +
+
+
+ +
+
+ + +
+ +
+ +
+
+
+
Export binfile: @@ -88,7 +100,7 @@ Debug Main Page the related libraries in a single custom formatted binary file. -
+
@@ -116,7 +128,7 @@ Debug Main Page Import binfile: Import penpot file in binary format. - +
@@ -130,79 +142,27 @@ Debug Main Page
- Reset file version - Allows reset file data version to a specific number/ - - -
- -
-
- -
- -
- - -
- - This is a just a security double check for prevent non intentional submits. - -
- - -
- -
- -
-
- -
-

Feature Flags

-
- Enable + Feature Flags for Team Add a feature flag to a team -
+
- +
- - -
- - Do not check if the feature is supported - -
- -
- - -
- - This is a just a security double check for prevent non intentional submits. - -
- -
- -
-
-
-
- Disable - Remove a feature flag from a team -
-
- -
-
- +
diff --git a/backend/resources/app/templates/error-list.tmpl b/backend/resources/app/templates/error-list.tmpl index b8af22fb89..c328700ecf 100644 --- a/backend/resources/app/templates/error-list.tmpl +++ b/backend/resources/app/templates/error-list.tmpl @@ -7,7 +7,9 @@ penpot - error list {% block content %}
diff --git a/backend/src/app/binfile/common.clj b/backend/src/app/binfile/common.clj index b1a131ef10..072327792b 100644 --- a/backend/src/app/binfile/common.clj +++ b/backend/src/app/binfile/common.clj @@ -155,7 +155,7 @@ (defn decode-file "A general purpose file decoding function that resolves all external pointers, run migrations and return plain vanilla file map" - [cfg {:keys [id] :as file}] + [cfg {:keys [id] :as file} & {:keys [migrate?] :or {migrate? true}}] (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)] (let [file (->> file (feat.fmigr/resolve-applied-migrations cfg) @@ -168,7 +168,7 @@ (update :data feat.fdata/process-pointers deref) (update :data feat.fdata/process-objects (partial into {})) (update :data assoc :id id) - (fmg/migrate-file libs))))) + (cond-> migrate? (fmg/migrate-file libs)))))) (defn get-file "Get file, resolve all features and apply migrations. diff --git a/backend/src/app/features/file_migrations.clj b/backend/src/app/features/file_migrations.clj index beec865555..9552d78ba4 100644 --- a/backend/src/app/features/file_migrations.clj +++ b/backend/src/app/features/file_migrations.clj @@ -37,3 +37,9 @@ {::db/return-keys false ::sql/on-conflict-do-nothing true}) (db/get-update-count)))) + +(defn reset-migrations! + "Replace file migrations" + [conn {:keys [id] :as file}] + (db/delete! conn :file-migration {:file-id id}) + (upsert-migrations! conn file)) diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj index 5e05962566..f4d835b417 100644 --- a/backend/src/app/http/debug.clj +++ b/backend/src/app/http/debug.clj @@ -15,9 +15,11 @@ [app.common.features :as cfeat] [app.common.logging :as l] [app.common.pprint :as pp] + [app.common.transit :as t] [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] + [app.features.file-migrations :as feat.fmig] [app.http.session :as session] [app.rpc.commands.auth :as auth] [app.rpc.commands.files-create :refer [create-file]] @@ -50,26 +52,26 @@ {::yres/status 200 ::yres/headers {"content-type" "text/html"} ::yres/body (-> (io/resource "app/templates/debug.tmpl") - (tmpl/render {:version (:full cf/version)}))}) + (tmpl/render {:version (:full cf/version) + :supported-features cfeat/supported-features}))}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; FILE CHANGES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn prepare-response - [body] - (let [headers {"content-type" "application/transit+json"}] - {::yres/status 200 - ::yres/body body - ::yres/headers headers})) +(defn- get-resolved-file + [cfg file-id] + (some-> (bfc/get-file cfg file-id :migrate? false) + (update :data blob/encode))) -(defn prepare-download-response - [body filename] - (let [headers {"content-disposition" (str "attachment; filename=" filename) - "content-type" "application/octet-stream"}] - {::yres/status 200 - ::yres/body body - ::yres/headers headers})) +(defn prepare-download + [file filename] + {::yres/status 200 + ::yres/headers + {"content-disposition" (str "attachment; filename=" filename ".json") + "content-type" "application/octet-stream"} + ::yres/body + (t/encode file {:type :json-verbose})}) (def sql:retrieve-range-of-changes "select revn, changes from file_change where file_id=? and revn >= ? and revn <= ? order by revn") @@ -77,45 +79,51 @@ (def sql:retrieve-single-change "select revn, changes, data from file_change where file_id=? and revn = ?") -(defn- retrieve-file-data - [{:keys [::db/pool]} {:keys [params ::session/profile-id] :as request}] +(defn- download-file-data + [cfg {:keys [params ::session/profile-id] :as request}] (let [file-id (some-> params :file-id parse-uuid) - revn (some-> params :revn parse-long) filename (str file-id)] (when-not file-id (ex/raise :type :validation :code :missing-arguments)) - (let [data (if (integer? revn) - (some-> (db/exec-one! pool [sql:retrieve-single-change file-id revn]) :data) - (some-> (db/get-by-id pool :file file-id) :data))] - - (when-not data - (ex/raise :type :not-found - :code :enpty-data - :hint "empty response")) + (if-let [file (get-resolved-file cfg file-id)] (cond (contains? params :download) - (prepare-download-response data filename) + (prepare-download file filename) (contains? params :clone) - (let [profile (profile/get-profile pool profile-id) - project-id (:default-project-id profile)] + (db/tx-run! cfg + (fn [{:keys [::db/conn] :as cfg}] + (let [profile (profile/get-profile conn profile-id) + project-id (:default-project-id profile) + file (-> (create-file cfg {:id (uuid/next) + :name (str "Cloned: " (:name file)) + :features (:features file) + :project-id project-id + :profile-id profile-id}) + (assoc :data (:data file)) + (assoc :migrations (:migrations file)))] - (db/run! pool (fn [{:keys [::db/conn] :as cfg}] - (create-file cfg {:id file-id - :name (str "Cloned file: " filename) - :project-id project-id - :profile-id profile-id}) - (db/update! conn :file - {:data data} - {:id file-id}) - {::yres/status 201 - ::yres/body "OK CREATED"}))) + (feat.fmig/reset-migrations! conn file) + (db/update! conn :file + {:data (:data file)} + {:id (:id file)} + {::db/return-keys false}) + + + {::yres/status 201 + ::yres/body "OK CLONED"}))) :else - (prepare-response (blob/decode data)))))) + (ex/raise :type :validation + :code :invalid-params + :hint "invalid button")) + + (ex/raise :type :not-found + :code :enpty-data + :hint "empty response")))) (defn- is-file-exists? [pool id] @@ -123,81 +131,61 @@ (-> (db/exec-one! pool [sql id]) :exists))) (defn- upload-file-data - [{:keys [::db/pool]} {:keys [::session/profile-id params] :as request}] + [{:keys [::db/pool] :as cfg} {:keys [::session/profile-id params] :as request}] (let [profile (profile/get-profile pool profile-id) project-id (:default-project-id profile) - data (some-> params :file :path io/read*)] + file (some-> params :file :path io/read* t/decode)] - (if (and data project-id) - (let [fname (str "Imported file *: " (dt/now)) + (if (and file project-id) + (let [fname (str "Imported: " (:name file) "(" (dt/now) ")") reuse-id? (contains? params :reuseid) file-id (or (and reuse-id? (ex/ignoring (-> params :file :filename parse-uuid))) (uuid/next))] (if (and reuse-id? file-id (is-file-exists? pool file-id)) - (do - (db/update! pool :file - {:data data - :deleted-at nil} - {:id file-id}) - {::yres/status 200 - ::yres/body "OK UPDATED"}) + (db/tx-run! cfg + (fn [{:keys [::db/conn] :as cfg}] + (db/update! conn :file + {:data (:data file) + :features (into-array (:features file)) + :deleted-at nil} + {:id file-id} + {::db/return-keys false}) + (feat.fmig/reset-migrations! conn file) + {::yres/status 200 + ::yres/body "OK UPDATED"})) + + (db/tx-run! cfg + (fn [{:keys [::db/conn] :as cfg}] + (let [file (-> (create-file cfg {:id file-id + :name fname + :features (:features file) + :project-id project-id + :profile-id profile-id}) + (assoc :data (:data file)) + (assoc :migrations (:migrations file)))] - (db/run! pool (fn [{:keys [::db/conn] :as cfg}] - (create-file cfg {:id file-id - :name fname - :project-id project-id - :profile-id profile-id}) (db/update! conn :file - {:data data} - {:id file-id}) + {:data (:data file)} + {:id file-id} + {::db/return-keys false}) + (feat.fmig/reset-migrations! conn file) {::yres/status 201 - ::yres/body "OK CREATED"})))) + ::yres/body "OK CREATED"}))))) - {::yres/status 500 - ::yres/body "ERROR"}))) + (ex/raise :type :validation + :code :invalid-params + :hint "invalid file uploaded")))) -(defn file-data-handler +(defn raw-export-import-handler [cfg request] (case (yreq/method request) - :get (retrieve-file-data cfg request) + :get (download-file-data cfg request) :post (upload-file-data cfg request) (ex/raise :type :http :code :method-not-found))) -(defn file-changes-handler - [{:keys [::db/pool]} {:keys [params] :as request}] - (letfn [(retrieve-changes [file-id revn] - (if (str/includes? revn ":") - (let [[start end] (->> (str/split revn #":") - (map str/trim) - (map parse-long))] - (some->> (db/exec! pool [sql:retrieve-range-of-changes file-id start end]) - (map :changes) - (map blob/decode) - (mapcat identity) - (vec))) - - (if-let [revn (parse-long revn)] - (let [item (db/exec-one! pool [sql:retrieve-single-change file-id revn])] - (some-> item :changes blob/decode vec)) - (ex/raise :type :validation :code :invalid-arguments))))] - - (let [file-id (some-> params :id parse-uuid) - revn (or (some-> params :revn parse-long) "latest") - filename (str file-id)] - - (when (or (not file-id) (not revn)) - (ex/raise :type :validation - :code :invalid-arguments - :hint "missing arguments")) - - (let [data (retrieve-changes file-id revn)] - (if (contains? params :download) - (prepare-download-response data filename) - (prepare-response data)))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ERROR BROWSER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -430,49 +418,49 @@ ::yres/body "OK"})) -(defn- add-team-feature - [{:keys [params] :as request}] - (let [team-id (some-> params :team-id d/parse-uuid) - feature (some-> params :feature str) +(defn- handle-team-features + [cfg {:keys [params] :as request}] + (let [team-id (some-> params :team-id d/parse-uuid) + feature (some-> params :feature str) + action (some-> params :action) skip-check (contains? params :skip-check)] - (when-not (contains? params :force) - (ex/raise :type :validation - :code :missing-force - :hint "missing force checkbox")) - (when (nil? team-id) (ex/raise :type :validation :code :invalid-team-id :hint "provided invalid team id")) - (srepl/enable-team-feature! team-id feature :skip-check skip-check) + (if (= action "show") + (let [team (db/run! cfg teams/get-team-info {:id team-id})] + {::yres/status 200 + ::yres/headers {"content-type" "text/plain"} + ::yres/body (apply str "Team features:\n" + (->> (:features team) + (map (fn [feature] + (str "- " feature "\n")))))}) - {::yres/status 200 - ::yres/headers {"content-type" "text/plain"} - ::yres/body "OK"})) + (do + (when-not (contains? params :force) + (ex/raise :type :validation + :code :missing-force + :hint "missing force checkbox")) -(defn- remove-team-feature - [{:keys [params] :as request}] - (let [team-id (some-> params :team-id d/parse-uuid) - feature (some-> params :feature str) - skip-check (contains? params :skip-check)] + (cond + (= action "enable") + (srepl/enable-team-feature! team-id feature :skip-check skip-check) - (when-not (contains? params :force) - (ex/raise :type :validation - :code :missing-force - :hint "missing force checkbox")) + (= action "disable") + (srepl/disable-team-feature! team-id feature :skip-check skip-check) - (when (nil? team-id) - (ex/raise :type :validation - :code :invalid-team-id - :hint "provided invalid team id")) + :else + (ex/raise :type :validation + :code :invalid-action + :hint (str "invalid action: " action))) - (srepl/disable-team-feature! team-id feature :skip-check skip-check) - {::yres/status 200 - ::yres/headers {"content-type" "text/plain"} - ::yres/body "OK"})) + {::yres/status 200 + ::yres/headers {"content-type" "text/plain"} + ::yres/body "OK"})))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; OTHER SMALL VIEWS/HANDLERS @@ -525,6 +513,25 @@ (ex/raise :type :authentication :code :only-admins-allowed)))))}) +(def errors + (letfn [(handle-error [cause] + (when-let [data (ex-data cause)] + (when (= :validation (:type data)) + (str "Error: " (or (:hint data) (ex-message cause)) "\n"))))] + {:name ::errors + :compile + (fn [& _params] + (fn [handler] + (fn [request] + (try + (handler request) + (catch Throwable cause + (let [body (or (handle-error cause) + (ex/format-throwable cause))] + {::yres/status 400 + ::yres/headers {"content-type" "text/plain"} + ::yres/body body}))))))})) + (defmethod ig/assert-key ::routes [_ params] (assert (db/pool? (::db/pool params)) "expected a valid database pool") @@ -540,15 +547,14 @@ ["/changelog" {:handler (partial changelog-handler cfg)}] ["/error/:id" {:handler (partial error-handler cfg)}] ["/error" {:handler (partial error-list-handler cfg)}] - ["/actions/resend-email-verification" - {:handler (partial resend-email-notification cfg)}] - ["/actions/reset-file-version" - {:handler (partial reset-file-version cfg)}] - ["/actions/add-team-feature" - {:handler (partial add-team-feature)}] - ["/actions/remove-team-feature" - {:handler (partial remove-team-feature)}] - ["/file/export" {:handler (partial export-handler cfg)}] - ["/file/import" {:handler (partial import-handler cfg)}] - ["/file/data" {:handler (partial file-data-handler cfg)}] - ["/file/changes" {:handler (partial file-changes-handler cfg)}]]]) + ["/actions" {:middleware [[errors]]} + ["/resend-email-verification" + {:handler (partial resend-email-notification cfg)}] + ["/reset-file-version" + {:handler (partial reset-file-version cfg)}] + ["/handle-team-features" + {:handler (partial handle-team-features cfg)}] + ["/file-export" {:handler (partial export-handler cfg)}] + ["/file-import" {:handler (partial import-handler cfg)}] + ["/file-raw-export-import" {:handler (partial raw-export-import-handler cfg)}]]]]) + diff --git a/backend/src/app/rpc/commands/files_create.clj b/backend/src/app/rpc/commands/files_create.clj index fc9dcb7fef..a4c9069e07 100644 --- a/backend/src/app/rpc/commands/files_create.clj +++ b/backend/src/app/rpc/commands/files_create.clj @@ -7,7 +7,6 @@ (ns app.rpc.commands.files-create (:require [app.binfile.common :as bfc] - [app.common.data.macros :as dm] [app.common.features :as cfeat] [app.common.schema :as sm] [app.common.types.file :as ctf] @@ -41,9 +40,7 @@ :or {is-shared false revn 0 create-page true} :as params}] - (dm/assert! - "expected a valid connection" - (db/connection? conn)) + (assert (db/connection? conn) "expected a valid connection") (binding [pmap/*tracked* (pmap/create-tracked) cfeat/*current* features] diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index bb23c6997c..a099223b83 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -78,9 +78,10 @@ (defn decode-row [{:keys [features subscription] :as row}] - (cond-> row - (some? features) (assoc :features (db/decode-pgarray features #{})) - (some? subscription) (assoc :subscription (db/decode-transit-pgobject subscription)))) + (when row + (cond-> row + (some? features) (assoc :features (db/decode-pgarray features #{})) + (some? subscription) (assoc :subscription (db/decode-transit-pgobject subscription))))) ;; FIXME: move @@ -461,11 +462,12 @@ ;; --- COMMAND QUERY: get-team-info -(defn- get-team-info +(defn get-team-info [{:keys [::db/conn] :as cfg} {:keys [id] :as params}] - (db/get* conn :team - {:id id} - {::sql/columns [:id :is-default]})) + (-> (db/get* conn :team + {:id id} + {::sql/columns [:id :is-default :features]}) + (decode-row))) (sv/defmethod ::get-team-info "Retrieve minimal team info by its ID." diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index bb25641333..d06da2ae4c 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -9,17 +9,16 @@ data resources." (:refer-clojure :exclude [read-string hash-map merge name update-vals parse-double group-by iteration concat mapcat - parse-uuid max min regexp?]) + parse-uuid max min regexp? array?]) #?(:cljs (:require-macros [app.common.data])) (:require - #?(:cljs [cljs.core :as c] - :clj [clojure.core :as c]) #?(:cljs [cljs.reader :as r] :clj [clojure.edn :as r]) #?(:cljs [goog.array :as garray]) [app.common.math :as mth] + [clojure.core :as c] [clojure.set :as set] [cuerdas.core :as str] [linked.map :as lkm] @@ -167,6 +166,15 @@ ;; Data Structures Access & Manipulation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn array? + [o] + #?(:cljs + (c/array? o) + :clj + (if (some? o) + (.isArray (class o)) + false))) + (defn not-empty? [coll] (boolean (seq coll))) From d46b5195246305f6c0fe237ea6957ec6be919c9c Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 23 Jul 2025 09:04:54 +0200 Subject: [PATCH 07/12] :bug: Fix remove color button in the gradient editor (#6942) --- CHANGES.md | 1 + frontend/src/app/main/ui/workspace/colorpicker.cljs | 5 ++--- .../src/app/main/ui/workspace/colorpicker/gradients.cljs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 24d02dc4e6..6fa4cb93d0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -43,6 +43,7 @@ - Fix duplicating pages with mainInstance shapes nested inside groups [Taiga #10774](https://tree.taiga.io/project/penpot/issue/10774) - Fix ESC key not closing Add/Manage Libraries modal [Taiga #11523](https://tree.taiga.io/project/penpot/issue/11523) - Fix copying a shadow color from info tab [Taiga #11211](https://tree.taiga.io/project/penpot/issue/11211) +- Fix remove color button in the gradient editor [Taiga #11623](https://tree.taiga.io/project/penpot/issue/11623) ## 2.8.1 (Unreleased) diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index 147aa74991..bb40c905c3 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -276,10 +276,9 @@ handle-gradient-remove-stop (mf/use-fn (mf/deps state) - (fn [stop] + (fn [index] (when (> (count (:stops state)) 2) - (when-let [index (d/index-of-pred (:stops state) #(= % stop))] - (st/emit! (dc/remove-gradient-stop index)))))) + (st/emit! (dc/remove-gradient-stop index))))) handle-stop-edit-start (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs index 6ebfc2a1d1..9e9eae0a19 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs @@ -80,10 +80,10 @@ handle-remove-stop (mf/use-callback - (mf/deps on-remove-stop stop) + (mf/deps on-remove-stop index) (fn [] (when on-remove-stop - (on-remove-stop stop)))) + (on-remove-stop index)))) handle-focus-stop-offset (mf/use-fn From b20b272eaebca4bbc80c1a4ca27ebc4a8e6af915 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Wed, 23 Jul 2025 08:51:44 +0200 Subject: [PATCH 08/12] :books: Update changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 6fa4cb93d0..c441b794dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,6 +27,7 @@ - Improve text layer auto-resize: auto-width switches to auto-height on horizontal resize, and only switches to fixed on vertical resize [Taiga #11578](https://tree.taiga.io/project/penpot/issue/11578) - Highlight first font in font selector search. Apply only on Enter or click. [Taiga #11579](https://tree.taiga.io/project/penpot/issue/11579) - Add the ability to show login dialog on profile settings [Github #6871](https://github.com/penpot/penpot/pull/6871) +- Improve the application of tokens with object specific tokens [Taiga #10209](https://tree.taiga.io/project/penpot/us/10209) ### :bug: Bugs fixed From 9390c1e7be82d92010856631ed02c23a06947ea5 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 23 Jul 2025 11:46:58 +0200 Subject: [PATCH 09/12] :bug: Fix "Copy as SVG" generates different code from the Inspect panel (#6945) --- CHANGES.md | 1 + frontend/src/app/main/data/preview.cljs | 3 +-- frontend/src/app/main/data/workspace/clipboard.cljs | 4 ++-- frontend/src/app/main/ui/inspect/code.cljs | 3 +-- frontend/src/app/plugins/api.cljs | 2 +- frontend/src/app/util/code_gen.cljs | 6 ++++++ frontend/src/app/util/code_gen/markup_svg.cljs | 6 ++++++ 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c441b794dc..4193b156db 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -45,6 +45,7 @@ - Fix ESC key not closing Add/Manage Libraries modal [Taiga #11523](https://tree.taiga.io/project/penpot/issue/11523) - Fix copying a shadow color from info tab [Taiga #11211](https://tree.taiga.io/project/penpot/issue/11211) - Fix remove color button in the gradient editor [Taiga #11623](https://tree.taiga.io/project/penpot/issue/11623) +- Fix "Copy as SVG" generates different code from the Inspect panel [Taiga #11519](https://tree.taiga.io/project/penpot/issue/11519) ## 2.8.1 (Unreleased) diff --git a/frontend/src/app/main/data/preview.cljs b/frontend/src/app/main/data/preview.cljs index 7be41d6e2d..fc4d4ca5dd 100644 --- a/frontend/src/app/main/data/preview.cljs +++ b/frontend/src/app/main/data/preview.cljs @@ -80,8 +80,7 @@ (cb/format-code style-type))) markup-code - (-> (cg/generate-markup-code objects markup-type [shape]) - (cb/format-code markup-type))] + (cg/generate-formatted-markup-code objects markup-type [shape])] (update-preview-window preview diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index 1a40e0ed3b..b943aad80a 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -346,8 +346,8 @@ (gsh/translate-to-frame % (get objects parent-frame-id))) shapes (mapv maybe-translate selected) - svg (svg/generate-markup objects shapes)] - (wapi/write-to-clipboard svg))))) + svg-formatted (svg/generate-formatted-markup objects shapes)] + (wapi/write-to-clipboard svg-formatted))))) (defn copy-selected-css [] diff --git a/frontend/src/app/main/ui/inspect/code.cljs b/frontend/src/app/main/ui/inspect/code.cljs index eaca3f114c..7268a120e6 100644 --- a/frontend/src/app/main/ui/inspect/code.cljs +++ b/frontend/src/app/main/ui/inspect/code.cljs @@ -149,8 +149,7 @@ (mf/use-memo (mf/deps markup-type shapes images-data) (fn [] - (-> (cg/generate-markup-code objects markup-type shapes) - (cb/format-code markup-type)))) + (cg/generate-formatted-markup-code objects markup-type shapes))) on-markup-copied (mf/use-fn diff --git a/frontend/src/app/plugins/api.cljs b/frontend/src/app/plugins/api.cljs index 28e8ed6b7b..641f99897b 100644 --- a/frontend/src/app/plugins/api.cljs +++ b/frontend/src/app/plugins/api.cljs @@ -410,7 +410,7 @@ :else (let [objects (u/locate-objects) shapes (into [] (map u/proxy->shape) shapes)] - (cg/generate-markup-code objects type shapes))))) + (cg/generate-formatted-markup-code objects type shapes))))) :generateStyle (fn [shapes options] diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index 99c5a4e89c..953b61165a 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -6,6 +6,7 @@ (ns app.util.code-gen (:require + [app.util.code-beautify :as cb] [app.util.code-gen.markup-html :as html] [app.util.code-gen.markup-svg :as svg] [app.util.code-gen.style-css :as css])) @@ -18,6 +19,11 @@ "svg" svg/generate-markup)] (generate-markup objects shapes))) +(defn generate-formatted-markup-code + [objects type shapes] + (let [markup (generate-markup-code objects type shapes)] + (cb/format-code markup type))) + (defn generate-style-code ([objects type root-shapes all-shapes] (generate-style-code objects type root-shapes all-shapes nil)) diff --git a/frontend/src/app/util/code_gen/markup_svg.cljs b/frontend/src/app/util/code_gen/markup_svg.cljs index a25177bee2..65044af6d7 100644 --- a/frontend/src/app/util/code_gen/markup_svg.cljs +++ b/frontend/src/app/util/code_gen/markup_svg.cljs @@ -8,6 +8,7 @@ (:require ["react-dom/server" :as rds] [app.main.render :as render] + [app.util.code-beautify :as cb] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -24,3 +25,8 @@ (->> shapes (map #(generate-svg objects %)) (str/join "\n"))) + +(defn generate-formatted-markup + [objects shapes] + (let [markup (generate-markup objects shapes)] + (cb/format-code markup "svg"))) From 9fdc6be4651e13bdca3fe911c0b72d2c227112b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 21 Jul 2025 16:24:07 +0200 Subject: [PATCH 10/12] :bug: Fix bad touched attributes when applying tokens to text shapes --- CHANGES.md | 1 + common/src/app/common/types/container.cljc | 8 ++++-- common/src/app/common/types/token.cljc | 33 +++++++++++++--------- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4193b156db..8c1d5b8430 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -46,6 +46,7 @@ - Fix copying a shadow color from info tab [Taiga #11211](https://tree.taiga.io/project/penpot/issue/11211) - Fix remove color button in the gradient editor [Taiga #11623](https://tree.taiga.io/project/penpot/issue/11623) - Fix "Copy as SVG" generates different code from the Inspect panel [Taiga #11519](https://tree.taiga.io/project/penpot/issue/11519) +- Fix overriden tokens in text copies are not preserved [Taiga #11486](https://tree.taiga.io/project/penpot/issue/11486) ## 2.8.1 (Unreleased) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 6c7e468314..fa1e506906 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -522,9 +522,13 @@ (let [old-applied-tokens (d/nilv (:applied-tokens shape) #{}) changed-token-attrs (filter #(not= (get old-applied-tokens %) (get new-applied-tokens %)) ctt/all-keys) + text-shape? (= (:type shape) :text) changed-groups (into #{} - (comp (map ctt/token-attr->shape-attr) - (map #(get ctk/sync-attrs %)) + (comp (mapcat #(if (and text-shape? (ctt/attrs-in-text-content %)) + [:content-group :text-content-attribute] + [(->> % + (ctt/token-attr->shape-attr) + (get ctk/sync-attrs))])) (filter some?)) changed-token-attrs)] changed-groups)) diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc index d3c1f27657..9ef1571e53 100644 --- a/common/src/app/common/types/token.cljc +++ b/common/src/app/common/types/token.cljc @@ -29,20 +29,20 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def token-type->dtcg-token-type - {:boolean "boolean" - :border-radius "borderRadius" - :color "color" - :dimensions "dimension" - :font-size "fontSizes" + {:boolean "boolean" + :border-radius "borderRadius" + :color "color" + :dimensions "dimension" + :font-size "fontSizes" :letter-spacing "letterSpacing" - :number "number" - :opacity "opacity" - :other "other" - :rotation "rotation" - :sizing "sizing" - :spacing "spacing" - :string "string" - :stroke-width "strokeWidth"}) + :number "number" + :opacity "opacity" + :other "other" + :rotation "rotation" + :sizing "sizing" + :spacing "spacing" + :string "string" + :stroke-width "strokeWidth"}) (def dtcg-token-type->token-type (set/map-invert token-type->dtcg-token-type)) @@ -263,6 +263,13 @@ [attributes token-type] (seq (appliable-attrs attributes token-type))) +;; Token attrs that are set inside content blocks of text shapes, instead +;; at the shape level. +(def attrs-in-text-content + (set/union + typography-keys + #{:fill})) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TOKENS IN SHAPES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From f55e7d81652fb7d6624d9ffc99212c548bd39552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 23 Jul 2025 10:54:30 +0200 Subject: [PATCH 11/12] :bug: Keep shape level groups for token sync later --- common/src/app/common/types/container.cljc | 36 ++++++++++++++-------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index fa1e506906..c231ba222c 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -518,19 +518,31 @@ ;; --- SHAPE UPDATE (defn- get-token-groups + "Get the sync attrs groups that are affected by changes in applied tokens. + + If any token has been applied or unapplied in the shape, calculate the corresponding + attributes and get the groups. If some of the attributes are to be applied in the + content nodes of a text shape, also return the content groups (only for attributes, + so the text is not touched)." [shape new-applied-tokens] - (let [old-applied-tokens (d/nilv (:applied-tokens shape) #{}) - changed-token-attrs (filter #(not= (get old-applied-tokens %) (get new-applied-tokens %)) - ctt/all-keys) - text-shape? (= (:type shape) :text) - changed-groups (into #{} - (comp (mapcat #(if (and text-shape? (ctt/attrs-in-text-content %)) - [:content-group :text-content-attribute] - [(->> % - (ctt/token-attr->shape-attr) - (get ctk/sync-attrs))])) - (filter some?)) - changed-token-attrs)] + (let [old-applied-tokens (d/nilv (:applied-tokens shape) #{}) + changed-token-attrs (filter #(not= (get old-applied-tokens %) (get new-applied-tokens %)) + ctt/all-keys) + text-shape? (= (:type shape) :text) + attrs-in-text-content? (some #(ctt/attrs-in-text-content %) + changed-token-attrs) + + changed-groups (into #{} + (comp (map ctt/token-attr->shape-attr) + (map #(get ctk/sync-attrs %)) + (filter some?)) + changed-token-attrs) + + changed-groups (if (and text-shape? + (d/not-empty? changed-groups) + attrs-in-text-content?) + (conj changed-groups :content-group :text-content-attribute) + changed-groups)] changed-groups)) (defn set-shape-attr From 523373dfa27c0ca46e711265555fe9703f1e1fb5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 23 Jul 2025 12:09:15 +0200 Subject: [PATCH 12/12] :paperclip: Update .gitignore file --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 7e3654e7d8..b4ea7ee77c 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,7 @@ /backend/resources/public/assets /backend/resources/public/media /backend/target/ +/backend/experiments /bundle* /cd.md /clj-profiler/ @@ -51,9 +52,6 @@ /exporter/target /frontend/.storybook/preview-body.html /frontend/.storybook/preview-head.html -/frontend/cypress/fixtures/validuser.json -/frontend/cypress/videos/*/ -/frontend/cypress/videos/*/ /frontend/dist/ /frontend/npm-debug.log /frontend/out/ @@ -70,6 +68,8 @@ /vendor/svgclean/bundle*.js /web /library/target/ +/library/*.zip +/external clj-profiler/ node_modules