Compare commits

...

23 Commits

Author SHA1 Message Date
Andrey Antukh
04f573cb23 WIP 2025-08-19 14:09:07 +02:00
Andrey Antukh
d46ad9d6f1 Add generative test case for openapi json serialization
This will prevent possible regression on introducing schemas without
generators or schema with generators that can't be serialized to json.
2025-08-19 11:34:50 +02:00
Andrey Antukh
2e8d08d047 🐛 Use ::sm/any instead of any for get-file-fragment rpc method schema
The usage of `any?` predicate as-is uses the standard any generator that
causes to generate java.lang.Character instances created that are not
properly serialiable to JSON. The `::sm/any` schema delimits the
generator to a commonly known serializable types on json.
2025-08-19 11:32:41 +02:00
Andrey Antukh
9c57596954 🐛 Add missing generators 2025-08-19 10:49:17 +02:00
Andrey Antukh
cc41fb569d 🔥 Remove already deprecated change spec 2025-08-19 10:49:00 +02:00
Andrey Antukh
79ad322809 📎 Update .gitignore file 2025-08-19 10:49:00 +02:00
Andrey Antukh
362ea401a0 🐛 Add missing attrs to add-component change schema 2025-08-19 10:49:00 +02:00
Andrey Antukh
7afb5abf35 🐛 Add proper schema for decoding :obj on add-obj change 2025-08-19 10:49:00 +02:00
Andrey Antukh
1b16129c45 🐛 Fix incorrect handling of assign operation on changes protocol 2025-08-19 10:49:00 +02:00
Eva Marco
ff2e845f2c 🐛 Fix double click on set name input (#7096) 2025-08-13 09:23:53 +02:00
Alejandro Alonso
8e0a6e4123 🐛 Fix auto height is fixed in the HTML inspect tab for text elements (#7078) 2025-08-11 09:07:43 +02:00
Marina López
0131cd6f8b Display the total price of the subscription and the cap amount (#7088) 2025-08-11 09:07:24 +02:00
Andrey Antukh
288a7b21d6 Merge tag '2.9.0-RC8' 2025-08-08 09:47:42 +02:00
andrés gonzález
32bd08533d 💄 Remove slide about overrides in the release notes (#7086) 2025-08-08 09:46:40 +02:00
Yamila Moreno
c1aae12327 📎 Improve gh actions 2025-08-07 18:08:25 +02:00
Yamila Moreno
23a6f4b7c1 📎 Improve gh actions 2025-08-07 18:07:47 +02:00
Andrey Antukh
133e6e1e68 Merge tag '2.9.0-RC7' 2025-08-07 16:30:30 +02:00
Andrey Antukh
6abd045273 🐛 Add missing generator for token-set file change operation (#7080)
* 🐛 Add missing generator for token-set file change operation

* 🐛 Use ::sm/any instead of :any for on get-file-data-for-thumbnail rpc method

Mainly because :any will use a very generic generator that can generate
instances of Character that are not directly serializable to JSON
2025-08-07 12:36:14 +02:00
Marina López
778a608854 🐛 Fix tooltip for icon plans from team dropdown (#7075) 2025-08-07 07:43:49 +02:00
Marina López
a76a9fae41 🐛 Fix an unused translation (#7074) 2025-08-06 13:28:02 +02:00
Andrey Antukh
f7cfbdd229 🐛 Comment the problematic migration 2025-08-05 22:05:52 +02:00
Andrey Antukh
e28d2842f6 🐛 Revert the revert of orientation detection on media
This reverts commit 515cbf7bef.
2025-08-05 22:03:09 +02:00
Francis Santiago
4501d13961 📚 Clarify OpenShift requirements (#6937)
* 📚 Clarify OpenShift requirements

* 📚 Remove the click for expanding
2025-08-01 16:26:04 +02:00
25 changed files with 440 additions and 309 deletions

View File

@@ -1,28 +1,14 @@
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
# Create bundle from manual action
workflow_dispatch:
workflow_call:
inputs:
zip_mode:
# zip_mode defines how the build artifacts are packaged:
# - 'individual': creates one ZIP file per component (frontend, backend, exporter)
# - 'all': creates a single ZIP containing all components
# - null: for the rest of cases (non-manual events)
description: 'Bundle packaging mode'
required: false
default: 'individual'
type: choice
options:
- individual
- all
gh_ref:
description: 'Name of the branch'
type: string
required: true
jobs:
build-bundles:
@@ -38,15 +24,15 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.gh_ref }}
- name: Extract somer useful variables
- name: Extract some useful variables
id: vars
run: |
echo "commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "gh_branch=${{ github.base_ref || github.ref_name }}" >> $GITHUB_OUTPUT
# Set up Docker Buildx for multi-arch build
- name: Set up Docker Buildx
- name: Set up Docker Buildx for multi-arch build
uses: docker/setup-buildx-action@v3
- name: Run manage.sh build-bundle from host
@@ -57,73 +43,22 @@ jobs:
mkdir zips
mv bundles penpot
- name: Create zip bundles for zip_mode == 'all'
if: ${{ github.event.inputs.zip_mode == 'all' }}
- name: Create zip bundles
run: |
echo "📦 Packaging Penpot 'all' bundles..."
zip -r zips/penpot-all-bundles.zip penpot
echo "📦 Packaging Penpot bundles..."
zip -r zips/penpot.zip penpot
- name: Create zip bundles for zip_mode != 'all'
if: ${{ github.event.inputs.zip_mode != 'all' }}
- name: Upload Penpot bundle to S3
run: |
echo "📦 Packaging Penpot 'individual' bundles..."
zip -r zips/penpot-frontend.zip penpot/frontend
zip -r zips/penpot-backend.zip penpot/backend
zip -r zips/penpot-exporter.zip penpot/exporter
aws s3 cp zips/penpot.zip s3://${{ secrets.S3_BUCKET }}/penpot-${{ steps.vars.outputs.gh_branch}}-latest.zip
aws s3 cp zips/penpot.zip s3://${{ secrets.S3_BUCKET }}/penpot-${{ steps.vars.outputs.commit_hash }}.zip
- name: Upload unified 'all' bundle
if: ${{ github.event.inputs.zip_mode == 'all' }}
uses: actions/upload-artifact@v4
with:
name: penpot-all-bundles
path: zips/penpot-all-bundles.zip
- name: Upload individual bundles
if: ${{ github.event.inputs.zip_mode != 'all' }}
uses: actions/upload-artifact@v4
with:
name: penpot-individual-bundles
path: |
zips/penpot-frontend.zip
zips/penpot-backend.zip
zips/penpot-exporter.zip
- name: Upload unified 'all' bundle to S3
if: ${{ github.event.inputs.zip_mode == 'all' }}
run: |
aws s3 cp zips/penpot-all-bundles.zip s3://${{ secrets.S3_BUCKET }}/penpot-all-bundles-${{ steps.vars.outputs.gh_branch}}.zip
aws s3 cp zips/penpot-all-bundles.zip s3://${{ secrets.S3_BUCKET }}/penpot-all-bundles-${{ steps.vars.outputs.commit_hash }}.zip
- name: Upload 'individual' bundles to S3
if: ${{ github.event.inputs.zip_mode != 'all' }}
run: |
for name in penpot-frontend penpot-backend penpot-exporter; do
aws s3 cp zips/${name}.zip s3://${{ secrets.S3_BUCKET }}/${name}-${{ steps.vars.outputs.gh_branch }}-latest.zip
aws s3 cp zips/${name}.zip s3://${{ secrets.S3_BUCKET }}/${name}-${{ steps.vars.outputs.commit_hash }}.zip
done
- name: Notify Mattermost about automatic bundles
if: github.event_name == 'pull_request'
- name: Notify Mattermost
if: failure()
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
TEXT: |
📦 *Penpot bundle automatically generated*
📄 PR: ${{ github.event.pull_request.title }}
🔁 From: \`${{ github.head_ref }}\` to \`{{ github.base_ref }}\`
*[PENPOT] Error during the execution of the job*
📄 Triggered from ref: `${{ steps.vars.outputs.gh_branch}}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: Notify Mattermost about manual bundles
if: github.event_name == 'workflow_dispatch'
uses: mattermost/action-mattermost-notify@master
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
TEXT: |
📦 *Penpot bundle manually generated*
📄 Triggered from branch: `${{ github.ref_name}}`
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
- name: Print artifact summary URL
run: |
echo "📦 Artifacts available at:"
echo "🔗 https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"

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

@@ -0,0 +1,12 @@
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
secrets: inherit
with:
gh_ref: "develop"

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

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

1
.gitignore vendored
View File

@@ -31,6 +31,7 @@
/.clj-kondo/.cache
/_dump
/notes
/playground/
/backend/*.md
/backend/*.sql
/backend/*.txt

View File

@@ -28,6 +28,8 @@
- 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)
- Add info to apply-token event [Taiga #11710](https://tree.taiga.io/project/penpot/task/11710)
- Fix double click on set name input [Taiga #11747](https://tree.taiga.io/project/penpot/issue/11747)
### :bug: Bugs fixed
@@ -58,6 +60,7 @@
- Fix export button width on inspect tab [Taiga #11394](https://tree.taiga.io/project/penpot/issue/11394)
- Fix stroke width token application [Taiga #11724](https://tree.taiga.io/project/penpot/issue/11724)
- Fix number token application on shape [Taiga #11331](https://tree.taiga.io/project/penpot/task/11331)
- Fix auto height is fixed in the HTML inspect tab for text elements [Taiga #11680](https://tree.taiga.io/project/penpot/task/11680)
## 2.8.1

View File

@@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.media :as cm]
[app.common.schema :as sm]
[app.common.schema.openapi :as-alias oapi]
@@ -21,6 +22,7 @@
[buddy.core.bytes :as bb]
[buddy.core.codecs :as bc]
[clojure.java.shell :as sh]
[clojure.string]
[clojure.xml :as xml]
[cuerdas.core :as str]
[datoteka.fs :as fs]
@@ -215,6 +217,23 @@
{:width (int width)
:height (int height)})))]))
(defn- get-dimensions-with-orientation [^String path]
;; Image magick doesn't give info about exif rotation so we use the identify command
;; If we are processing an animated gif we use the first frame with -scene 0
(let [dim-result (sh/sh "identify" "-format" "%w %h\n" path)
orient-result (sh/sh "identify" "-format" "%[EXIF:Orientation]\n" path)]
(if (and (= 0 (:exit dim-result))
(= 0 (:exit orient-result)))
(let [[w h] (-> (:out dim-result)
str/trim
(clojure.string/split #"\s+")
(->> (mapv #(Integer/parseInt %))))
orientation (-> orient-result :out str/trim)]
(case orientation
("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees
{:width w :height h})) ; Normal or unknown orientation
nil)))
(defmethod process :info
[{:keys [input] :as params}]
(let [{:keys [path mtype] :as input} (check-input input)]
@@ -234,13 +253,17 @@
:code :media-type-mismatch
:hint (str "Seems like you are uploading a file whose content does not match the extension."
"Expected: " mtype ". Got: " mtype')))
;; For an animated GIF, getImageWidth/Height returns the delta size of one frame (if no frame given
;; it returns size of the last one), whereas getPageWidth/Height always return the full size of
;; any frame.
(assoc input
:width (.getPageWidth instance)
:height (.getPageHeight instance)
:ts (dt/now))))))
(let [{:keys [width height]}
(or (get-dimensions-with-orientation (str path))
(do
(l/warn "Failed to read image dimensions with orientation; falling back to im4java"
{:path path})
{:width (.getPageWidth instance)
:height (.getPageHeight instance)}))]
(assoc input
:width width
:height height
:ts (dt/now)))))))
(defmethod process-error org.im4java.core.InfoException
[error]

View File

@@ -357,7 +357,7 @@
[:id ::sm/uuid]
[:file-id ::sm/uuid]
[:created-at ::dt/instant]
[:content any?]])
[:content ::sm/any]])
(def schema:get-file-fragment
[:map {:title "get-file-fragment"}

View File

@@ -185,7 +185,7 @@
[:map {:title "PartialFile"}
[:id ::sm/uuid]
[:revn {:min 0} ::sm/int]
[:page :any]])
[:page [:map-of :keyword ::sm/any]]])
(sv/defmethod ::get-file-data-for-thumbnail
"Retrieves the data for generate the thumbnail of the file. Used

View File

@@ -0,0 +1,36 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns backend-tests.rpc-doc-test
"Internal binfile test, no RPC involved"
(:require
[app.common.json :as json]
[app.common.pprint :as pp]
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.schema.test :as smt]
[app.rpc :as-alias rpc]
[app.rpc.doc :as rpc.doc]
[backend-tests.helpers :as th]
[clojure.test :as t]))
(t/use-fixtures :once th/state-init)
(t/deftest openapi-context-json-encode
(smt/check!
(smt/for [context (->> sg/int
(sg/fmap (fn [_]
(rpc.doc/prepare-openapi-context (::rpc/methods th/*system*)))))]
(try
(json/encode context)
true
(catch Throwable _cause
false)))
{:num 30}))

View File

@@ -177,12 +177,6 @@
:title "Change"
:decode/json #(update % :type keyword)
::smd/simplified true}
[:set-option
;; DEPRECATED: remove before 2.3 release
;;
;; Is still there for not cause error when event is received
[:map {:title "SetOptionChange"}]]
[:set-comment-thread-position
[:map
@@ -195,7 +189,7 @@
[:map {:title "AddObjChange"}
[:type [:= :add-obj]]
[:id ::sm/uuid]
[:obj :map]
[:obj cts/schema:shape]
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:frame-id ::sm/uuid]
@@ -328,7 +322,9 @@
[:id ::sm/uuid]
[:name :string]
[:shapes {:optional true} [:vector {:gen/max 3} ::sm/any]]
[:path {:optional true} :string]]]
[:path {:optional true} :string]
[:main-instance-id ::sm/uuid]
[:main-instance-page ::sm/uuid]]]
[:mod-component
[:map {:title "ModCompoenentChange"}
@@ -418,7 +414,14 @@
[:type [:= :set-token-set]]
[:set-name :string]
[:group? :boolean]
[:token-set [:maybe [:fn ctob/token-set?]]]]]
;; FIXME: we should not pass private types as part of changes
;; protocol, the changes protocol should reflect a
;; method/protocol for perform surgical operations on file data,
;; this has nothing todo with internal types of a file data
;; structure.
[:token-set {:gen/gen (sg/generator ctob/schema:token-set)}
[:maybe [:fn ctob/token-set?]]]]]
[:set-token
[:map {:title "SetTokenChange"}
@@ -539,11 +542,6 @@
#?(:clj (validate-shapes! data result items))
result))))
;; DEPRECATED: remove after 2.3 release
(defmethod process-change :set-option
[data _]
data)
;; --- Comment Threads
(defmethod process-change :set-comment-thread-position
@@ -1083,21 +1081,23 @@
;; === Operations
(def ^:private decode-shape
(sm/decoder cts/schema:shape sm/json-transformer))
(def decode-shape-attrs
(sm/decoder cts/schema:shape-attrs sm/json-transformer))
(defmethod process-operation :assign
[{:keys [type] :as shape} {:keys [value] :as op}]
(let [modifications (assoc value :type type)
modifications (decode-shape modifications)]
modifications (decode-shape-attrs modifications)]
(reduce-kv (fn [shape k v]
(process-operation shape {:type :set
:attr k
:val v
:ignore-touched (:ignore-touched op)
:ignore-geometry (:ignore-geometry op)}))
(if (not= v (get shape k))
(process-operation shape {:type :set
:attr k
:val v
:ignore-touched (:ignore-touched op)
:ignore-geometry (:ignore-geometry op)})
shape))
shape
modifications)))
(dissoc modifications :type))))
(defmethod process-operation :set
[shape op]

View File

@@ -1618,4 +1618,4 @@
"0007-clear-invalid-strokes-and-fills-v2"
"0008-fix-library-colors-v4"
"0009-clean-library-colors"
"0009-add-partial-text-touched-flags"]))
#_"0009-add-partial-text-touched-flags"]))

View File

@@ -111,7 +111,7 @@
(defmethod visit :fn [_ _ _ _] "FN")
(defmethod visit :vector [_ _ children _]
(str "[" (last children) "]"))
(str "[" (str/trim (last children)) "]"))
(defn -tagged [children] (map (fn [[tag _ c]] (str c " (tag: " tag ")")) children))
@@ -137,8 +137,14 @@
(some? suffix)
(str suffix))))
(defmethod visit :map-of [_ _ children _]
(str "map[" (first children) "," (second children) "]"))
(defmethod visit :map-of
[_ schema children _]
(let [props (m/properties schema)
title (some->> (:title props) str/camel str/capital)]
(str (if title
(str "type " title ": ")
"")
"map[" (first children) "," (second children) "]")))
(defmethod visit :union [_ _ children _]
(str/join " | " children))
@@ -156,61 +162,123 @@
(or (:title props)
"*")))
(defn- format-map
[schema children]
(let [props (m/properties schema)
closed? (get props :closed)
title (some->> (:title props) str/camel str/capital)
level 0
optional (into #{} (comp (filter (m/-comp :optional second))
(map first))
children)
entries (->> children
(map (fn [[k _ s]]
;; NOTE: maybe we can detect multiple lines
;; and then just insert a break line
(str (pad " " level) (str/camel k)
(when (contains? optional k) "?")
": " (str/trim s))))
(str/join ",\n"))
header (cond-> (str "type " title)
closed? (str "!")
(some? title) (str " "))]
(str (pad header level) "{\n" entries "\n" (pad "}\n" level))))
(defmethod visit :map
[_ schema children {:keys [::level ::max-level] :as options}]
(let [props (m/properties schema)
closed? (:closed props)
title (some->> (:title props) str/camel str/capital)]
(let [props (m/properties schema)
closed? (get props :closed)
title (some->> (:title props) str/camel str/capital)
extracted? (get props ::extracted false)]
(if (>= level max-level)
(or (some-> title str)
"<untitled>")
(let [optional (into #{} (comp (filter (m/-comp :optional second))
(map first))
children)
entries (->> children
(map (fn [[k _ s]]
(str (pad " " level) (str/camel k)
(when (contains? optional k) "?")
": " s)))
(str/join ",\n"))
(if (= level 0)
(format-map schema children)
header (cond-> (str "type " title)
closed? (str "!")
(some? title) (str " "))]
(if title
(if extracted?
(format-map schema children)
(let [schema (mu/update-properties schema assoc ::extracted true)]
(swap! *definitions* conj (describe* schema options))
(pad title level)))
(format-map schema children)))))
(str (pad header level) "{\n" entries "\n" (pad "}\n" level))))))
(defn format-multi
[s children]
(let [props (m/properties s)
title (some-> (:title props) str/camel str/capital)
dispatcher (or (-> s m/properties :dispatch-description)
(-> s m/properties :dispatch))
(defmethod visit :multi
[_ s children {:keys [::level ::max-level] :as options}]
(let [props (m/properties s)
title (some-> (:title props) str/camel str/capital)]
(if (>= level max-level)
title
(let [dispatcher (or (-> s m/properties :dispatch-description)
(-> s m/properties :dispatch))
entries (->> children
(map (fn [[_ _ shape]]
(str shape)))
(str/join ",\n"))
prefix (apply str (take (inc level) (repeat " ")))
entries (->> children
(map (fn [[_ _ shape]]
(str prefix shape)))
(str/join ",\n"))
header (cond-> "multi"
header (cond-> "multi"
(some? title) (str " " title)
:always (str " [dispatch=" (d/name dispatcher) "]"))]
(str header " {\n" entries "\n" (pad "}" level))))))
(str header " {\n" entries "\n}")))
(defmethod visit :multi
[_ schema children {:keys [::level ::max-level] :as options}]
(let [props (m/properties schema)
title (some-> (:title props) str/camel str/capital)
extracted? (get props ::extracted false)]
(cond
(zero? level)
(format-multi schema children)
(and title extracted?)
(format-multi schema children)
(and title (not extracted?))
(let [schema (mu/update-properties schema assoc ::extracted true)]
(swap! *definitions* conj (describe* schema options))
(pad title level))
:else
(format-multi schema children))))
(defn- format-merge
[schema children]
(let [props (m/properties schema)
entries (->> children
(map (fn [shape]
(str shape)))
(str/join ",\n"))
title (some-> (:title props) str/camel str/capital)
header (str "merge type " title)]
(str header " {\n" entries "\n}")))
(defmethod visit :merge
[_ schema children _]
(let [entries (str/join ",\n" children)
props (m/properties schema)
title (or (some-> (:title props) str/camel str/capital)
"<untitled>")]
(str "merge type " title " { \n" entries "\n}\n")))
[_ schema children {:keys [::level ::max-level] :as options}]
(let [props (m/properties schema)
title (some-> (:title props) str/camel str/capital)
extracted? (get props ::extracted false)]
(cond
(zero? level)
(format-merge schema children)
(and title extracted?)
(format-merge schema children)
(and title (not extracted?))
(let [schema (mu/update-properties schema assoc ::extracted true)]
(swap! *definitions* conj (describe* schema options))
(pad title level))
:else
(format-merge schema children))))
(defmethod visit ::sm/one-of
[_ _ children _]
@@ -219,8 +287,21 @@
(map d/name)
(str/join "|")) ")")))
(defmethod visit :schema [_ schema children options]
(visit ::m/schema schema children options))
(defmethod visit :schema
[_ schema children options]
(let [props (m/properties schema)
title (some-> (:title props) str/camel str/capital)
extracted? (get props ::extracted false)]
(if title
(if extracted?
(str "type " title ": "
(visit ::m/schema schema children options))
(let [schema (mu/update-properties schema assoc ::extracted true)]
(swap! *definitions* conj (describe* schema options))
title))
(visit ::m/schema schema children options))))
(defmethod visit ::m/schema
[_ schema _ {:keys [::level ::limit ::max-level] :as options}]
@@ -239,9 +320,12 @@
(describe* schema' options)))
(and ref title)
(do
(when (<= limit max-level)
(swap! *definitions* conj (describe* schema' (assoc options ::base-limit limit))))
(let [options (-> options
(assoc ::base-level level)
)]
;; (when (<= limit max-level)
(swap! *definitions* conj (describe* schema' options))
title)
@@ -254,10 +338,11 @@
(describe* schema' (assoc options ::base-level level ::base-limit limit)))))
(defn describe* [s options]
(letfn [(walk-fn [schema path children {:keys [::base-level ::base-limit] :or {base-level 0 base-limit 0} :as options}]
(let [options (assoc options
::limit (+ base-limit (count path))
::level (+ base-level (count path)))]
(letfn [(walk-fn [schema path children {:keys [::base-level ::base-limit]
:or {base-level 0 base-limit 0} :as options}]
(let [options (-> options
(assoc ::limit (+ base-limit (count path)))
(assoc ::level (+ base-level (count path))))]
(visit (m/type schema) schema children options)))]
(m/walk s walk-fn options)))

View File

@@ -24,7 +24,7 @@
(def schema:plugin-data
(sm/register!
^{::sm/type ::plugin-data}
[:map-of {:gen/max 5}
[:map-of {:gen/max 5 :title "PluginsData"}
schema:keyword
[:map-of {:gen/max 5}
schema:string

View File

@@ -185,50 +185,50 @@
[:height ::sm/safe-number]])
(def schema:shape-generic-attrs
[:map {:title "ShapeAttrs"}
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
[:component-file {:optional true} ::sm/uuid]
[:component-root {:optional true} :boolean]
[:main-instance {:optional true} :boolean]
[:remote-synced {:optional true} :boolean]
[:shape-ref {:optional true} ::sm/uuid]
[:touched {:optional true} [:maybe [:set :keyword]]]
[:blocked {:optional true} :boolean]
[:collapsed {:optional true} :boolean]
[:locked {:optional true} :boolean]
[:hidden {:optional true} :boolean]
[:masked-group {:optional true} :boolean]
[:fills {:optional true}
[:vector {:gen/max 2} schema:fill]]
[:proportion {:optional true} ::sm/safe-number]
[:proportion-lock {:optional true} :boolean]
[:constraints-h {:optional true}
[::sm/one-of horizontal-constraint-types]]
[:constraints-v {:optional true}
[::sm/one-of vertical-constraint-types]]
[:fixed-scroll {:optional true} :boolean]
[:r1 {:optional true} ::sm/safe-number]
[:r2 {:optional true} ::sm/safe-number]
[:r3 {:optional true} ::sm/safe-number]
[:r4 {:optional true} ::sm/safe-number]
[:opacity {:optional true} ::sm/safe-number]
[:grids {:optional true}
[:vector {:gen/max 2} ::ctg/grid]]
[:exports {:optional true}
[:vector {:gen/max 2} ::ctse/export]]
[:strokes {:optional true}
[:vector {:gen/max 2} schema:stroke]]
[:blend-mode {:optional true}
[::sm/one-of blend-modes]]
[:map {:title "ShapeGenericAttrs"}
;; [:page-id {:optional true} ::sm/uuid]
;; [:component-id {:optional true} ::sm/uuid]
;; [:component-file {:optional true} ::sm/uuid]
;; [:component-root {:optional true} :boolean]
;; [:main-instance {:optional true} :boolean]
;; [:remote-synced {:optional true} :boolean]
;; [:shape-ref {:optional true} ::sm/uuid]
;; [:touched {:optional true} [:maybe [:set :keyword]]]
;; [:blocked {:optional true} :boolean]
;; [:collapsed {:optional true} :boolean]
;; [:locked {:optional true} :boolean]
;; [:hidden {:optional true} :boolean]
;; [:masked-group {:optional true} :boolean]
;; [:fills {:optional true}
;; [:vector {:gen/max 2} schema:fill]]
;; [:proportion {:optional true} ::sm/safe-number]
;; [:proportion-lock {:optional true} :boolean]
;; [:constraints-h {:optional true}
;; [::sm/one-of horizontal-constraint-types]]
;; [:constraints-v {:optional true}
;; [::sm/one-of vertical-constraint-types]]
;; [:fixed-scroll {:optional true} :boolean]
;; [:r1 {:optional true} ::sm/safe-number]
;; [:r2 {:optional true} ::sm/safe-number]
;; [:r3 {:optional true} ::sm/safe-number]
;; [:r4 {:optional true} ::sm/safe-number]
;; [:opacity {:optional true} ::sm/safe-number]
;; [:grids {:optional true}
;; [:vector {:gen/max 2} ::ctg/grid]]
;; [:exports {:optional true}
;; [:vector {:gen/max 2} ::ctse/export]]
;; [:strokes {:optional true}
;; [:vector {:gen/max 2} schema:stroke]]
;; [:blend-mode {:optional true}
;; [::sm/one-of blend-modes]]
[:interactions {:optional true}
[:vector {:gen/max 2} ::ctsi/interaction]]
[:shadow {:optional true}
[:vector {:gen/max 1} ctss/schema:shadow]]
[:blur {:optional true} ::ctsb/blur]
;; [:shadow {:optional true}
;; [:vector {:gen/max 1} ctss/schema:shadow]]
;; [:blur {:optional true} ::ctsb/blur]
[:grow-type {:optional true}
[::sm/one-of grow-types]]
[:applied-tokens {:optional true} cto/schema:applied-tokens]
;; [:applied-tokens {:optional true} cto/schema:applied-tokens]
[:plugin-data {:optional true} ::ctpg/plugin-data]])
(def schema:group-attrs
@@ -306,6 +306,15 @@
(merge attrs1 shape attrs2 attrs3)))))
(sg/fmap create-shape)))
(def kaka
[:merge {:title "BoolShape"}
ctsl/schema:layout-child-attrs
schema:bool-attrs
schema:shape-generic-attrs
schema:shape-base-attrs])
(def schema:shape-attrs
[:multi {:dispatch :type
:decode/json (fn [shape]

View File

@@ -175,21 +175,23 @@
[:url :string]])
(def schema:interaction
[:and {:title "Interaction"
:gen/gen (sg/one-of (sg/generator schema:navigate-interaction)
(sg/generator schema:open-overlay-interaction)
(sg/generator schema:close-overlay-interaction)
(sg/generator schema:toggle-overlay-interaction)
(sg/generator schema:prev-scren-interaction)
(sg/generator schema:open-url-interaction))}
schema:interaction-attrs
[:multi {:dispatch :action-type}
[:navigate schema:navigate-interaction]
[:open-overlay schema:open-overlay-interaction]
[:toggle-overlay schema:toggle-overlay-interaction]
[:close-overlay schema:close-overlay-interaction]
[:prev-screen schema:prev-scren-interaction]
[:open-url schema:open-url-interaction]]])
[:schema {:title "Interaction"
:gen/gen (sg/one-of (sg/generator schema:navigate-interaction)
(sg/generator schema:open-overlay-interaction)
(sg/generator schema:close-overlay-interaction)
(sg/generator schema:toggle-overlay-interaction)
(sg/generator schema:prev-scren-interaction)
(sg/generator schema:open-url-interaction))}
[:and
schema:interaction-attrs
[:multi {:dispatch :action-type}
;; [:navigate schema:navigate-interaction]
;; [:open-overlay schema:open-overlay-interaction]
;; [:toggle-overlay schema:toggle-overlay-interaction]
;; [:close-overlay schema:close-overlay-interaction]
;; [:prev-screen schema:prev-scren-interaction]
[:open-url schema:open-url-interaction]
]]])
(sm/register! ::interaction schema:interaction)

View File

@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.schema :as sm]
[app.common.schema.desc-js-like :as smdj]
[clojure.data :as data]
[clojure.set :as set]
[malli.util :as mu]))
@@ -51,7 +52,8 @@
(into #{} (keys token-type->dtcg-token-type)))
(def token-name-ref
[:and :string [:re #"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(?<!\.)$"]])
[:schema {:title "TokenNameRef"}
[:and :string [:re #"^(?!\$)([a-zA-Z0-9-$_]+\.?)*(?<!\.)$"]]])
(def ^:private schema:color
[:map
@@ -61,7 +63,7 @@
(def color-keys (schema-keys schema:color))
(def ^:private schema:border-radius
[:map
[:map {:title "BorderRadiusTokenAttrs"}
[:r1 {:optional true} token-name-ref]
[:r2 {:optional true} token-name-ref]
[:r3 {:optional true} token-name-ref]
@@ -76,7 +78,7 @@
(def stroke-width-keys (schema-keys schema:stroke-width))
(def ^:private schema:sizing
[:map
[:map {:title "SizingTokenAttrs"}
[:width {:optional true} token-name-ref]
[:height {:optional true} token-name-ref]
[:layout-item-min-w {:optional true} token-name-ref]
@@ -87,44 +89,46 @@
(def sizing-keys (schema-keys schema:sizing))
(def ^:private schema:opacity
[:map
[:map {:title "OpacityTokenAttrs"}
[:opacity {:optional true} token-name-ref]])
(def opacity-keys (schema-keys schema:opacity))
(def ^:private schema:spacing-gap
[:map
[:map {:title "SpacingGapTokenAttrs"}
[:row-gap {:optional true} token-name-ref]
[:column-gap {:optional true} token-name-ref]])
(def ^:private schema:spacing-padding
[:map
[:map {:title "SpacingPaddingTokenAttrs"}
[:p1 {:optional true} token-name-ref]
[:p2 {:optional true} token-name-ref]
[:p3 {:optional true} token-name-ref]
[:p4 {:optional true} token-name-ref]])
(def ^:private schema:spacing-margin
[:map
[:map {:title "SpacingMarginTokenAttrs"}
[:m1 {:optional true} token-name-ref]
[:m2 {:optional true} token-name-ref]
[:m3 {:optional true} token-name-ref]
[:m4 {:optional true} token-name-ref]])
(def ^:private schema:spacing
(reduce mu/union [schema:spacing-gap
schema:spacing-padding
schema:spacing-margin]))
(-> (reduce mu/union [schema:spacing-gap
schema:spacing-padding
schema:spacing-margin])
(mu/update-properties assoc :title "SpacingTokenAttrs")))
(def spacing-margin-keys (schema-keys schema:spacing-margin))
(def spacing-keys (schema-keys schema:spacing))
(def ^:private schema:dimensions
(reduce mu/union [schema:sizing
schema:spacing
schema:stroke-width
schema:border-radius]))
(-> (reduce mu/union [schema:sizing
schema:spacing
schema:stroke-width
schema:border-radius])
(mu/update-properties assoc :title "DimensionsTokenAttrs")))
(def dimensions-keys (schema-keys schema:dimensions))
@@ -135,22 +139,20 @@
(def axis-keys (schema-keys schema:axis))
(def ^:private schema:rotation
[:map
[:map {:title "RotationTokenAttrs"}
[:rotation {:optional true} token-name-ref]])
(def rotation-keys (schema-keys schema:rotation))
(def ^:private schema:font-size
[:map
[:map {:title "FontSizeTokenAttrs"}
[:font-size {:optional true} token-name-ref]])
(def font-size-keys (schema-keys schema:font-size))
(def ^:private schema:letter-spacing
[:map
[:map {:title "LetterSpacingTokenAttrs"}
[:letter-spacing {:optional true} token-name-ref]])
(def letter-spacing-keys (schema-keys schema:letter-spacing))
@@ -162,8 +164,9 @@
(def ff-typography-keys (set/difference typography-keys font-size-keys))
(def ^:private schema:number
(reduce mu/union [[:map [:line-height {:optional true} token-name-ref]]
schema:rotation]))
(-> (reduce mu/union [[:map [:line-height {:optional true} token-name-ref]]
schema:rotation])
(mu/update-properties assoc :title "NumberTokenAttrs")))
(def number-keys (schema-keys schema:number))
@@ -180,10 +183,10 @@
number-keys))
(def ^:private schema:tokens
[:map {:title "Applied Tokens"}])
[:map {:title "GenericTokenAttrs"}])
(def schema:applied-tokens
[:merge
[:merge {:title "AppliedTokens"}
schema:tokens
schema:border-radius
schema:sizing

View File

@@ -186,6 +186,10 @@
:modified-at modified-at
:tokens tokens})])
#?@(:clj
[json/JSONWriter
(-write [this writter options] (json/-write (deref this) writter options))])
#?@(:cljs [cljs.core/IEncodeJS
(-clj->js [_] (js-obj "id" (clj->js id)
"name" (clj->js name)
@@ -292,7 +296,9 @@
(declare make-token-set)
(def schema:token-set
(sm/required-keys schema:token-set-attrs))
[:schema {:gen/gen (->> (sg/generator schema:token-set-attrs)
(sg/fmap #(make-token-set %)))}
(sm/required-keys schema:token-set-attrs)])
(sm/register! ::token-set schema:token-set) ;; need to register for the recursive schema of token-sets
@@ -466,18 +472,17 @@
(get-ordered-set-names [_] "get an ordered sequence of all sets names in the library")
(get-set [_ set-name] "get one set looking for name"))
(def schema:token-set-node
[:schema {:registry {::node [:or [:fn token-set?]
[:and
[:map-of {:gen/max 5} :string [:ref ::node]]
[:fn d/ordered-map?]]]}}
(def ^:private schema:token-set-node
[:schema {:registry {::node
[:or [:fn token-set?]
[:and
[:map-of {:gen/max 5} :string [:ref ::node]]
[:fn d/ordered-map?]]]}}
[:ref ::node]])
(def schema:token-sets
[:and
[:map-of {:title "TokenSets"}
:string
schema:token-set-node]
(def ^:private schema:token-sets
[:and {:title "TokenSets"}
[:map-of :string schema:token-set-node]
[:fn d/ordered-map?]])
(def ^:private check-token-sets

View File

@@ -125,7 +125,11 @@
(mf/defc menu-team-icon*
[{:keys [subscription-type]}]
[:span {:class (stl/css :subscription-icon) :data-testid "subscription-icon"}
[:span {:class (stl/css :subscription-icon)
:title (if (= subscription-type "unlimited")
(tr "subscription.dashboard.power-up.unlimited-plan")
(tr "subscription.dashboard.power-up.enterprise-plan"))
:data-testid "subscription-icon"}
(case subscription-type
"unlimited" i/character-u
"enterprise" i/character-e)])

View File

@@ -74,42 +74,12 @@
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 3}]
:total 2}]
[:button {:on-click next
:class (stl/css :next-btn)} "Continue"]]]]]]
1
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
[:img {:src "images/features/2.9-overrides.gif"
:class (stl/css :start-image)
:border "0"
:alt "Component text overrides"}]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-header)}
[:h1 {:class (stl/css :modal-title)}
"Component text overrides"]]
[:div {:class (stl/css :feature)}
[:p {:class (stl/css :feature-content)}
"You can now edit the text content independently from its properties—such as color, effects, font size, or weight—allowing you to update the wording while preserving the styling."]
[:p {:class (stl/css :feature-content)}
"This change (inspired by community feedback) greatly improves consistency, predictability, and control when working with texts in components."]]
[:div {:class (stl/css :navigation)}
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 3}]
[:button {:on-click next
:class (stl/css :next-btn)} "Continue"]]]]]]
2
[:div {:class (stl/css-case :modal-overlay true)}
[:div.animated {:class klass}
[:div {:class (stl/css :modal-container)}
@@ -135,7 +105,7 @@
[:& c/navigation-bullets
{:slide slide
:navigate navigate
:total 3}]
:total 2}]
[:button {:on-click finish
:class (stl/css :next-btn)} "Let's go"]]]]]])))

View File

@@ -150,7 +150,16 @@
:class (stl/css :input-field)}]]
[:div {:class (stl/css :editors-cost)}
[:span {:class (stl/css :modal-text-small)}
(tr "subscription.settings.management.dialog.price-month" (or (get-in @form [:clean-data :min-members]) 0))]
(when (> (get-in @form [:clean-data :min-members]) 25)
[:> i18n/tr-html*
{:class (stl/css :modal-text-cap)
:tag-name "span"
:content (tr "subscription.settings.management.dialog.price-month" "175")}])
[:> i18n/tr-html*
{:class (stl/css-case :text-strikethrough (> (get-in @form [:clean-data :min-members]) 25))
:tag-name "span"
:content (tr "subscription.settings.management.dialog.price-month"
(* 7 (or (get-in @form [:clean-data :min-members]) 0)))}]]
[:span {:class (stl/css :modal-text-small)}
(tr "subscription.settings.management.dialog.payment-explanation")]]]

View File

@@ -199,6 +199,20 @@
@include t.use-typography("body-small");
}
.modal-text-cap {
margin-inline-end: var(--sp-s);
}
.text-strikethrough {
text-decoration: line-through;
}
.modal-text-small strong,
.text-strikethrough strong,
.modal-text-cap strong {
font-weight: $fw700;
}
.modal-content,
.modal-end {
color: var(--color-foreground-secondary);

View File

@@ -59,7 +59,7 @@
:type "text"
:on-blur on-submit
:on-key-down on-key-down
:maxlength "256"
:max-length "256"
:auto-focus true
:placeholder (tr "workspace.tokens.set-edit-placeholder")
:default-value default-value}]))
@@ -210,7 +210,7 @@
(mf/defc sets-tree-set*
[{:keys [id set label tree-depth tree-path tree-index is-selected is-active is-draggable is-editing
on-select on-drop on-toggle on-start-edition on-reset-edition on-edit-submit]}]
on-select on-drop on-toggle on-start-edition on-reset-edition on-edit-submit is-new]}]
(let [set-name (ctob/get-name set)
can-edit? (mf/use-ctx ctx/can-edit?)
@@ -239,7 +239,11 @@
:path tree-path})))))
on-double-click
(mf/use-fn (mf/deps id) #(on-start-edition id))
(mf/use-fn
(mf/deps id is-new)
(fn []
(when-not is-new
(on-start-edition id))))
on-checkbox-click
(mf/use-fn
@@ -392,7 +396,7 @@
:is-editing true
:is-active true
:is-selected true
:is-new true
:tree-path path
:tree-depth depth
:tree-index index
@@ -416,6 +420,7 @@
:tree-path path
:tree-depth depth
:tree-index index
:is-new false
:tree-parent-path parent-path
:on-toggle on-toggle-set
:edition-id edition-id

View File

@@ -116,13 +116,17 @@
(let [root? (contains? (:root-shapes options) (:id shape))]
(if (and root? (ctl/any-layout? shape))
:fill
(get-shape-size shape objects :width))))
;; Don't set fixed width for auto-width text shapes
(when-not (and (cfh/text-shape? shape) (= (:grow-type shape) :auto-width))
(get-shape-size shape objects :width)))))
(defmethod get-value :height
[_ shape objects options]
(let [root? (contains? (:root-shapes options) (:id shape))]
(when-not (and root? (ctl/any-layout? shape))
(get-shape-size shape objects :height))))
;; Don't set fixed height for auto-height text shapes
(when-not (and (cfh/text-shape? shape) (= (:grow-type shape) :auto-height))
(get-shape-size shape objects :height)))))
(defmethod get-value :flex-grow
[_ shape _ options]

View File

@@ -4387,11 +4387,6 @@ msgstr "Subscription"
msgid "subscription.settings.add-payment-to-continue"
msgstr "Add a payment method to continue after your trial"
#: src/app/main/ui/settings/subscription.cljs:82, src/app/main/ui/settings/subscription.cljs:115, src/app/main/ui/settings/subscription.cljs:127
#, fuzzy, unused
msgid "subscription.settings.benefits.all-professiona-benefits"
msgstr ""
#: src/app/main/ui/settings/subscription.cljs:247, src/app/main/ui/settings/subscription.cljs:268, src/app/main/ui/settings/subscription.cljs:303, src/app/main/ui/settings/subscription.cljs:317
msgid "subscription.settings.benefits.all-professional-benefits"
msgstr "All Professional plan benefits and:"
@@ -4446,8 +4441,10 @@ msgid "subscription.settings.management.dialog.payment-explanation"
msgstr "(No payment will be made now)"
#: src/app/main/ui/settings/subscription.cljs:124
#, markdown
msgid "subscription.settings.management.dialog.price-month"
msgstr "$7 per editor/month x %s"
msgstr ""
"**$%s** per month"
#: src/app/main/ui/settings/subscription.cljs:112
msgid "subscription.settings.management.dialog.select-editors"

View File

@@ -4474,8 +4474,10 @@ msgid "subscription.settings.management.dialog.payment-explanation"
msgstr "(Ahora no se efectuará ningún pago)"
#: src/app/main/ui/settings/subscription.cljs:124
#, markdown
msgid "subscription.settings.management.dialog.price-month"
msgstr "$7 por editor/mes x %s"
msgstr ""
"**$%s** por mes"
#: src/app/main/ui/settings/subscription.cljs:112
msgid "subscription.settings.management.dialog.select-editors"