Compare commits

..

1 Commits

Author SHA1 Message Date
Ramiro Sanchez Balo
f70561ae13 📚 Improve metadata description 2025-05-09 15:30:12 +02:00
147 changed files with 4309 additions and 2112 deletions

View File

@@ -6,36 +6,6 @@
### :boom: Breaking changes & Deprecations
**Breaking changes on penpot library:**
- Change the signature of the `addPage` method: it now accepts an object (as a single argument) where you can pass `id`,
`name`, and `background` props (instead of the previous positional arguments)
- Rename the `file.createRect` method to `file.addRect`
- Rename the `file.createCircle` method to `file.addCircle`
- Rename the `file.createPath` method to `file.addPath`
- Rename the `file.createText` method to `file.addText`
- Rename `file.startComponent` to `file.addComponent` (to preserve the naming style)
- Rename `file.createComponentInstance` to `file.addComponentInstance` (to preserve the naming style)
- Rename `file.lookupShape` to `file.getShape`
- Rename `file.asMap` to `file.toMap`
- Remove `file.updateLibraryColor` (use `file.addLibraryColor` if you just need to replace a color)
- Remove `file.deleteLibraryColor` (this library is intended to build files)
- Remove `file.updateLibraryTypography` (use `file.addLibraryTypography` if you just need to replace a typography)
- Remove `file.deleteLibraryTypography` (this library is intended to build files)
- Remove `file.add/update/deleteLibraryMedia` (they are no longer supported by Penpot and have been replaced by components)
- Remove `file.deleteObject` (this library is intended to build files)
- Remove `file.updateObject` (this library is intended to build files)
- Remove `file.finishComponent` (it is no longer necessary; see below for more details on component creation changes)
- Change the `file.getCurrentPageId` function to a read-only `file.currentPageId` property
- Add `file.currentFrameId` read-only property
- Add `file.lastId` read-only property
There are also relevant semantic changes in how components should be created: this refactor removes
all notions of the old components (v1). Since v2, the shapes that are part of a component live on a
page. So, from now on, to create a component, you should first create a frame, then add shapes
and/or groups to that frame, and then create a component by declaring that frame as the component
root.
### :heart: Community contributions (Thank you!)
### :sparkles: New features
@@ -53,8 +23,6 @@ root.
### :heart: Community contributions (Thank you!)
- Design improvements to the Invitations page with an empty state [GitHub #2608](https://github.com/penpot/penpot/issues/2608) by [@iprithvitharun](https://github.com/iprithvitharun)
### :sparkles: New features
- Update board presets with a newer devices [Taiga #10610](https://tree.taiga.io/project/penpot/us/10610)
@@ -64,10 +32,9 @@ root.
- Add set selection in create Token themes flow [Taiga #10746](https://tree.taiga.io/project/penpot/issue/10746)
- Display indicator on not active sets [Taiga #10668](https://tree.taiga.io/project/penpot/issue/10668)
- Create `input*` wrapper component, and `label*`, `input-field*` and `hint-message*` components [Taiga #10713](https://tree.taiga.io/project/penpot/us/10713)
- Fix problem in viewer with the back button [Taiga #10907](https://tree.taiga.io/project/penpot/issue/10907)
### :bug: Bugs fixed
- Fix problem in viewer with the back button [Taiga #10907](https://tree.taiga.io/project/penpot/issue/10907)
- Fix resize bar background on tokens panel [Taiga #10811](https://tree.taiga.io/project/penpot/issue/10811)
- Fix shortcut for history version panel [Taiga #11006](https://tree.taiga.io/project/penpot/issue/11006)
- Fix positioning of comment drafts when near the right / bottom edges of viewport [Taiga #10534](https://tree.taiga.io/project/penpot/issue/10534)
@@ -92,9 +59,8 @@ root.
- Fix Color should preserve color space [Github #69](https://github.com/tokens-studio/penpot/issues/69)
- Fix cannot rename Design Token Sets when group of same name exists [Taiga Issue #10773](https://tree.taiga.io/project/penpot/issue/10773)
- Fix problem when duplicating grid layout [Github #6391](https://github.com/penpot/penpot/issues/6391)
- Fix issue that makes workspace shortcuts stop working [Taiga #11062](https://tree.taiga.io/project/penpot/issue/11062)
## 2.6.2
## 2.6.2 (Unreleased)
### :bug: Bugs fixed

View File

@@ -9,7 +9,6 @@
for recently imported shapes."
(:require
[app.common.data :as d]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -56,52 +55,9 @@
(fn [shadows]
(into [] xform shadows)))))
(defn- fix-root-shape
"Ensure all root objects are well formed shapes"
[shape]
(if (= (:id shape) uuid/zero)
(-> shape
(assoc :parent-id uuid/zero)
(assoc :frame-id uuid/zero)
;; We explicitly dissoc them and let the shape-setup
;; to regenerate it with valid values.
(dissoc :selrect)
(dissoc :points)
(cts/setup-shape))
shape))
(defn- fix-legacy-flex-dir
"This operation is only relevant to old data and it is fixed just
for convenience."
[shape]
(d/update-when shape :layout-flex-dir
(fn [dir]
(case dir
:reverse-row :row-reverse
:reverse-column :column-reverse
dir))))
(defn clean-shape-post-decode
"A shape procesor that expected to be executed after schema decoding
process but before validation."
[shape]
(-> shape
(fix-shape-shadow-color)
(fix-root-shape)
(fix-legacy-flex-dir)))
(defn- fix-container
[container]
(-> container
;; Remove possible `nil` keys on objects
(d/update-when :objects dissoc nil)
(d/update-when :objects d/update-vals clean-shape-post-decode)))
(defn clean-file
[file & {:as _opts}]
(update file :data
(fn [data]
(-> data
(d/update-when :pages-index d/update-vals fix-container)
(d/update-when :components d/update-vals fix-container)
(d/without-nils)))))
(fix-shape-shadow-color)))

View File

@@ -431,13 +431,7 @@
(update :components relink-shapes)
(update :media relink-media)
(update :colors relink-colors)
(d/without-nils))))
;; NOTE: this is necessary because when we just creating a new
;; file from imported artifact or cloned file there are no
;; migrations registered on the database, so we need to persist
;; all of them, not only the applied
(vary-meta dissoc ::fmg/migrated)))
(d/without-nils))))))
(defn encode-file
[{:keys [::db/conn] :as cfg} {:keys [id features] :as file}]

View File

@@ -756,7 +756,14 @@
(assoc :name file-name)
(assoc :project-id project-id)
(dissoc :options)
(bfc/process-file))]
(bfc/process-file)
;; NOTE: this is necessary because when we just
;; creating a new file from imported artifact,
;; there are no migrations registered on the
;; database, so we need to persist all of them, not
;; only the applied
(vary-meta dissoc ::fmg/migrated))]
(bfm/register-pending-migrations! cfg file)
(bfc/save-file! cfg file ::db/return-keys false)

View File

