Compare commits

...

4 Commits

Author SHA1 Message Date
Eva Marco
8b220d3df8 ♻️ Add spanish translation 2025-07-28 17:25:42 +02:00
Alejandro Alonso
996feaf588 🐛 Fix switching theme for wasm render 2025-07-28 16:39:40 +02:00
Yamila Moreno
b5ed2ad17f Reuse github workflows (#6989) 2025-07-28 16:39:40 +02:00
Florian Schroedl
828ac8ffc8 Implement text case token 2025-07-25 18:06:14 +02:00
18 changed files with 186 additions and 34 deletions

View File

@@ -1,13 +1,6 @@
name: Build and Upload Penpot Bundles non-prod
name: Build and Upload Penpot Bundles
on:
# Create bundler for every tag
push:
tags:
- '**' # Pattern matched against refs/tags
# Create bundler every hour between 5:00 and 20:00 on working days
schedule:
- cron: '0 5-20 * * 1-5'
# Create bundler from manual action
workflow_dispatch:
inputs:
@@ -23,6 +16,12 @@ on:
options:
- individual
- all
workflow_call:
inputs:
gh_ref:
description: 'Name of the branch'
type: string
required: true
jobs:
build-bundles:
@@ -38,6 +37,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.gh_ref }}
- name: Extract somer useful variables
id: vars

11
.github/workflows/build-develop.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
name: Build and Upload Penpot DEVELOP Bundles
on:
schedule:
- cron: '16 5-20 * * 1-5'
jobs:
build-develop-bundle:
uses: ./.github/workflows/build-bundles.yml
with:
gh_ref: "develop"

11
.github/workflows/build-staging.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
name: Build and Upload Penpot STAGING Bundles
on:
schedule:
- cron: '0 5 * * 1-5'
jobs:
build-staging-bundle:
uses: ./.github/workflows/build-bundles.yml
with:
gh_ref: "staging"

View File