@@ -208,7 +208,7 @@
[:project-id {:optional true} ::sm/uuid]])
(defn- migrate-file
[{:keys [::db/conn] :as cfg} {:keys [id] :as file} {:keys [read-only?]}]
[{:keys [::db/conn] :as cfg} {:keys [id] :as file}]
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)
pmap/*tracked* (pmap/create-tracked)]
(let [;; For avoid unnecesary overhead of creating multiple pointers and
@@ -219,45 +219,43 @@
file (-> file
(update :data feat.fdata/process-pointers deref)
(update :data feat.fdata/process-objects (partial into {}))
(fmg/migrate-file))]
(fmg/migrate-file))
(if (or read-only? (db/read-only? conn))
file
(let [;; When file is migrated, we break the rule of no perform
;; mutations on get operations and update the file with all
;; migrations applied
file (if (contains? (:features file) "fdata/objects-map")
(feat.fdata/enable-objects-map file)
file)
file (if (contains? (:features file) "fdata/pointer-map")
(feat.fdata/enable-pointer-map file)
file)]
;; When file is migrated, we break the rule of no perform
;; mutations on get operations and update the file with all
;; migrations applied
;;
;; WARN: he following code will not work on read-only mode,
;; it is a known issue; we keep is not implemented until we
;; really need this.
file (if (contains? (:features file) "fdata/objects-map")
(feat.fdata/enable-objects-map file)
file)
file (if (contains? (:features file) "fdata/pointer-map")
(feat.fdata/enable-pointer-map file)
file)]
(db/update! conn :file
{:data (blob/encode (:data file))
:version (:version file)
:features (db/create-array conn "text" (:features file))}
{:id id}
{::db/return-keys false})
(db/update! conn :file
{:data (blob/encode (:data file))
:version (:version file)
:features (db/create-array conn "text" (:features file))}
{:id id})
(when (contains? (:features file) "fdata/pointer-map")
(feat.fdata/persist-pointers! cfg id))
(when (contains? (:features file) "fdata/pointer-map")
(feat.fdata/persist-pointers! cfg id))
(feat.fmigr/upsert-migrations! conn file)
(feat.fmigr/resolve-applied-migrations cfg file))))))
(feat.fmigr/upsert-migrations! conn file)
(feat.fmigr/resolve-applied-migrations cfg file))))
(defn get-file
[{:keys [::db/conn ::wrk/executor] :as cfg} id
& {:keys [project-id
migrate?
include-deleted?
lock-for-update?
preload-pointers?]
lock-for-update?]
:or {include-deleted? false
lock-for-update? false
migrate? true
preload-pointers? false}
:as options}]
migrate? true}}]
(assert (db/connection? conn) "expected cfg with valid connection")
@@ -275,16 +273,10 @@
;; because it has heavy and synchronous operations for
;; decoding file body that are not very friendly with virtual
;; threads.
file (px/invoke! executor #(decode-row file))
file (if (and migrate? (fmg/need-migration? file))
(migrate-file cfg file options)
file)]
(if preload-pointers?
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
(update file :data feat.fdata/process-pointers deref))
file (px/invoke! executor #(decode-row file))]
(if (and migrate? (fmg/need-migration? file))
(migrate-file cfg file)
file)))
(defn get-minimal-file
@@ -492,7 +484,7 @@
(let [perms (get-permissions conn profile-id file-id share-id)
file (get-file cfg file-id :read-only? true)
file (get-file cfg file-id)
proj (db/get conn :project {:id (:project-id file)})
@@ -749,9 +741,7 @@
:project-id project-id
:file-id id)
file (get-file cfg id
:project-id project-id
:read-only? true)]
file (get-file cfg id :project-id project-id)]
(-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))

View File

@@ -55,8 +55,8 @@
:features features
:ignore-sync-until ignore-sync-until
:modified-at modified-at
:deleted-at deleted-at}
{:create-page create-page
:deleted-at deleted-at
:create-page create-page
:page-id page-id})
file (-> (bfc/insert-file! cfg file)
(bfc/decode-row))]

View File

@@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.features :as cfeat]
[app.common.files.helpers :as cfh]
[app.common.files.migrations :as fmg]
[app.common.geom.shapes :as gsh]
[app.common.schema :as sm]
[app.common.thumbnails :as thc]
@@ -17,6 +18,7 @@
[app.config :as cf]
[app.db :as db]
[app.db.sql :as-alias sql]
[app.features.fdata :as feat.fdata]
[app.loggers.audit :as-alias audit]
[app.loggers.webhooks :as-alias webhooks]
[app.media :as media]
@@ -198,13 +200,14 @@
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
(files/check-read-permissions! conn profile-id file-id)
(let [team (teams/get-team conn
:profile-id profile-id
:file-id file-id)
(let [team (teams/get-team conn
:profile-id profile-id
:file-id file-id)
file (files/get-file cfg file-id
:preload-pointers? true
:read-only? true)]
file (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
(-> (files/get-file cfg file-id :migrate? false)
(update :data feat.fdata/process-pointers deref)
(fmg/migrate-file)))]
(-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-file-features! (:features file)))

View File

@@ -10,7 +10,6 @@
file is eligible to be garbage collected after some period of
inactivity (the default threshold is 72h)."
(:require
[app.binfile.cleaner :as bfl]
[app.binfile.common :as bfc]
[app.common.files.helpers :as cfh]
[app.common.files.validate :as cfv]
@@ -259,7 +258,6 @@
(if-let [file (get-file cfg file-id)]
(let [file (->> file
(bfc/decode-file cfg)
(bfl/clean-file)
(clean-media! cfg)
(clean-fragments! cfg))
file (assoc file :has-media-trimmed true)]

View File

@@ -2,7 +2,7 @@
{org.clojure/clojure {:mvn/version "1.12.0"}
org.clojure/data.json {:mvn/version "2.5.1"}
org.clojure/tools.cli {:mvn/version "1.1.230"}
org.clojure/clojurescript {:mvn/version "1.12.38"}
org.clojure/clojurescript {:mvn/version "1.11.132"}
org.clojure/test.check {:mvn/version "1.1.1"}
org.clojure/data.fressian {:mvn/version "1.1.0"}
@@ -59,7 +59,7 @@
{:dev
{:extra-deps
{org.clojure/tools.namespace {:mvn/version "RELEASE"}
thheller/shadow-cljs {:mvn/version "3.0.3"}
thheller/shadow-cljs {:mvn/version "2.28.20"}
com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"}

View File

@@ -17,7 +17,7 @@
"devDependencies": {
"concurrently": "^9.0.1",
"nodemon": "^3.1.7",
"shadow-cljs": "3.0.3",
"shadow-cljs": "2.28.20",
"source-map-support": "^0.5.21",
"ws": "^8.17.0"
},

View File

@@ -2,20 +2,16 @@
export PENPOT_FLAGS="enable-asserts enable-audit-log $PENPOT_FLAGS"
export JAVA_OPTS="\
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-Djdk.attach.allowAttachSelf \
-Dlog4j2.configurationFile=log4j2-devenv-repl.xml \
-Djdk.tracePinnedThreads=full \
-XX:+EnableDynamicAgentLoading \
-XX:-OmitStackTraceInFastThrow \
-XX:+UnlockDiagnosticVMOptions \
-XX:+DebugNonSafepoints \
--sun-misc-unsafe-memory-access=allow \
--enable-preview \
--enable-native-access=ALL-UNNAMED";
export OPTIONS="-A:dev"
export OPTIONS="
-A:dev \
-J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-J-Djdk.attach.allowAttachSelf \
-J-Dpolyglot.engine.WarnInterpreterOnly=false \
-J-XX:+EnableDynamicAgentLoading \
-J-XX:-OmitStackTraceInFastThrow \
-J-XX:+UnlockDiagnosticVMOptions \
-J-XX:+DebugNonSafepoints \
-J-Djdk.tracePinnedThreads=full"
export OPTIONS_EVAL="nil"
# export OPTIONS_EVAL="(set! *warn-on-reflection* true)"

View File

File diff suppressed because it is too large Load Diff

View File

@@ -732,22 +732,20 @@
(update-group [group objects]
(let [lookup (d/getf objects)
children (get group :shapes)]
children (->> group :shapes (map lookup))]
(cond
;; If the group is empty we don't make any changes. Will be removed by a later process
(empty? children)
group
(= :bool (:type group))
(gsh/update-bool group objects)
(gsh/update-bool group children objects)
(:masked-group group)
(->> (map lookup children)
(set-mask-selrect group))
(set-mask-selrect group children)
:else
(->> (map lookup children)
(gsh/update-group-selrect group)))))]
(gsh/update-group-selrect group children))))]
(if page-id
(d/update-in-when data [:pages-index page-id :objects] reg-objects)

View File

@@ -660,13 +660,9 @@
nil ;; so it does not need resize
(= (:type parent) :bool)
(gsh/update-bool parent objects)
(gsh/update-bool parent children objects)
(= (:type parent) :group)
;; FIXME: this functions should be
;; normalized in the same way as
;; update-bool in order to make all
;; this code consistent
(if (:masked-group parent)
(gsh/update-mask-selrect parent children)
(gsh/update-group-selrect parent children)))]

View File

@@ -126,20 +126,21 @@
o)))
(def schema:matrix
(sm/register!
{:type ::matrix
:pred valid-matrix?
:type-properties
{:title "matrix"
:description "Matrix instance"
:error/message "expected a valid matrix instance"
:gen/gen (matrix-generator)
:decode/json decode-matrix
:decode/string decode-matrix
:encode/json matrix->json
:encode/string matrix->str
::oapi/type "string"
::oapi/format "matrix"}}))
{:type :map
:pred valid-matrix?
:type-properties
{:title "matrix"
:description "Matrix instance"
:error/message "expected a valid matrix instance"
:gen/gen (matrix-generator)
:decode/json decode-matrix
:decode/string decode-matrix
:encode/json matrix->json
:encode/string matrix->str
::oapi/type "string"
::oapi/format "matrix"}})
(sm/register! ::matrix schema:matrix)
;; FIXME: deprecated
(s/def ::a ::us/safe-float)

View File

@@ -85,22 +85,24 @@
(into {} p)
p))
;; FIXME: make like matrix
(def schema:point
(sm/register!
{:type ::point
:pred valid-point?
:type-properties
{:title "point"
:description "Point"
:error/message "expected a valid point"
:gen/gen (->> (sg/tuple (sg/small-int) (sg/small-int))
(sg/fmap #(apply pos->Point %)))
::oapi/type "string"
::oapi/format "point"
:decode/json decode-point
:decode/string decode-point
:encode/json point->json
:encode/string point->str}}))
{:type ::point
:pred valid-point?
:type-properties
{:title "point"
:description "Point"
:error/message "expected a valid point"
:gen/gen (->> (sg/tuple (sg/small-int) (sg/small-int))
(sg/fmap #(apply pos->Point %)))
::oapi/type "string"
::oapi/format "point"
:decode/json decode-point
:decode/string decode-point
:encode/json point->json
:encode/string point->str}})
(sm/register! schema:point)
(defn point-like?
[{:keys [x y] :as v}]

View File

@@ -455,12 +455,12 @@
(defn update-bool
"Calculates the selrect+points for the boolean shape"
[shape objects]
[shape _children objects]
(let [content (path/calc-bool-content shape objects)
shape (assoc shape :content content)]
(path/update-geometry shape)))
;; FIXME: revisit
(defn update-shapes-geometry
[objects ids]
(->> ids
@@ -474,7 +474,7 @@
(update-mask-selrect shape children)
(cfh/bool-shape? shape)
(update-bool shape objects)
(update-bool shape children objects)
(cfh/group-shape? shape)
(update-group-selrect shape children)

View File

@@ -1757,18 +1757,18 @@
(let [attr-group (get ctk/sync-attrs attr)
[roperations' uoperations']
(if (or
;; If the attribute is not valid for the destiny, don't copy it
;; If the attribute is not valid for the destiny, don't copy it
(not (cts/is-allowed-attr? attr (:type dest-shape)))
;; If the values are already equal, don't copy it
;; If the values are already equal, don't copy it
(= (get origin-shape attr) (get dest-shape attr))
;; If the referenced shape on the original component doesn't have the same value, don't copy it
;; Exceptions: :points :selrect and :content can be different
;; If the referenced shape on the original component doesn't have the same value, don't copy it
;; Exceptions: :points :selrect and :content can be different
(and
(not (contains? #{:points :selrect :content} attr))
(not= (get origin-ref-shape attr) (get dest-shape attr)))
;; The :content attr cant't be copied to elements of different type
;; The :content attr cant't be copied to elements of different type
(and (= attr :content) (not= (:type origin-shape) (:type dest-shape)))
;; If the attr is not touched in the origin shape, don't copy it
;; If the attr is not touched in the origin shape, don't copy it
(not (touched-origin attr-group)))
[roperations uoperations]
(add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched))]

View File

@@ -5,6 +5,7 @@
[app.common.files.variant :as cfv]
[app.common.logic.libraries :as cll]
[app.common.logic.variant-properties :as clvp]
[app.common.types.components-list :as ctcl]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.variant :as ctv]))
@@ -47,8 +48,12 @@
(pcb/change-parent (:parent-id shape) [new-shape] 0))))
(defn generate-keep-touched
[changes new-shape original-shape original-shapes page libraries]
(let [objects (pcb/get-objects changes)
[changes new-shape original-shape original-shapes page]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
orig-comp (ctcl/get-component data (:component-id original-shape) true)
new-path-map (into {}
(map (fn [shape] {(generate-path "" objects (:id new-shape) shape) shape}))
(cfh/get-children-with-self objects (:id new-shape)))
@@ -60,7 +65,7 @@
(fn [changes touched-shape]
(let [path (generate-path "" orig-objects (:id original-shape) touched-shape)
related-shape (get new-path-map path)
orig-ref-shape (ctf/find-ref-shape nil container libraries touched-shape)]
orig-ref-shape (ctf/get-ref-shape data orig-comp touched-shape)]
(if related-shape
(cll/update-attrs-on-switch
changes related-shape touched-shape new-shape original-shape orig-ref-shape container)

View File

@@ -28,6 +28,10 @@
[malli.transform :as mt]
[malli.util :as mu]))
(defprotocol ILazySchema
(-validate [_ o])
(-explain [_ o]))
(def default-options
{:registry sr/default-registry})
@@ -47,6 +51,10 @@
[s]
(m/type-properties s))
(defn- lazy-schema?
[s]
(satisfies? ILazySchema s))
(defn schema
[s]
(if (schema? s)
@@ -103,16 +111,12 @@
(malli.error/error-value exp {:malli.error/mask-valid-values '...}))
(defn optional-keys
([schema]
(mu/optional-keys schema nil default-options))
([schema keys]
(mu/optional-keys schema keys default-options)))
[schema]
(mu/optional-keys schema default-options))
(defn required-keys
([schema]
(mu/required-keys schema nil default-options))
([schema keys]
(mu/required-keys schema keys default-options)))
[schema]
(mu/required-keys schema default-options))
(defn transformer
[& transformers]
@@ -225,11 +229,6 @@
(let [vfn (delay (decoder (if (delay? s) (deref s) s) transformer))]
(fn [v] (@vfn v))))
(defn decode-fn
[s transformer]
(let [vfn (delay (decoder (if (delay? s) (deref s) s) transformer))]
(fn [v] (@vfn v))))
(defn humanize-explain
"Returns a string representation of the explain data structure"
[{:keys [errors value]} & {:keys [length level]}]
@@ -275,36 +274,38 @@
([s] (lookup sr/default-registry s))
([registry s] (schema (mr/schema registry s))))
(defn- fast-check
"A fast path for checking process, assumes the ILazySchema protocol
implemented on the provided `s` schema. Sould not be used directly."
[s type code hint value]
(when-not ^boolean (-validate s value)
(let [explain (-explain s value)]
(throw (ex-info hint {:type type
:code code
:hint hint
::explain explain}))))
value)
(declare ^:private lazy-schema)
(defn check-fn
"Create a predefined check function"
[s & {:keys [hint type code]}]
(let [s (schema s)
validator* (delay (m/validator s))
explainer* (delay (m/explainer s))
hint (or ^boolean hint "check error")
type (or ^boolean type :assertion)
code (or ^boolean code :data-validation)]
(fn [value]
(let [validate-fn @validator*]
(when-not ^boolean (validate-fn value)
(let [explain-fn @explainer*
explain (explain-fn value)]
(throw (ex-info hint {:type type
:code code
:hint hint
::explain explain}))))
value))))
(let [schema (if (lazy-schema? s) s (lazy-schema s))
hint (or ^boolean hint "check error")
type (or ^boolean type :assertion)
code (or ^boolean code :data-validation)]
(partial fast-check schema type code hint)))
(defn check
"A helper intended to be used on assertions for validate/check the
schema over provided data. Raises an assertion exception.
Use only on non-performance sensitive code, because it creates the
check-fn instance all the time it is invoked."
[s value & {:as opts}]
(let [check-fn (check-fn s opts)]
(check-fn value)))
schema over provided data. Raises an assertion exception."
[s value & {:keys [hint type code]}]
(let [s (if (lazy-schema? s) s (lazy-schema s))
hint (or ^boolean hint "check error")
type (or ^boolean type :assertion)
code (or ^boolean code :data-validation)]
(fast-check s type code hint value)))
(defn type-schema
[& {:as params}]
@@ -318,14 +319,11 @@
([params]
(cond
(map? params)
(let [mdata (meta params)
type (or (get mdata ::id)
(get mdata ::type)
(get params :type))]
(let [type (get params :type)]
(assert (qualified-keyword? type) "expected qualified keyword for `type`")
(let [s (m/-simple-schema params)]
(swap! sr/registry assoc type s)
s))
nil))
(vector? params)
(let [mdata (meta params)
@@ -333,19 +331,83 @@
(get mdata ::type))]
(assert (qualified-keyword? type) "expected qualified keyword to be on metadata")
(swap! sr/registry assoc type params)
params)
nil)
(m/into-schema? params)
(let [type (m/-type params)]
(swap! sr/registry assoc type params)
params)
(swap! sr/registry assoc type params))
:else
(throw (ex-info "Invalid Arguments" {}))))
([type params]
(swap! sr/registry assoc type params)
params))
(let [s (if (map? params)
(cond
(= :set (:type params))
(m/-collection-schema params)
(= :vector (:type params))
(m/-collection-schema params)
:else
(m/-simple-schema params))
params)]
(swap! sr/registry assoc type s)
nil)))
(defn- lazy-schema
"Create ans instance of ILazySchema"
[s]
(let [schema (schema s)
validator (delay (m/validator schema))
explainer (delay (m/explainer schema))]
(reify
m/AST
(-to-ast [_ options] (m/-to-ast schema options))
m/EntrySchema
(-entries [_] (m/-entries schema))
(-entry-parser [_] (m/-entry-parser schema))
m/Cached
(-cache [_] (m/-cache schema))
m/LensSchema
(-keep [_] (m/-keep schema))
(-get [_ key default] (m/-get schema key default))
(-set [_ key value] (m/-set schema key value))
m/Schema
(-validator [_]
(m/-validator schema))
(-explainer [_ path]
(m/-explainer schema path))
(-parser [_]
(m/-parser schema))
(-unparser [_]
(m/-unparser schema))
(-transformer [_ transformer method options]
(m/-transformer schema transformer method options))
(-walk [_ walker path options]
(m/-walk schema walker path options))
(-properties [_]
(m/-properties schema))
(-options [_]
(m/-options schema))
(-children [_]
(m/-children schema))
(-parent [_]
(m/-parent schema))
(-form [_]
(m/-form schema))
ILazySchema
(-validate [_ o]
(@validator o))
(-explain [_ o]
(@explainer o)))))
;; --- BUILTIN SCHEMAS

View File

@@ -23,32 +23,28 @@
(defn sample-file
[label & {:keys [page-label name view-only?] :as params}]
(let [params
(cond-> params
label
(assoc :id (thi/new-id! label))
(binding [ffeat/*current* #{"components/v2"}]
(let [params (cond-> params
label
(assoc :id (thi/new-id! label))
(nil? name)
(assoc :name "Test file")
page-label
(assoc :page-id (thi/new-id! page-label))
:always
(assoc :features ffeat/default-features))
(nil? name)
(assoc :name "Test file"))
opts
(cond-> {}
page-label
(assoc :page-id (thi/new-id! page-label)))
file (-> (ctf/make-file (dissoc params :page-label))
(assoc :features #{"components/v2"})
(assoc :permissions {:can-edit (not (true? view-only?))}))
file (-> (ctf/make-file params opts)
(assoc :permissions {:can-edit (not (true? view-only?))}))
page (-> file
:data
(ctpl/pages-seq)
(first))]
page (-> file
:data
(ctpl/pages-seq)
(first))]
(with-meta file
{:current-page-id (:id page)})))
(with-meta file
{:current-page-id (:id page)}))))
(defn validate-file!
([file] (validate-file! file {}))

View File

@@ -41,18 +41,17 @@
[o]
(and (string? o) (some? (re-matches rgb-color-re o))))
(def schema:rgb-color
(sm/register!
{:type ::rgb-color
:pred rgb-color-string?
:type-properties
{:title "rgb-color"
:description "RGB Color String"
:error/message "expected a valid RGB color"
:error/code "errors.invalid-rgb-color"
:gen/gen (generate-rgb-color)
::oapi/type "integer"
::oapi/format "int64"}}))
(def ^:private type:rgb-color
{:type :string
:pred rgb-color-string?
:type-properties
{:title "rgb-color"
:description "RGB Color String"
:error/message "expected a valid RGB color"
:error/code "errors.invalid-rgb-color"
:gen/gen (generate-rgb-color)
::oapi/type "integer"
::oapi/format "int64"}})
(def schema:image-color
[:map {:title "ImageColor"}
@@ -77,7 +76,7 @@
[:stops
[:vector {:min 1 :gen/max 2}
[:map {:title "GradientStop"}
[:color schema:rgb-color]
[:color ::rgb-color]
[:opacity {:optional true} [:maybe ::sm/safe-number]]
[:offset ::sm/safe-number]]]]])
@@ -87,7 +86,7 @@
[:name {:optional true} :string]
[:path {:optional true} [:maybe :string]]
[:value {:optional true} [:maybe :string]]
[:color {:optional true} [:maybe schema:rgb-color]]
[:color {:optional true} [:maybe ::rgb-color]]
[:opacity {:optional true} [:maybe ::sm/safe-number]]
[:modified-at {:optional true} ::sm/inst]
[:ref-id {:optional true} ::sm/uuid]
@@ -104,17 +103,12 @@
[:and
[:map {:title "RecentColor"}
[:opacity {:optional true} [:maybe ::sm/safe-number]]
[:color {:optional true} [:maybe schema:rgb-color]]
[:color {:optional true} [:maybe ::rgb-color]]
[:gradient {:optional true} [:maybe schema:gradient]]
[:image {:optional true} [:maybe schema:image-color]]]
[::sm/contains-any {:strict true} [:color :gradient :image]]])
;; Same as color but with :id prop required
(def schema:library-color
[:and
(sm/required-keys schema:color-attrs [:id])
[::sm/contains-any {:strict true} [:color :gradient :image]]])
(sm/register! ::rgb-color type:rgb-color)
(sm/register! ::color schema:color)
(sm/register! ::gradient schema:gradient)
(sm/register! ::image-color schema:image-color)
@@ -125,13 +119,10 @@
(sm/lazy-validator schema:color))
(def check-color
(sm/check-fn schema:color :hint "expected valid color"))
(def check-library-color
(sm/check-fn schema:library-color :hint "expected valid library color"))
(sm/check-fn schema:color :hint "expected valid color struct"))
(def check-recent-color
(sm/check-fn schema:recent-color :hint "expected valid recent color"))
(sm/check-fn schema:recent-color))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS

View File

@@ -18,19 +18,19 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def schema:component
(sm/register!
^{::sm/type ::component}
[:merge
[:map
[:id ::sm/uuid]
[:name :string]
[:path {:optional true} [:maybe :string]]
[:modified-at {:optional true} ::sm/inst]
[:objects {:gen/max 10 :optional true} ctp/schema:objects]
[:main-instance-id ::sm/uuid]
[:main-instance-page ::sm/uuid]
[:plugin-data {:optional true} ctpg/schema:plugin-data]]
ctv/schema:variant-component]))
[:merge
[:map
[:id ::sm/uuid]
[:name :string]
[:path {:optional true} [:maybe :string]]
[:modified-at {:optional true} ::sm/inst]
[:objects {:gen/max 10 :optional true} ::ctp/objects]
[:main-instance-id ::sm/uuid]
[:main-instance-page ::sm/uuid]
[:plugin-data {:optional true} ::ctpg/plugin-data]]
::ctv/variant-component])
(sm/register! ::component schema:component)
(def check-component
(sm/check-fn schema:component))

View File

@@ -41,7 +41,7 @@
[:map-of {:gen/max 10} ::sm/uuid :map]]
[:plugin-data {:optional true} ::ctpg/plugin-data]])
(def check-container
(def check-container!
(sm/check-fn ::container))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -62,9 +62,9 @@
(defn get-container
[file type id]
(assert (map? file))
(assert (contains? valid-container-types type))
(assert (uuid? id))
(dm/assert! (map? file))
(dm/assert! (contains? valid-container-types type))
(dm/assert! (uuid? id))
(-> (if (= type :page)
(ctpl/get-page file id)
@@ -74,9 +74,13 @@
(defn get-shape
[container shape-id]
(assert (check-container container))
(assert (uuid? shape-id)
"expected valid uuid for `shape-id`")
(dm/assert!
"expected valid container"
(check-container! container))
(dm/assert!
"expected valid uuid for `shape-id`"
(uuid? shape-id))
(-> container
(get :objects)

View File

@@ -83,7 +83,6 @@
because sometimes we want to validate file without the data."
[:map {:title "file"}
[:id ::sm/uuid]
[:name :string]
[:revn {:optional true} :int]
[:vern {:optional true} :int]
[:created-at {:optional true} ::sm/inst]
@@ -102,15 +101,13 @@
(sm/register! ::media schema:media)
(sm/register! ::colors schema:colors)
(sm/register! ::typographies schema:typographies)
(sm/register! ::media-object schema:media)
(def check-file
(sm/check-fn schema:file :hint "check error on validating file"))
(def check-file-data!
(sm/check-fn ::data))
(def check-file-data
(sm/check-fn schema:data))
(def check-media-object
(def check-media-object!
(sm/check-fn schema:media))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -137,36 +134,33 @@
(update :options assoc :components-v2 true)))))
(defn make-file
[{:keys [id project-id name revn is-shared features migrations
ignore-sync-until modified-at deleted-at]
:or {is-shared false revn 0}}
& {:keys [create-page page-id]
:or {create-page true}}]
[{:keys [id project-id name revn is-shared features
ignore-sync-until modified-at deleted-at
create-page page-id]
:or {is-shared false revn 0 create-page true}}]
(let [id (or id (uuid/next))
data (if create-page
(if page-id
(make-file-data id page-id)
(make-file-data id))
(make-file-data id nil))
file (d/without-nils
{:id id
:project-id project-id
:name name
:revn revn
:vern 0
:is-shared is-shared
:version version
:data data
:features features
:migrations migrations
:ignore-sync-until ignore-sync-until
:modified-at modified-at
:deleted-at deleted-at})]
file {:id id
:project-id project-id
:name name
:revn revn
:vern 0
:is-shared is-shared
:version version
:data data
:features features
:ignore-sync-until ignore-sync-until
:modified-at modified-at
:deleted-at deleted-at}]
(check-file file)))
(d/without-nils file)))
;; Helpers

View File

@@ -70,7 +70,7 @@
(def valid-guide?
(sm/lazy-validator schema:guide))
(def check-page
(def check-page!
(sm/check-fn schema:page))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -82,7 +82,8 @@
(def root uuid/zero)
(def empty-page-data
{:objects {root
{:options {}
:objects {root
(cts/setup-shape {:id root
:type :frame
:parent-id root
@@ -90,12 +91,10 @@
:name "Root Frame"})}})
(defn make-empty-page
[{:keys [id name background]}]
[{:keys [id name]}]
(-> empty-page-data
(assoc :id (or id (uuid/next)))
(assoc :name (d/nilv name "Page 1"))
(cond-> background
(assoc :background background))))
(assoc :name (or name "Page 1"))))
(defn get-frame-flow
[flows frame-id]

View File

@@ -22,13 +22,14 @@
:keyword])
(def schema:plugin-data
(sm/register!
^{::sm/type ::plugin-data}
[:map-of {:gen/max 5}
schema:keyword
[:map-of {:gen/max 5}
schema:keyword
[:map-of {:gen/max 5}
schema:string
schema:string]]))
schema:string
schema:string]])
(sm/register! ::plugin-data schema:plugin-data)
(def ^:private schema:registry-entry
[:map

View File

@@ -120,35 +120,35 @@
[:vector {:gen/max 4 :gen/min 4} ::gpt/point])
(def schema:fill
(sm/register!
^{::sm/type ::fill}
[:map {:title "Fill"}
[:fill-color {:optional true} ::ctc/rgb-color]
[:fill-opacity {:optional true} ::sm/safe-number]
[:fill-color-gradient {:optional true} [:maybe ::ctc/gradient]]
[:fill-color-ref-file {:optional true} [:maybe ::sm/uuid]]
[:fill-color-ref-id {:optional true} [:maybe ::sm/uuid]]
[:fill-image {:optional true} ::ctc/image-color]]))
[:map {:title "Fill"}
[:fill-color {:optional true} ::ctc/rgb-color]
[:fill-opacity {:optional true} ::sm/safe-number]
[:fill-color-gradient {:optional true} [:maybe ::ctc/gradient]]
[:fill-color-ref-file {:optional true} [:maybe ::sm/uuid]]
[:fill-color-ref-id {:optional true} [:maybe ::sm/uuid]]
[:fill-image {:optional true} ::ctc/image-color]])
(def schema:stroke
(sm/register!
^{::sm/type ::stroke}
[:map {:title "Stroke"}
[:stroke-color {:optional true} :string]
[:stroke-color-ref-file {:optional true} ::sm/uuid]
[:stroke-color-ref-id {:optional true} ::sm/uuid]
[:stroke-opacity {:optional true} ::sm/safe-number]
[:stroke-style {:optional true}
[::sm/one-of #{:solid :dotted :dashed :mixed :none :svg}]]
[:stroke-width {:optional true} ::sm/safe-number]
[:stroke-alignment {:optional true}
[::sm/one-of #{:center :inner :outer}]]
[:stroke-cap-start {:optional true}
[::sm/one-of stroke-caps]]
[:stroke-cap-end {:optional true}
[::sm/one-of stroke-caps]]
[:stroke-color-gradient {:optional true} ::ctc/gradient]
[:stroke-image {:optional true} ::ctc/image-color]]))
(sm/register! ::fill schema:fill)
(def ^:private schema:stroke
[:map {:title "Stroke"}
[:stroke-color {:optional true} :string]
[:stroke-color-ref-file {:optional true} ::sm/uuid]
[:stroke-color-ref-id {:optional true} ::sm/uuid]
[:stroke-opacity {:optional true} ::sm/safe-number]
[:stroke-style {:optional true}
[::sm/one-of #{:solid :dotted :dashed :mixed :none :svg}]]
[:stroke-width {:optional true} ::sm/safe-number]
[:stroke-alignment {:optional true}
[::sm/one-of #{:center :inner :outer}]]
[:stroke-cap-start {:optional true}
[::sm/one-of stroke-caps]]
[:stroke-cap-end {:optional true}
[::sm/one-of stroke-caps]]
[:stroke-color-gradient {:optional true} ::ctc/gradient]
[:stroke-image {:optional true} ::ctc/image-color]])
(sm/register! ::stroke schema:stroke)
(def check-stroke
(sm/check-fn schema:stroke))
@@ -172,7 +172,8 @@
[:width ::sm/safe-number]
[:height ::sm/safe-number]])
(def schema:shape-generic-attrs
;; FIXME: rename to shape-generic-attrs
(def schema:shape-attrs
[:map {:title "ShapeAttrs"}
[:page-id {:optional true} ::sm/uuid]
[:component-id {:optional true} ::sm/uuid]
@@ -276,7 +277,7 @@
[]
(->> (sg/generator schema:shape-base-attrs)
(sg/mcat (fn [{:keys [type] :as shape}]
(sg/let [attrs1 (sg/generator schema:shape-generic-attrs)
(sg/let [attrs1 (sg/generator schema:shape-attrs)
attrs2 (sg/generator schema:shape-geom-attrs)
attrs3 (case type
:text (sg/generator schema:text-attrs)
@@ -294,100 +295,94 @@
(merge attrs1 shape attrs2 attrs3)))))
(sg/fmap create-shape)))
(def schema:shape-attrs
[:multi {:dispatch :type
:decode/json (fn [shape]
(update shape :type keyword))
:title "Shape"}
[:group
[:merge {:title "GroupShape"}
ctsl/schema:layout-attrs
schema:group-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]
[:frame
[:merge {:title "FrameShape"}
ctsl/schema:layout-attrs
::ctsl/layout-attrs
schema:frame-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
schema:shape-base-attrs
::ctv/variant-shape
::ctv/variant-container]]
[:bool
[:merge {:title "BoolShape"}
ctsl/schema:layout-attrs
schema:bool-attrs
schema:shape-generic-attrs
schema:shape-base-attrs]]
[:rect
[:merge {:title "RectShape"}
ctsl/schema:layout-attrs
schema:rect-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]
[:circle
[:merge {:title "CircleShape"}
ctsl/schema:layout-attrs
schema:circle-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]
[:image
[:merge {:title "ImageShape"}
ctsl/schema:layout-attrs
schema:image-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]
[:svg-raw
[:merge {:title "SvgRawShape"}
ctsl/schema:layout-attrs
schema:svg-raw-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]
[:path
[:merge {:title "PathShape"}
ctsl/schema:layout-attrs
schema:path-attrs
schema:shape-generic-attrs
schema:shape-base-attrs]]
[:text
[:merge {:title "TextShape"}
ctsl/schema:layout-attrs
schema:text-attrs
schema:shape-generic-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]])
(def schema:shape
(sm/register!
^{::sm/type ::shape}
[:and {:title "Shape"
:gen/gen (shape-generator)
:decode/json {:leave decode-shape}}
[:fn shape?]
schema:shape-attrs]))
[:and {:title "Shape"
:gen/gen (shape-generator)
:decode/json {:leave decode-shape}}
[:fn shape?]
[:multi {:dispatch :type
:decode/json (fn [shape]
(update shape :type keyword))
:title "Shape"}
[:group
[:merge {:title "GroupShape"}
::ctsl/layout-child-attrs
schema:group-attrs
schema:shape-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]
(def check-shape-generic-attrs
(sm/check-fn schema:shape-generic-attrs))
[:frame
[:merge {:title "FrameShape"}
::ctsl/layout-child-attrs
::ctsl/layout-attrs
schema:frame-attrs
schema:shape-attrs
schema:shape-geom-attrs
schema:shape-base-attrs
::ctv/variant-shape
::ctv/variant-container]]
(def check-shape-attrs
[:bool
[:merge {:title "BoolShape"}
::ctsl/layout-child-attrs
schema:bool-attrs
schema:shape-attrs
schema:shape-base-attrs]]
[:rect
[:merge {:title "RectShape"}
::ctsl/layout-child-attrs
schema:rect-attrs
schema:shape-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]
[:circle
[:merge {:title "CircleShape"}
::ctsl/layout-child-attrs
schema:circle-attrs
schema:shape-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]
[:image
[:merge {:title "ImageShape"}
::ctsl/layout-child-attrs
schema:image-attrs
schema:shape-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]
[:svg-raw
[:merge {:title "SvgRawShape"}
::ctsl/layout-child-attrs
schema:svg-raw-attrs
schema:shape-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]
[:path
[:merge {:title "PathShape"}
::ctsl/layout-child-attrs
schema:path-attrs
schema:shape-attrs
schema:shape-base-attrs]]
[:text
[:merge {:title "TextShape"}
::ctsl/layout-child-attrs
schema:text-attrs
schema:shape-attrs
schema:shape-geom-attrs
schema:shape-base-attrs]]]])
(sm/register! ::shape schema:shape)
(def check-shape-attrs!
(sm/check-fn schema:shape-attrs))
(def check-shape
(def check-shape!
(sm/check-fn schema:shape
:hint "expected valid shape"))

View File

@@ -168,24 +168,25 @@
(def item-align-self-types
#{:start :end :center :stretch})
(def schema:layout-attrs
[:map {:title "LayoutChildAttrs"}
[:layout-item-margin-type {:optional true} [::sm/one-of item-margin-types]]
[:layout-item-margin {:optional true}
[:map
[:m1 {:optional true} ::sm/safe-number]
[:m2 {:optional true} ::sm/safe-number]
[:m3 {:optional true} ::sm/safe-number]
[:m4 {:optional true} ::sm/safe-number]]]
[:layout-item-max-h {:optional true} ::sm/safe-number]
[:layout-item-min-h {:optional true} ::sm/safe-number]
[:layout-item-max-w {:optional true} ::sm/safe-number]
[:layout-item-min-w {:optional true} ::sm/safe-number]
[:layout-item-h-sizing {:optional true} [::sm/one-of item-h-sizing-types]]
[:layout-item-v-sizing {:optional true} [::sm/one-of item-v-sizing-types]]
[:layout-item-align-self {:optional true} [::sm/one-of item-align-self-types]]
[:layout-item-absolute {:optional true} :boolean]
[:layout-item-z-index {:optional true} ::sm/safe-number]])
(sm/register!
^{::sm/type ::layout-child-attrs}
[:map {:title "LayoutChildAttrs"}
[:layout-item-margin-type {:optional true} [::sm/one-of item-margin-types]]
[:layout-item-margin {:optional true}
[:map
[:m1 {:optional true} ::sm/safe-number]
[:m2 {:optional true} ::sm/safe-number]
[:m3 {:optional true} ::sm/safe-number]
[:m4 {:optional true} ::sm/safe-number]]]
[:layout-item-max-h {:optional true} ::sm/safe-number]
[:layout-item-min-h {:optional true} ::sm/safe-number]
[:layout-item-max-w {:optional true} ::sm/safe-number]
[:layout-item-min-w {:optional true} ::sm/safe-number]
[:layout-item-h-sizing {:optional true} [::sm/one-of item-h-sizing-types]]
[:layout-item-v-sizing {:optional true} [::sm/one-of item-v-sizing-types]]
[:layout-item-align-self {:optional true} [::sm/one-of item-align-self-types]]
[:layout-item-absolute {:optional true} :boolean]
[:layout-item-z-index {:optional true} ::sm/safe-number]])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMAS

View File

@@ -16,8 +16,6 @@
[app.common.types.shape.layout :as ctl]
[app.common.uuid :as uuid]))
;; FIXME: the order of arguments seems arbitrary, container should be a first artgument
(defn add-shape
"Insert a shape in the tree, at the given index below the given parent or frame.
Update the parent as needed."

View File

@@ -17,25 +17,25 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def schema:typography
(sm/register!
^{::sm/type ::typography}
[:map {:title "Typography"}
[:id ::sm/uuid]
[:name :string]
[:font-id :string]
[:font-family :string]
[:font-variant-id :string]
[:font-size :string]
[:font-weight :string]
[:font-style :string]
[:line-height :string]
[:letter-spacing :string]
[:text-transform :string]
[:modified-at {:optional true} ::sm/inst]
[:path {:optional true} [:maybe :string]]
[:plugin-data {:optional true} ::ctpg/plugin-data]]))
[:map {:title "Typography"}
[:id ::sm/uuid]
[:name :string]
[:font-id :string]
[:font-family :string]
[:font-variant-id :string]
[:font-size :string]
[:font-weight :string]
[:font-style :string]
[:line-height :string]
[:letter-spacing :string]
[:text-transform :string]
[:modified-at {:optional true} ::sm/inst]
[:path {:optional true} [:maybe :string]]
[:plugin-data {:optional true} ::ctpg/plugin-data]])
(def check-typography
(sm/register! ::typography schema:typography)
(def check-typography!
(sm/check-fn ::typography))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@@ -23,11 +23,9 @@
(def schema:variant-component
;; A component that is part of a variant set.
(sm/register!
^{::sm/type ::variant-component}
[:map
[:variant-id {:optional true} ::sm/uuid]
[:variant-properties {:optional true} [:vector schema:variant-property]]]))
[:map
[:variant-id {:optional true} ::sm/uuid]
[:variant-properties {:optional true} [:vector schema:variant-property]]])
(def schema:variant-shape
;; The root shape of the main instance of a variant component.
@@ -42,6 +40,7 @@
[:is-variant-container {:optional true} :boolean]])
(sm/register! ::variant-property schema:variant-property)
(sm/register! ::variant-component schema:variant-component)
(sm/register! ::variant-shape schema:variant-shape)
(sm/register! ::variant-container schema:variant-container)

View File

@@ -3,7 +3,6 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{{ description or metadata.description }}">
<link rel="stylesheet" href="{{ '/css/index.css' | url }}">
<link rel="stylesheet" href="{{ '/css/prism.css' | url }}">
<link rel="shortcut icon" href="/img/favicon.png">
@@ -15,7 +14,7 @@
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
{% metagen
title=title or metadata.title,
desc=desc or metadata.desc,
desc=desc or metadata.desc or description or metadata.description,
url="https://help.penpot.app" + page.url,
img="https://help.penpot.app/img/th-help-center.jpg",
img_alt=alt,

View File

@@ -1,5 +1,6 @@
---
title: 04· Code of Conduct
desc: Learn about contributing to the Penpot project! This page outlines the Code of Conduct, reporting bugs, translations, core code contributions, & more.
---
<h1 id="coc">Code of conduct</h1>

View File

@@ -1,5 +1,6 @@
---
title: 03· Core code contributions
desc: Learn how to contribute to Penpot's open-source design collaboration platform. Find guidelines for bug reporting, code contributions & more.
---
<h1 id="code-contributions">Core code contributions</h1>

View File

@@ -1,5 +1,6 @@
---
title: Contributing
desc: Learn how to contribute to Penpot, the open-source design collaboration platform! Find guides on bug reporting, translations, code contributions, and more.
eleventyNavigation:
key: Contributing
order: 3

View File

@@ -1,5 +1,6 @@
---
title: 05· Libraries & Templates
desc: Contribute to Penpot's libraries & templates! Learn how to share your files and access resources. Try Penpot - It's free!
---
<h1 id="libraries">Libraries & templates</h1>

View File

@@ -1,5 +1,6 @@
---
title: 01· Reporting bugs
desc: Learn how to contribute to Penpot, the open-source design and prototyping platform! Find guidelines for reporting bugs, translations, & code contributions.
---
<h1 id="reporting-bugs">Reporting bugs</h1>

View File

@@ -1,5 +1,6 @@
---
title: 02· Translations
desc: Contribute to Penpot! Learn how to translate Penpot into your language using Weblate. Add new translations, languages, or edit existing ones today.
---
<h1 id="translations">Translations</h1>

View File

@@ -1,5 +1,6 @@
---
title: Help center
desc: Find user guides, technical documentation, plugin info, FAQs, and contributing guidelines in Penpot's help center. Join the open-source community!
layout: layouts/home.njk
twitter: "@penpotapp"
image: img/placeholder.png

View File

@@ -1,6 +1,7 @@
---
layout: layouts/plugins.njk
title: 4. API
desc: Create, deploy, and use the Penpot plugin API with our comprehensive documentation. Get started today and expand Penpot's capabilities.
---
# Penpot plugins API

View File

@@ -1,5 +1,7 @@
---
layout: layouts/plugins-no-sidebar.njk
title: Beta changelog
desc: See the Penpot plugin API changelog for version 1.0! Find breaking changes, deprecations, new features, and updated documentation. Try Penpot for free.
---
# Beta changelog

View File

@@ -1,6 +1,7 @@
---
layout: layouts/plugins.njk
title: 2. Create a Plugin
desc: Dive into Penpot plugin development! This guide covers creating plugins from scratch or using templates, libraries, API communication, & deployment.
---
# Create a Plugin

View File

@@ -1,6 +1,7 @@
---
layout: layouts/plugins.njk
title: 3. Deployment
desc: Deploy your free Penpot plugins! Learn about Netlify, Cloudflare, Surge & Penpot submission in this guide. Build and share your creations.
---
# Deployment

View File

@@ -1,6 +1,7 @@
---
layout: layouts/plugins.njk
title: 5. Examples and templates
desc: Learn to create shapes, text, layouts, components, themes, and interactive prototypes. Start building now! See Penpot plugins with examples & templates!
---
# Examples and templates
@@ -117,7 +118,6 @@ Just a friendly reminder that it's important to have the <b>comment permissions<
<a target="_blank" href="https://github.com/penpot/penpot-plugins-samples/tree/main/create-comments">Comments example</a>
## 5.2. Templates
As we mentioned in the <a target="_blank" href="/plugins/create-a-plugin/">Create a plugin</a> section, we've got two great options for you to get started with your plugin.

View File

@@ -1,6 +1,7 @@
---
layout: layouts/plugins.njk
title: 6. FAQ
desc: Find answers to common questions about plugin development, from choosing the right Node version to creating components. See Penpot plugins!
---
# FAQ

View File

@@ -1,6 +1,7 @@
---
layout: layouts/plugins.njk
title: 1. Getting started
desc: Dive into Penpot plugins! Extend Penpot's functionality by automating tasks and adding new features using JavaScript, HTML, & CSS. Get started now!
---
# Getting started

View File

@@ -1,6 +1,7 @@
---
layout: layouts/plugins-home.njk
title: Plugins
desc: "Get started with Penpot Plugins: Installation, development, and deployment. Access API documentation, examples, templates, and FAQs."
eleventyNavigation:
key: Plugins
order: 5

View File

@@ -1,5 +1,6 @@
---
title: 2. Penpot Configuration
desc: Learn about self-hosting, configuration via environment variables, and authentication providers. Try Penpot - It's free! See Penpot's technical guide.
---
# Penpot Configuration

View File

@@ -1,5 +1,6 @@
---
title: Backend app
desc: Dive into self-hosting, configuration, developer insights (architecture, data model), integration, and troubleshooting. See Penpot's Technical Guide.
---
# Backend app

View File

@@ -1,5 +1,6 @@
---
title: Common code
desc: Learn about architecture, data models, and development environments. See Penpot's technical guide for developers. Dive into common code.
---
# Common code

View File

@@ -1,5 +1,6 @@
---
title: Exporter app
desc: Learn about self-hosting, configuration, architecture (backend, frontend), data model, and development environment. See Penpot's technical guide.
---
# Exporter app

View File

@@ -1,5 +1,6 @@
---
title: Frontend app
desc: Dive into the UI, data namespaces, ClojureScript, React, and worker app functionalities. View Penpot's frontend app architecture. Free to try!
---
### Frontend app

View File

@@ -1,5 +1,6 @@
---
title: 3.01. Architecture
desc: Dive into architecture, backend, frontend, data models, and development environments. Contribute and self-host for free! See Penpot's technical guide.
---
# Architecture

View File

@@ -1,5 +1,6 @@
---
title: 3.04. Common Guide
desc: "View Penpot's technical guide: self-hosting, configuration, developer insights, architecture, data model, integration, and troubleshooting."
---
# Common guide

View File

@@ -1,5 +1,6 @@
---
title: 3.08. Data Guide
desc: Learn about data structures, code organization, file operations, migrations, shape editing, and component syncing. See Penpot's technical guide. Try it free!
---
# Data Guide

View File

@@ -1,5 +1,6 @@
---
title: 3.02. Data model
desc: Learn about self-hosting, configuration, developer tools, data models, architecture, and integrations. View Penpot's technical guide. Free to use!
---
# Penpot Data Model

View File

@@ -1,5 +1,6 @@
---
title: 3.03. Dev environment
desc: Dive into Penpot's development environment. Learn about self-hosting, configuration, developer tools, architecture, and more. See the Penpot Technical Guide!
---
# Development environment
@@ -95,7 +96,7 @@ npx shadow-cljs cljs-repl main
### Storybook
The storybook local server is started on tmux **window 2** and will listen
for changes in the styles, components or stories defined in the folders
for changes in the styles, components or stories defined in the folders
under the design system namespace: `app.main.ui.ds`.
You can open the broser on http://localhost:6006/ to see it.

View File

@@ -1,5 +1,6 @@
---
title: 3.05. Frontend Guide
desc: "See Penpot's technical guide: self-hosting, configuration, developer insights (architecture, data model), frontend, backend, and integrations & more!"
---
# Frontend Guide

View File

@@ -1,5 +1,6 @@
---
title: 3. Developer Guide
desc: Dive into architecture, data models, and more. Start building today! See Penpot's technical guide for self-hosting, configuration, and developer insights.
---
# Developer Guide

View File

@@ -1,5 +1,6 @@
---
title: Assets storage
desc: Learn about assets storage, API, object buckets, sharing, and garbage collection. See Penpot's technical guide for developers. Try Penpot - It's free.
---
# Assets storage

View File

@@ -1,5 +1,6 @@
---
title: Authentication
desc: Dive into Penpot today! Learn about self-hosting, configuration, developer insights, authentication, and more. View Penpot's technical guide. Try it free.
---
# User authentication

View File

@@ -1,5 +1,6 @@
---
title: 3.09. Penpot subsystems
desc: Learn about architecture, data models, and subsystems. View Penpot's technical guide for self-hosting, configuration, and development insights. Free!
---
# Penpot subsystems
@@ -12,4 +13,3 @@ implemented, over the whole app (backend, frontend or exporter), and points to
the most relevant source files to look at to start exploring it. When some
special considerations are needed (performance questions, limits, common
"gotchas", historic reasons of some decisions, etc.) they are also noted.

View File

@@ -1,5 +1,6 @@
---
title: 3.10. UI Guide
desc: Learn UI development with React & Rumext, design system implementation, and performance considerations. See Penpot's technical guide. Free to use!
---
# UI Guide

View File

@@ -1,5 +1,6 @@
---
title: 1.3 Install with Docker
desc: This Penpot technical guide covers self-hosting, Docker installation, configuration, updates, backups, and proxy setup with NGINX and Caddy. Try Penpot!
---
<p class="advice">

View File

@@ -1,5 +1,6 @@
---
title: 1. Self-hosting Guide
desc: Customize your Penpot instance today. Learn how to install with Elestio, Docker, or Kubernetes from the technical guide for self-hosting options.
---
# Self-hosting Guide

View File

@@ -1,5 +1,6 @@
---
title: 1.4 Install with Kubernetes
desc: Learn how to install and configure Penpot on your Kubernetes cluster using Helm. Our technical guide provides step-by-step instructions for setup.
---
# Install with Kubernetes

View File

@@ -1,5 +1,6 @@
---
title: 1.1 Recommended storage
desc: Learn recommended self-hosting settings, Docker & Kubernetes installs, configuration, and troubleshooting tips in Penpot's technical guide.
---
# Recommended storage

View File

@@ -1,5 +1,6 @@
---
title: 1.5 Unofficial self-host options
desc: Find guides for Docker, Kubernetes, and more in Penpot's Technical Guide for self-hosting options! Discover unofficial self-host options too.
---
# Unofficial self-host options

View File

@@ -1,5 +1,6 @@
---
title: Technical Guide
desc: Get self-hosting instructions, integration details, and developer resources. Troubleshoot issues easily. Try Penpot free! See Penpot's technical guide.
eleventyNavigation:
key: Technical Guide
order: 4

View File

@@ -1,5 +1,6 @@
---
title: 4. Integration Guide
desc: Connect Penpot with other apps using webhooks and access tokens! Learn from Penpot's integration guide for seamless workflows. Try Penpot - It's free.
---
# Integration Guide

View File

@@ -1,5 +1,6 @@
---
title: 5. Troubleshooting Penpot
desc: Troubleshoot Penpot like a pro! Our technical guide offers tips and tricks for diagnosing issues, reading logs, and resolving problems. Get started now!
---
# Troubleshooting Penpot

View File

@@ -1,10 +1,11 @@
---
title: 11· Components
desc: Streamline your design workflow with Penpot's Components guide! Learn to create, duplicate, group, and manage reusable components.
---
<h1 id="components">Components</h1>
<p class="main-paragraph">Speed your workflow with the reusable power of components.</p>
<p>A component is an object or group of objects that can be reused multiple times across files. This can help you maintain consistency across a group of designs.</p>
<p>A component is an object or group of objects that can be reused multiple times across files. This can help you maintain consistency across a group of designs.</p>
<p>A component has two parts:</p>
<ul>
@@ -62,7 +63,7 @@ title: 11· Components
<h2 id="component-group">Group components</h2>
<h3>Create component groups</h3>
<p>At the Components section from the Assets library, there are two ways to create groups in a components library.</p>
<p>At the Components section from the Assets library, there are two ways to create groups in a components library.</p>
<ol>
<li><strong>Using slashes (/):</strong> Select one component and rename it as follows: "FOLDER NAME/COMPONENT NAME". For example, "Buttons/Alert Button".</li>
<li><strong>Using the "Group" option:</strong> Select one or more components at the Assets library, right click to show the menu and then select "Group".</li>

View File

@@ -1,5 +1,6 @@
---
title: 17· Custom fonts
desc: Penpot's guide on custom fonts! Upload, manage, and use custom fonts in Penpot! Enhance your designs with personalised typography.
---
<h1 id="customfonts">Custom fonts</h1>
@@ -13,9 +14,9 @@ title: 17· Custom fonts
<h3>To upload a local font:</h3>
<ol>
<li>Press “Add custom font”.</li>
<li>Inspect your local files to select one or more fonts that you want to upload. <strong>You can upload fonts with
<li>Inspect your local files to select one or more fonts that you want to upload. <strong>You can upload fonts with
the following formats: TTF, OTF and WOFF</strong>. Only one format will be needed.</li>
<li>Change the font name if needed. The font name is the name that will be shown in the font list at the workspace.
<li>Change the font name if needed. The font name is the name that will be shown in the font list at the workspace.
It is also what Penpot uses to group fonts in families. You can always edit it later.</li>
<li>Once ready, press upload. That's it. The font will be available at the font list of this teams files.</li>
</ol>

View File

@@ -1,5 +1,6 @@
---
title: 07· Exporting objects
desc: Learn how to export objects in Penpot, the free, open-source design collaboration tool. This guide covers export presets, file formats, and more!
---
<h1 id="export">Exporting objects</h1>

View File

@@ -1,5 +1,6 @@
---
title: 08· Flexible Layouts
desc: Master responsive web design with Penpot's flexible and grid layouts! Learn Flexbox and CSS Grid standards. Explore tutorials, properties, and more.
---
<h1 id="layouts">Flexible Layouts</h1>

View File

@@ -1,5 +1,6 @@
---
title: 15· Import/export files
desc: Learn how to import and export files in Penpot, the free, open-source design tool. Discover file formats, backups, sharing, and library management.
---
<h1 id="import-export">Import and export files</h1>

View File

@@ -1,5 +1,6 @@
---
title: User guide
desc: Learn everything from interface basics to advanced features like prototyping and design sharing with Penpot's comprehensive user guide! Free access.
eleventyNavigation:
key: User guide
order: 2

View File

@@ -1,5 +1,6 @@
---
title: 14· Inspect designs
desc: Learn how to inspect designs in Penpot! This guide covers distances, properties, code snippets (CSS, SVG, HTML), & exporting assets for seamless collaboration.
---
<h1 id="inspect">Inspect designs</h1>

View File

@@ -1,5 +1,6 @@
---
title: 01· Introduction
desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorials. Learn the interface, layers, objects, styling, and more.
---
<h1 id="section-1">Introduction</h1>

View File

@@ -1,5 +1,6 @@
---
title: Tutorials & info
desc: Begin with Penpot's comprehensive user guide! Get tutorials, learn interface basics, and master design features. Discover FAQs and more.
---
<h1 id="section-1-1">Tutorials & info</h1>

View File

@@ -1,5 +1,6 @@
---
title: Quickstart
desc: Start instantly with Penpot, the open-source design and prototyping platform! Access our free user guide to learn now.
---
<h1 id="section-1-1">Quickstart</h1>

View File

@@ -1,5 +1,6 @@
---
title: Shortcuts
desc: Get quickstart tips, shortcuts, and tutorials for Penpot! Learn interface basics and more with this free, open-source design tool.
---
<h1 id="section-1-1">Shortcuts</h1>

View File

@@ -1,5 +1,6 @@
---
title: 04· Layer basics
desc: Master layer basics with Penpot's user guide! Learn to create, manipulate, and organize layers for stunning designs. Try Penpot, it's free!
---
<h1 id="layer-basics">Layer basics</h1>

View File

@@ -1,5 +1,6 @@
---
title: 09· Asset Libraries
desc: Use Penpot's asset libraries for reusable design elements! Learn to create, manage, and share components, colors, and typography. Try Penpot - it's free!
---
<h1 id="asset-libraries">Asset Libraries</h1>
@@ -112,7 +113,7 @@ title: 09· Asset Libraries
</figure>
<h4>Group assets</h4>
<p>There are two ways to create groups in a library.</p>
<p>There are two ways to create groups in a library.</p>
<ol>
<li><strong>With slashes (/):</strong> Select an asset and rename it as follows: "FOLDER NAME/ASSET NAME". For example, "Buttons/Alert Button".</li>
<li><strong>With the "Group" option:</strong> Select one or more assets at the library, then right click to show the menu and then select "Group".</li>
@@ -138,7 +139,7 @@ title: 09· Asset Libraries
<h2 id="libraries">Libraries</h2>
<h3 id="file-libraries">File libraries</h3>
<p>Each file has its own file library which is where the assets that belong to this file are stored.</p>
<p>Each file has its own file library which is where the assets that belong to this file are stored.</p>
<p>You have two ways to access the file library from the file <a href="/user-guide/the-interface/#interface-workspace">workspace</a>:</p>
<ul>
<li>Click the assets tab icon at the left sidebar.</li>

View File

@@ -1,5 +1,6 @@
---
title: 05· Objects
desc: "Work with Penpot's objects: boards, shapes, text, paths, and graphics. Learn to create, select, rename, and customize boards for optimal workflow."
---
<h1 id="objects">Objects</h1>
@@ -7,7 +8,7 @@ title: 05· Objects
available in Penpot, and how to get the most of them.</p>
<h2 id="Boards">Boards</h2>
<p>A Board is a layer typically used as a container for a design. Boards are useful if you want to design for a specific screen or print size. Boards can contain other boards. First level boards
<p>A Board is a layer typically used as a container for a design. Boards are useful if you want to design for a specific screen or print size. Boards can contain other boards. First level boards
are shown by default at the <a href="/user-guide/view-mode">View mode</a>, acting as screens of a design or pages of a document. Also, objects inside boards can be clipped. Boards are a powerful element at Penpot, opening up a ton of possibilities when creating and organizing your designs.</p>
<h3>Create boards</h3>
@@ -80,7 +81,7 @@ are shown by default at the <a href="/user-guide/view-mode">View mode</a>, actin
</figure>
<h3>Show in View mode</h3>
<p>Boards offer the option to be shown as a separate board/screen in the <a href="/user-guide/the-interface/#interface-viewmode">View mode</a>. Use this setting to decide what boards should be shown as individual items in your presentations.</p>
<p>Boards offer the option to be shown as a separate board/screen in the <a href="/user-guide/the-interface/#interface-viewmode">View mode</a>. Use this setting to decide what boards should be shown as individual items in your presentations.</p>
<p><strong>Defaults</strong></p>
<p>As it is very likely that the first level boards will be used as a screen and the interiors will not, there are different defaults for newly created boards.</p>
<ul>

View File

@@ -1,5 +1,6 @@
---
title: 18· Plugins
desc: Extend Penpot's functionality with plugins! Install from PenpotHub or via URL. Learn to install, use, and create your own plugins.
---
<h1 id="penpot-plugins">Penpot Plugins</h1>

View File

@@ -1,5 +1,6 @@
---
title: 12· Prototyping
desc: This Penpot user guide explains how to prototype interactions, connect boards, use triggers/actions, and animations. Learn to build flows and more!
---
<h1 id="prototype">Prototyping interactions</h1>

View File

@@ -1,13 +1,14 @@
---
title: 06· Styling
desc: Style your designs with Penpot's options! Learn about color fills, gradients, strokes, shadows, blur, opacity, blend modes, and property copying.
---
<h1 id="styling">Styling</h1>
<p class="main-paragraph">Penpot has a variety of styling options for each object. When selected, the styling options are displayed in the design panel on the right.</p>
<h2 id="fill">Color fills</h2>
<p>Color fills can be added to boards, shapes, texts and groups of layers.</p>
<p>You can add as fills:</p>
<p>Color fills can be added to boards, shapes, texts and groups of layers.</p>
<p>You can add as fills:</p>
<ul>
<li>Custom colors (hex).</li>
<li>Color <a href="/user-guide/libraries/#asset-types">assets</a>.</li>
@@ -169,7 +170,7 @@ title: 06· Styling
</figure>
<h2 id="blend">Opacity and blend</h2>
<p>Set the overal opacity for layers and their blend mode.</p>
<p>Set the overal opacity for layers and their blend mode.</p>
<p>Blend allows you to control how a layer interacts with the layers beneath it, determining how pixels from the current layer are combined with pixels in the underlying layers. Use blend to achive various effects, such as shading, highlights, or creative visual styles.</p>
<figure>
<img alt="Layer blend and opacity" src="/img/styling/blend-opacity.webp"/>
@@ -195,7 +196,7 @@ title: 06· Styling
</ul>
<h2 id="copy-paste-properties">Copy/Paste properties</h2>
<p>You can copy and apply properties, including fills, strokes, shadows, and others from one layer to another—or multiple layers with just a few clicks. You can do it using the layer's menu or shortcuts.</p>
<p>You can copy and apply properties, including fills, strokes, shadows, and others from one layer to another—or multiple layers with just a few clicks. You can do it using the layer's menu or shortcuts.</p>
<figure>
<video title="Apply blur to a layer" muted="" playsinline="" controls="" width="100%" poster="/img/styling/copy-properties.webp" height="auto">

View File

@@ -1,11 +1,12 @@
---
title: 16· Teams
desc: Manage teams and roles with Penpot's collaboration features! Learn how to manage teams, roles (Viewer, Editor, Admin, Owner), send invites and use webhooks.
---
<h1 id="teams">Teams</h1>
<p class="main-paragraph">A team is a group of members who collaborate on a collection of projects.
<p class="main-paragraph">A team is a group of members who collaborate on a collection of projects.
Team members are allowed to work with any project or file within the team. The actions that each team
member is allowed to do depends on their permissions.</p>
member is allowed to do depends on their permissions.</p>
<p class="main-paragraph">At Penpot you can create and join as many teams as you need and add all necessary stakeholders with no team size limits.</p>
<h2 id="teams-management">Manage teams</h2>

View File

@@ -1,5 +1,6 @@
---
title: 02· The interface
desc: Discover Penpot's free user guide! Learn the interface, workspace basics, flexible layouts, and prototyping. Master Penpot today.
---
<h1 id="the-interface">The interface</h1>

View File

@@ -1,5 +1,6 @@
---
title: 13· View mode
desc: Present designs and share prototypes with Penpot's View mode! Play interactions, navigate boards, zoom, and toggle fullscreen. Try it for free!
---
<h1 id="viewmode">View mode</h1>

View File

@@ -1,5 +1,6 @@
---
title: 03· Workspace basics
desc: Master Penpot's workspace basics! Learn interface navigation, zoom tools, dynamic alignment, rulers, guides, and shortcuts.
---
<h1 id="workspace-basics">Workspace basics</h1>

View File

@@ -42,7 +42,7 @@
:dev
{:extra-paths ["dev"]
:extra-deps
{thheller/shadow-cljs {:mvn/version "3.0.3"}
{thheller/shadow-cljs {:mvn/version "2.28.18"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"}

View File

@@ -25,7 +25,6 @@
"build:app:libs": "node ./scripts/build-libs.js",
"build:app:main": "clojure -M:dev:shadow-cljs release main worker",
"build:app": "yarn run clear:shadow-cache && yarn run build:app:main && yarn run build:app:libs",
"build:library": "yarn run clear:shadow-cache && clojure -M:dev:shadow-cljs release library",
"e2e:server": "node ./scripts/e2e-server.js",
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
"fmt:clj:check": "cljfmt check --parallel=false src/ test/",
@@ -45,7 +44,6 @@
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
"clear:shadow-cache": "rm -rf .shadow-cljs",
"watch:app": "yarn run clear:shadow-cache && concurrently \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
"watch:library": "yarn run clear:shadow-cache && clojure -M:dev:shadow-cljs watch library",
"watch": "yarn run watch:app:assets",
"watch:storybook": "concurrently \"storybook dev -p 6006 --no-open\" \"yarn run watch:storybook:assets\"",
"watch:storybook:assets": "node ./scripts/watch-storybook.js"
@@ -91,7 +89,7 @@
"rimraf": "^6.0.1",
"sass": "^1.83.4",
"sass-embedded": "^1.83.4",
"shadow-cljs": "3.0.3",
"shadow-cljs": "2.28.20",
"storybook": "^8.5.2",
"svg-sprite": "^2.0.4",
"typescript": "^5.7.3",

View File

@@ -409,7 +409,7 @@ test.describe("Tokens: Tokens Tab", () => {
// Clearing the input field should pick hex
await valueField.fill("");
await expect(
tokensUpdateCreateModal.getByText("Token value cannot be empty"),
tokensUpdateCreateModal.getByText("Resolved value: -"),
).toBeVisible();
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
await expect(valueField).toHaveValue(/^#[A-Fa-f\d]+$/);

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 208 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Some files were not shown because too many files have changed in this diff Show More