@@ -37,6 +37,7 @@
:font-family "fontFamilies"
:font-size "fontSizes"
:letter-spacing "letterSpacing"
:text-case "textCase"
:number "number"
:opacity "opacity"
:other "other"
@@ -154,7 +155,16 @@
(def font-family-keys (schema-keys schema:font-family))
(def typography-keys (set/union font-size-keys letter-spacing-keys font-family-keys))
(def ^:private schema:text-case
[:map
[:text-case {:optional true} token-name-ref]])
(def text-case-keys (schema-keys schema:text-case))
(def typography-keys (set/union font-size-keys
letter-spacing-keys
font-family-keys
text-case-keys))
;; TODO: Created to extract the font-size feature from the typography feature flag.
;; Delete this once the typography feature flag is removed.
@@ -191,6 +201,7 @@
schema:font-size
schema:letter-spacing
schema:font-family
schema:text-case
schema:dimensions])
(defn shape-attr->token-attrs
@@ -221,6 +232,7 @@
(font-size-keys shape-attr) #{shape-attr}
(letter-spacing-keys shape-attr) #{shape-attr}
(font-family-keys shape-attr) #{shape-attr}
(text-case-keys shape-attr) #{shape-attr}
(border-radius-keys shape-attr) #{shape-attr}
(sizing-keys shape-attr) #{shape-attr}
(opacity-keys shape-attr) #{shape-attr}

View File

@@ -157,6 +157,24 @@
:else {:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value value)]})))
(defn- parse-sd-token-text-case-value
"Parses `value` of a text-case `sd-token` into a map like `{:value \"uppercase\"}`.
If the `value` is not parseable and/or has missing references returns a map with `:errors`."
[value]
(let [normalized-value (str/lower (str/trim value))
valid? (contains? #{"none" "uppercase" "lowercase" "capitalize"} normalized-value)
references (seq (ctob/find-token-value-references value))]
(cond
valid?
{:value normalized-value}
references
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)]
:references references}
:else
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-text-case value)]})))
(defn process-sd-tokens
"Converts a StyleDictionary dictionary with resolved tokens (aka `sd-tokens`) back to clojure.
The `get-origin-token` argument should be a function that takes an
@@ -199,6 +217,7 @@
:color (parse-sd-token-color-value value)
:opacity (parse-sd-token-opacity-value value has-references?)
:stroke-width (parse-sd-token-stroke-width-value value has-references?)
:text-case (parse-sd-token-text-case-value value)
:number (parse-sd-token-number-value value)
(parse-sd-token-general-value value))
output-token (cond (:errors parsed-token-value)

View File

@@ -298,6 +298,13 @@
(when (number? value)
(generate-text-shape-update {:font-size (str value)} shape-ids page-id))))
(defn update-text-case
([value shape-ids attributes] (update-text-case value shape-ids attributes nil))
([value shape-ids _attributes page-id]
(when (string? value)
(generate-text-shape-update {:text-transform value} shape-ids page-id))))
;; Events to apply / unapply tokens to shapes ------------------------------------------------------------
(defn apply-token
@@ -464,6 +471,14 @@
:fields [{:label "Font Family"
:key :font-family}]}}
:text-case
{:title "Text Case"
:attributes ctt/text-case-keys
:on-update-shape update-text-case
:modal {:key :tokens/text-case
:fields [{:label "Text Case"
:key :text-case}]}}
:stroke-width
{:title "Stroke Width"
:attributes ctt/stroke-width-keys

View File

@@ -72,6 +72,10 @@
{:error/code :error.style-dictionary/invalid-token-value-stroke-width
:error/fn #(str/join "\n" [(str (tr "workspace.tokens.invalid-value" %) ".") (tr "workspace.tokens.stroke-width-range")])}
:error.style-dictionary/invalid-token-value-text-case
{:error/code :error.style-dictionary/invalid-token-value-text-case
:error/fn #(tr "workspace.tokens.invalid-text-case-token-value" %)}
:error/unknown
{:error/code :error/unknown
:error/fn #(tr "labels.unknown-error")}})

View File

@@ -36,6 +36,7 @@
#{:font-size} dwta/update-font-size
#{:letter-spacing} dwta/update-letter-spacing
#{:font-family} dwta/update-font-family
#{:text-case} dwta/update-text-case
#{:x :y} dwta/update-shape-position
#{:p1 :p2 :p3 :p4} dwta/update-layout-padding
#{:m1 :m2 :m3 :m4} dwta/update-layout-item-margin

View File

@@ -268,6 +268,7 @@
(let [stroke-width (partial generic-attribute-actions #{:stroke-width} "Stroke Width")
font-size (partial generic-attribute-actions #{:font-size} "Font Size")
line-height #(generic-attribute-actions #{:line-height} "Line Height" (assoc % :on-update-shape dwta/update-line-height))
text-case (partial generic-attribute-actions #{:text-case} "Text Case")
border-radius (partial all-or-separate-actions {:attribute-labels {:r1 "Top Left"
:r2 "Top Right"
:r4 "Bottom Left"
@@ -291,6 +292,7 @@
(when (seq line-height) line-height))))
:stroke-width stroke-width
:font-size font-size
:text-case text-case
:dimensions (fn [context-data]
(-> (concat
(when (seq (sizing-attribute-actions context-data)) [{:title "Sizing" :submenu :sizing}])

View File

@@ -787,10 +787,18 @@
:custom-input-token-value font-picker*
:on-value-resolve on-value-resolve})]))
(mf/defc text-case-form*
[{:keys [token] :rest props}]
(let [placeholder (tr "workspace.tokens.text-case-value-enter")]
[:> form*
(mf/spread-props props {:token token
:input-placeholder placeholder})]))
(mf/defc form-wrapper*
[{:keys [token token-type] :as props}]
(let [token-type' (or (:type token) token-type)]
(case token-type'
:color [:> color-form* props]
:font-family [:> font-family-form* props]
:text-case [:> text-case-form* props]
[:> form* props])))

View File

@@ -197,3 +197,9 @@
::mf/register-as :tokens/font-family}
[properties]
[:& token-update-create-modal properties])
(mf/defc text-case-modal
{::mf/register modal/components
::mf/register-as :tokens/text-case}
[properties]
[:& token-update-create-modal properties])

View File

@@ -30,6 +30,7 @@
:font-family "text-font-family"
:font-size "text-font-size"
:letter-spacing "text-letterspacing"
:text-case "text-mixed"
:opacity "percentage"
:number "number"
:rotation "rotation"

View File

@@ -176,7 +176,7 @@
selected-shapes)))
(def token-types-with-status-icon
#{:color :border-radius :rotation :sizing :dimensions :opacity :spacing :stroke-width})
#{:color :border-radius :rotation :sizing :dimensions :opacity :spacing :stroke-width :text-case})
(mf/defc token-pill*
{::mf/wrap [mf/memo]}

View File

@@ -1065,3 +1065,9 @@
(js/console.error cause)
(p/resolved false)))))
(p/resolved false))))
(defn shape-in-current-page?
"Check if a shape is in the current page by looking up the current page objects"
[shape-id]
(let [objects (deref refs/workspace-page-objects)]
(contains? objects shape-id)))

View File

@@ -208,31 +208,13 @@
(defn set-wasm-multi-attrs!
[shape properties]
(api/use-shape (:id shape))
(let [result
(->> properties
(mapcat #(set-wasm-single-attr! shape %)))
pending (-> (d/index-by :key :callback result) vals)]
(if (and pending (seq pending))
(->> (rx/from pending)
(rx/mapcat (fn [callback] (callback)))
(rx/reduce conj [])
(rx/subs!
(fn [_]
(api/update-shape-tiles)
(api/clear-drawing-cache)
(api/request-render "set-wasm-attrs-pending"))))
(do
(api/update-shape-tiles)
(api/request-render "set-wasm-attrs")))))
(defn set-wasm-attrs!
[shape k v]
(let [shape (assoc shape k v)]
;; Only call WASM API if the shape is in the current page
(when (api/shape-in-current-page? (:id shape))
(api/use-shape (:id shape))
(let [result (set-wasm-single-attr! shape k)
(let [result
(->> properties
(mapcat #(set-wasm-single-attr! shape %)))
pending (-> (d/index-by :key :callback result) vals)]
;; TODO: set-wasm-attrs is called twice with every set
(if (and pending (seq pending))
(->> (rx/from pending)
(rx/mapcat (fn [callback] (callback)))
@@ -246,6 +228,28 @@
(api/update-shape-tiles)
(api/request-render "set-wasm-attrs"))))))
(defn set-wasm-attrs!
[shape k v]
(let [shape (assoc shape k v)]
;; Only call WASM API if the shape is in the current page
(when (api/shape-in-current-page? (:id shape))
(api/use-shape (:id shape))
(let [result (set-wasm-single-attr! shape k)
pending (-> (d/index-by :key :callback result) vals)]
;; TODO: set-wasm-attrs is called twice with every set
(if (and pending (seq pending))
(->> (rx/from pending)
(rx/mapcat (fn [callback] (callback)))
(rx/reduce conj [])
(rx/subs!
(fn [_]
(api/update-shape-tiles)
(api/clear-drawing-cache)
(api/request-render "set-wasm-attrs-pending"))))
(do
(api/update-shape-tiles)
(api/request-render "set-wasm-attrs")))))))
(defn- impl-assoc
[self k v]
(when ^boolean shape/*wasm-sync*
@@ -274,7 +278,9 @@
[self k]
(when ^boolean shape/*wasm-sync*
(binding [shape/*wasm-sync* false]
(set-wasm-attrs! self k nil)))
;; Only call WASM API if the shape is in the current page
(when (api/shape-in-current-page? (.-id ^ShapeProxy self))
(set-wasm-attrs! self k nil))))
(case k
:id

View File

@@ -592,6 +592,40 @@
(t/is (= (:font-family (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:font-family style-text-blocks) (:font-id txt/default-text-attrs))))))))))
(t/deftest test-apply-text-case
(t/testing "applies text-case token and updates the text transform"
(t/async
done
(let [text-case-token {:name "uppercase-case"
:value "uppercase"
:type :text-case}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token text-case-token))))
store (ths/setup-store file)
text-1 (cths/get-shape file :text-1)
events [(dwta/apply-token {:shape-ids [(:id text-1)]
:attributes #{:text-case}
:token (toht/get-token file "uppercase-case")
:on-update-shape dwta/update-text-case})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
token-target' (toht/get-token file' "uppercase-case")
text-1' (cths/get-shape file' :text-1)
style-text-blocks (->> (:content text-1')
(txt/content->text+styles)
(remove (fn [[_ text]] (str/empty? (str/trim text))))
(mapv (fn [[style text]]
{:styles (merge txt/default-text-attrs style)
:text-content text}))
(first)
(:styles))]
(t/is (some? (:applied-tokens text-1')))
(t/is (= (:text-case (:applied-tokens text-1')) (:name token-target')))
(t/is (= (:text-transform style-text-blocks) "uppercase")))))))))
(t/deftest test-toggle-token-none
(t/testing "should apply token to all selected items, where no item has the token applied"
(t/async

View File

@@ -7253,6 +7253,14 @@ msgstr "Multiple files"
msgid "workspace.tokens.export.no-tokens-themes-sets"
msgstr "There are no tokens, themes or sets to export."
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:602
msgid "workspace.tokens.text-case-value-enter"
msgstr "Enter text case: none | Uppercase | Lowercase | Capitalize"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:109
msgid "workspace.tokens.invalid-text-case-token-value"
msgstr "Invalid token value: only none, Uppercase, Lowercase or Capitalize are accepted"
#: src/app/main/ui/workspace/tokens/modals/export.cljs:33
msgid "workspace.tokens.export.preview"
msgstr "Preview:"

View File

@@ -7227,6 +7227,14 @@ msgstr "Múltiples ficheros"
msgid "workspace.tokens.export.no-tokens-themes-sets"
msgstr "No existen tokens, temas o sets para exportar."
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:602
msgid "workspace.tokens.text-case-value-enter"
msgstr "Introduce una capitalización: none | Uppercase | Lowercase | Capitalize"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:109
msgid "workspace.tokens.invalid-text-case-token-value"
msgstr "Valor de token no valido: Los únicos valores aceptados son none, Uppercase, Lowercase y Capitalize"
#: src/app/main/ui/workspace/tokens/modals/export.cljs:33
msgid "workspace.tokens.export.preview"
msgstr "Previsualizar:"