Compare commits

...

48 Commits

Author SHA1 Message Date
Andres Gonzalez
9ebc976f4d 📚 Add Files and Projects section to user guide 2026-01-15 12:05:49 +01:00
andrés gonzález
b72de2dc8f 📚 Add shadow token documentation (#8082) 2026-01-15 09:09:08 +01:00
Alejandro Alonso
51635770ce Merge pull request #8065 from penpot/eva-fix-lost-translation
🐛 Fix translation on rename token
2026-01-14 09:44:41 +01:00
Eva Marco
18a4e63da0 🐛 Fix translation on rename token 2026-01-13 15:57:09 +01:00
Andrey Antukh
d71f811dbe Update help center build script 2026-01-13 10:35:15 +01:00
Andrey Antukh
1beb3b86aa 🐛 Add missing msgid_plural on several translations 2026-01-12 11:52:07 +01:00
Andrey Antukh
fe20bdd00e 🐛 Fix multiple selection options on dashboard deleted page (#8055)
* 🐛 Fix multiple selection options on dashboard deleted page

* 📎 Fix translations
2026-01-12 11:41:37 +01:00
María Valderrama
5420897b92 🐛 Fix empty state message in trash page (#8045) 2026-01-12 11:17:01 +01:00
Andrey Antukh
e430a4c9f3 Revert " Backport translations from develop"
This reverts commit ec6d72bd91.
2026-01-12 10:40:17 +01:00
Andrey Antukh
ec6d72bd91 Backport translations from develop 2026-01-12 09:39:33 +01:00
Eva Marco
6ec451b46d 🐛 Fix resolved value on line height (#8047) 2026-01-09 12:54:24 +01:00
Andrey Antukh
2b836f10cb 🐛 Do not show deleted files on search (#8036)
* 🐛 Do not show deleted files on search

* 💄 Add cosmetic changes to dashboard deleted files page
2026-01-09 11:11:29 +01:00
Andrey Antukh
1ae0f3fc87 Merge pull request #8037 from penpot/niwinz-staging-project-name-fix
🐛 Fix long project name visual problem on dashboard
2026-01-08 17:41:37 +01:00
Eva Marco
e13c203b8d ♻️ Refactor scss file 2026-01-08 16:35:56 +01:00
Andrey Antukh
90efb665b5 Add several additional renames for make translation string consistent 2026-01-08 14:37:58 +01:00
Pablo Alba
47ee490158 🐛 Fix typos on download modal 2026-01-08 14:37:58 +01:00
Andrey Antukh
f0f89599bc 🌐 Backport translations from develop 2026-01-08 14:08:02 +01:00
Andrey Antukh
b0dc7d6ffb 🔧 Change default jmx port on deps.edn 2026-01-08 13:56:22 +01:00
Andrey Antukh
b7cd315872 🐛 Fix wasm-playground on devenv 2026-01-08 13:48:09 +01:00
Eva Marco
743d4e5c8d 🐛 Fix error on shadow token creation (#8029) 2026-01-08 13:26:01 +01:00
Andrey Antukh
97e4f4c424 🐛 Fix long project name visual problem on dashboard 2026-01-08 11:40:55 +01:00
Belén Albeza
fb9560c315 🐛 Fix guides dropdown width (#8031)
* 🐛 Fix width of guides column dropdown

* ♻️ Remove deprecated tokens in css

* 🔧 Update changelog
2026-01-08 10:47:11 +01:00
Alejandro Alonso
d53c090900 Merge pull request #8028 from penpot/elenatorro-12956-fix-text-color-tokens
🐛 Fix missing text color token from selected shapes in selected colors list
2026-01-07 16:49:41 +01:00
Elena Torro
621e030095 🐛 Fix missing text color token from selected shapes in selected colors list 2026-01-07 16:41:25 +01:00
Alejandro Alonso
157e4aa2d0 Merge pull request #8025 from penpot/elenatorro-12951-fix-inner-text-shadow-token
🐛 Fix inner shadow selector on shadow token
2026-01-07 16:37:19 +01:00
Elena Torro
7cd2308f3b 🐛 Fix inner shadow selector on shadow token 2026-01-07 16:36:51 +01:00
Alejandro Alonso
c315a15b48 Merge pull request #8026 from penpot/elenatorro-12997-fix-clojure-on-css-box-shadow
🐛 Fix CSS generated box-shadow property
2026-01-07 16:32:12 +01:00
Elena Torro
8a3e6d026e 🐛 Fix CSS generated box-shadow property 2026-01-07 16:28:05 +01:00
Florian Schrödl
0dd062d011 🐛 Fix line-height throwing for int (#7927) 2026-01-07 16:13:10 +01:00
Alejandro Alonso
bfbb546699 Merge pull request #8027 from penpot/superalex-fix-colors-assets-from-shared-libraries
🐛 Fix color assets from shared libraries
2026-01-07 14:16:57 +01:00
Alejandro Alonso
083e77e9c5 🐛 Fix color assets from shared libraries 2026-01-07 14:02:28 +01:00
Alejandro Alonso
919f78daeb Merge pull request #7965 from penpot/eva-fix-styles-on-viewer
🐛 Fix inspect tab styles on viewer
2026-01-07 11:54:33 +01:00
Eva Marco
b5c30f8c41 🐛 Fix inspect tab styles on viewer 2026-01-07 11:41:49 +01:00
Alejandro Alonso
60aa426753 Merge pull request #8022 from penpot/alotor-fix-drag-handlers
🐛 Fix problem with dragging handlers
2026-01-07 11:36:26 +01:00
Alejandro Alonso
86f7d6b26b Sanitizing error values (#8020) 2026-01-07 11:23:19 +01:00
Andrey Antukh
36732a4bd3 Make the devenv runtine initialization yarn independent (#8023) 2026-01-07 11:21:58 +01:00
Andrey Antukh
eff572d3bb Revert "💄 Group tokens by name path (#7775)"
This reverts commit 0956b66281.
2026-01-07 11:20:44 +01:00
alonso.torres
d470d96833 🐛 Fix problem with dragging handlers 2026-01-07 11:00:02 +01:00
Aitor Moreno
cab70773d2 Merge pull request #7667 from penpot/azazeln28-doc-add-more-info-text-editor-v2-readme
📚 Add more info about text editor v2
2026-01-07 09:40:06 +01:00
Alejandro Alonso
32ca42a093 Merge remote-tracking branch 'origin/staging-render' into staging 2026-01-05 13:21:58 +01:00
Alejandro Alonso
523a97a4ec Merge pull request #8016 from penpot/alotor-fix-refresh-thumbnails
🐛 Fix problem with thumbnail regeneration
2026-01-05 13:21:34 +01:00
Alejandro Alonso
260f6861a3 Merge pull request #8015 from penpot/alotor-fix-grid-component-auto-sizing
🐛 Fix problem with grid layout components and auto sizing
2026-01-05 13:18:59 +01:00
alonso.torres
edd53b419a 🐛 Fix problem with thumbnail regeneration 2026-01-05 13:09:40 +01:00
alonso.torres
078a3d5a5c 🐛 Fix problem with grid layout components and auto sizing 2026-01-05 10:54:36 +01:00
Alejandro Alonso
c4e57427ac Merge branch 'staging-render' into staging 2026-01-05 10:30:06 +01:00
Alejandro Alonso
218f34380a Merge pull request #8012 from penpot/azazeln28-refactor-minor-changes
♻️ Minor naming changes and event handling
2026-01-02 14:16:55 +01:00
Aitor Moreno
6c6b3db87e ♻️ Minor naming changes and event handling 2026-01-02 13:41:48 +01:00
Aitor Moreno
e39f292499 📚 Add more info about text editor v2 2026-01-02 10:13:34 +01:00
130 changed files with 2808 additions and 1287 deletions

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.pnpm-store
*-init.clj
*.css.json
*.jar

View File

@@ -21,6 +21,14 @@
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565)
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915)
- Fix color assets from shared libraries not appearing as assets in Selected colors panel [Taiga #12957](https://tree.taiga.io/project/penpot/issue/12957)
- Fix CSS generated box-shadow property [Taiga #12997](https://tree.taiga.io/project/penpot/issue/12997)
- Fix inner shadow selector on shadow token [Taiga #12951](https://tree.taiga.io/project/penpot/issue/12951)
- Fix missing text color token from selected shapes in selected colors list [Taiga #12956](https://tree.taiga.io/project/penpot/issue/12956)
- Fix dropdown option width in Guides columns dropdown [Taiga #12959](https://tree.taiga.io/project/penpot/issue/12959)
- Fix typos on download modal [Taiga #12865](https://tree.taiga.io/project/penpot/issue/12865)
## 2.12.1

View File

@@ -97,8 +97,8 @@
:jmx-remote
{:jvm-opts ["-Dcom.sun.management.jmxremote"
"-Dcom.sun.management.jmxremote.port=9090"
"-Dcom.sun.management.jmxremote.rmi.port=9090"
"-Dcom.sun.management.jmxremote.port=9000"
"-Dcom.sun.management.jmxremote.rmi.port=9000"
"-Dcom.sun.management.jmxremote.local.only=false"
"-Dcom.sun.management.jmxremote.authenticate=false"
"-Dcom.sun.management.jmxremote.ssl=false"

View File

@@ -36,17 +36,6 @@
[integrant.core :as ig]
[yetti.response :as-alias yres]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn obfuscate-string
[s]
(if (< (count s) 10)
(apply str (take (count s) (repeat "*")))
(str (subs s 0 5)
(apply str (take (- (count s) 5) (repeat "*"))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OIDC PROVIDER (GENERIC)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -177,7 +166,7 @@
(l/inf :hint "provider initialized"
:provider (:id provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)))
:client-secret (d/obfuscate-string (:client-secret provider)))
provider)
(catch Throwable cause
@@ -222,7 +211,7 @@
(l/inf :hint "provider initialized"
:provider (:id provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)))
:client-secret (d/obfuscate-string (:client-secret provider)))
provider)
(catch Throwable cause
@@ -299,7 +288,7 @@
(l/inf :hint "provider initialized"
:provider (:id provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)))
:client-secret (d/obfuscate-string (:client-secret provider)))
provider)
(catch Throwable cause
@@ -341,7 +330,7 @@
:provider "gitlab"
:base-uri (:base-uri provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)))
:client-secret (d/obfuscate-string (:client-secret provider)))
provider)
(catch Throwable cause
(ex/raise :type ::internal
@@ -361,7 +350,7 @@
(l/inf :hint "provider initialized"
:provider (:id provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider)))
:client-secret (d/obfuscate-string (:client-secret provider)))
provider)
(catch Throwable cause
@@ -459,7 +448,7 @@
(l/trc :hint "fetch access token"
:provider (:id provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))
:client-secret (d/obfuscate-string (:client-secret provider))
:grant-type (:grant_type params)
:redirect-uri (:redirect_uri params))
@@ -512,7 +501,7 @@
[cfg provider tdata]
(l/trc :hint "fetch user info"
:uri (:user-uri provider)
:token (obfuscate-string (:token/access tdata)))
:token (d/obfuscate-string (:token/access tdata)))
(let [params {:uri (:user-uri provider)
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}

View File

@@ -19,7 +19,7 @@
inner join team_profile_rel as tpr on (tpr.team_id = p.team_id)
where tpr.profile_id = ?
and p.team_id = ?
and (p.deleted_at is null or p.deleted_at > now())
and (p.deleted_at is null)
and (tpr.is_admin = true or
tpr.is_owner = true or
tpr.can_edit = true)
@@ -29,7 +29,7 @@
inner join project_profile_rel as ppr on (ppr.project_id = p.id)
where ppr.profile_id = ?
and p.team_id = ?
and (p.deleted_at is null or p.deleted_at > now())
and (p.deleted_at is null)
and (ppr.is_admin = true or
ppr.is_owner = true or
ppr.can_edit = true)
@@ -47,7 +47,7 @@
left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn)
inner join projects as pr on (f.project_id = pr.id)
where f.name ilike ('%' || ? || '%')
and (f.deleted_at is null or f.deleted_at > now())
and (f.deleted_at is null)
order by f.created_at asc")
(defn search-files

View File

@@ -1024,6 +1024,26 @@
:clj
(sort comp-fn items))))
(defn obfuscate-string
"Obfuscates potentially sensitive values.
- One-arg arity:
* For strings shorter than 10 characters, all characters are replaced by `*`.
* For longer strings, the first 5 characters are preserved and the rest obfuscated.
- Two-arg arity accepts a boolean `full?` that, when true, replaces the whole value
by `*`, preserving only the length."
([v]
(obfuscate-string v false))
([v full?]
(let [s (str v)
n (count s)]
(cond
(zero? n) s
full? (apply str (repeat n "*"))
(< n 10) (apply str (repeat n "*"))
:else (str (subs s 0 5)
(apply str (repeat (- n 5) "*")))))))
(defn reorder
"Reorder a vector by moving one of their items from some position to some space between positions.
It clamps the position numbers to a valid range."

View File

@@ -10,6 +10,7 @@
(:refer-clojure :exclude [instance?])
(:require
#?(:clj [clojure.stacktrace :as strace])
[app.common.data :refer [obfuscate-string]]
[app.common.pprint :as pp]
[app.common.schema :as sm]
[clojure.core :as c]
@@ -19,6 +20,10 @@
(:import
clojure.lang.IPersistentMap)))
(def ^:private sensitive-fields
"Keys whose values must be obfuscated in validation explains."
#{:password :old-password :token :invitation-token})
#?(:clj (set! *warn-on-reflection* true))
(def ^:dynamic *data-length* 8)
@@ -110,7 +115,25 @@
(explain (:explain data) opts)
(contains? data ::sm/explain)
(sm/humanize-explain (::sm/explain data) opts)))
(let [exp (::sm/explain data)
sanitize-map (fn sanitize-map [m]
(reduce-kv
(fn [acc k v]
(let [k* (if (string? k) (keyword k) k)]
(cond
(contains? sensitive-fields k*)
(assoc acc k (if (map? v)
(sanitize-map v)
(obfuscate-string v true)))
(map? v) (assoc acc k (sanitize-map v))
:else (assoc acc k v))))
{}
m))
sanitize-explain (fn [exp]
(cond-> exp
(:value exp) (update :value sanitize-map)))]
(sm/humanize-explain (sanitize-explain exp) opts))))
#?(:clj
(defn format-throwable

View File

@@ -132,94 +132,3 @@ Some naming conventions:
(if-let [last-period (str/last-index-of s ".")]
[(subs s 0 (inc last-period)) (subs s (inc last-period))]
[s ""]))
;; Tree building functions --------------------------------------------------
"Build tree structure from flat list of paths"
"`build-tree-root` is the main function to build the tree."
"Receives a list of segments with 'name' properties representing paths,
and a separator string."
"E.g segments = [{... :name 'one/two/three'} {... :name 'one/two/four'} {... :name 'one/five'}]"
"Transforms into a tree structure like:
[{:name 'one'
:path 'one'
:depth 0
:leaf nil
:children-fn (fn [] [{:name 'two'
:path 'one.two'
:depth 1
:leaf nil
:children-fn (fn [] [{... :name 'three'} {... :name 'four'}])}
{:name 'five'
:path 'one.five'
:depth 1
:leaf {... :name 'five'}
...}])}]"
(defn- sort-by-children
"Sorts segments so that those with children come first."
[segments separator]
(sort-by (fn [segment]
(let [path (split-path (:name segment) :separator separator)
path-length (count path)]
(if (= path-length 1)
1
0)))
segments))
(defn- group-by-first-segment
"Groups segments by their first path segment and update segment name."
[segments separator]
(reduce (fn [acc segment]
(let [[first-segment & remaining-segments] (split-path (:name segment) :separator separator)
rest-path (when (seq remaining-segments) (join-path remaining-segments :separator separator :with-spaces? false))]
(update acc first-segment (fnil conj [])
(if rest-path
(assoc segment :name rest-path)
segment))))
{}
segments))
(defn- sort-and-group-segments
"Sorts elements and groups them by their first path segment."
[segments separator]
(let [sorted (sort-by-children segments separator)
grouped (group-by-first-segment sorted separator)]
grouped))
(defn- build-tree-node
"Builds a single tree node with lazy children."
[segment-name remaining-segments separator parent-path depth]
(let [current-path (if parent-path
(str parent-path "." segment-name)
segment-name)
is-leaf? (and (seq remaining-segments)
(every? (fn [segment]
(let [remaining-segment-name (first (split-path (:name segment) :separator separator))]
(= segment-name remaining-segment-name)))
remaining-segments))
leaf-segment (when is-leaf? (first remaining-segments))
node {:name segment-name
:path current-path
:depth depth
:leaf leaf-segment
:children-fn (when-not is-leaf?
(fn []
(let [grouped-elements (sort-and-group-segments remaining-segments separator)]
(mapv (fn [[child-segment-name remaining-child-segments]]
(build-tree-node child-segment-name remaining-child-segments separator current-path (inc depth)))
grouped-elements))))}]
node))
(defn build-tree-root
"Builds the root level of the tree."
[segments separator]
(let [grouped-elements (sort-and-group-segments segments separator)]
(mapv (fn [[segment-name remaining-segments]]
(build-tree-node segment-name remaining-segments separator nil 0))
grouped-elements)))

View File

@@ -269,8 +269,8 @@
"Remove flex children properties except the fit-content for flex layouts. These are properties
that we don't have to propagate to copies but will be respected when swapping components"
[shape]
(let [layout-item-h-sizing (when (and (ctl/flex-layout? shape) (ctl/auto-width? shape)) :auto)
layout-item-v-sizing (when (and (ctl/flex-layout? shape) (ctl/auto-height? shape)) :auto)]
(let [layout-item-h-sizing (when (and (ctl/any-layout? shape) (ctl/auto-width? shape)) :auto)
layout-item-v-sizing (when (and (ctl/any-layout? shape) (ctl/auto-height? shape)) :auto)]
(-> shape
(d/without-keys ctk/swap-keep-attrs)
(cond-> (some? layout-item-h-sizing)

View File

@@ -112,8 +112,10 @@
(:c2y params) (update-in [index :params :c2y] + (:c2y params)))
content))]
(impl/path-data
(reduce apply-to-index (vec content) modifiers))))
(if (some? modifiers)
(impl/path-data
(reduce apply-to-index (vec content) modifiers))
content)))
(defn transform-content
"Applies a transformation matrix over content and returns a new

View File

@@ -41,7 +41,10 @@ services:
- 6062:6062
- 6063:6063
- 6064:6064
- 9000:9000
- 9001:9001
- 9090:9090
- 9091:9091
environment:
- EXTERNAL_UID=${CURRENT_USER_ID}

View File

@@ -145,8 +145,8 @@ http {
proxy_pass http://127.0.0.1:3000/;
}
location /playground {
alias /home/penpot/penpot/experiments/;
location /wasm-playground {
alias /home/penpot/penpot/frontend/resources/public/wasm-playground/;
add_header Cache-Control "no-cache, max-age=0";
autoindex on;
}

View File

@@ -8,14 +8,10 @@ source ~/.bashrc
echo "[start-tmux.sh] Installing node dependencies"
pushd ~/penpot/frontend/
corepack install;
yarn install;
yarn playwright install chromium
./scripts/setup;
popd
pushd ~/penpot/exporter/
corepack install;
yarn install
yarn playwright install chromium
./scripts/setup;
popd
tmux -2 new-session -d -s penpot

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1,8 +1,10 @@
#!/usr/bin/env bash
source ~/.bashrc
set -ex
corepack enable;
corepack install;
rm -rf ./_dist
yarn
yarn install
yarn run build

View File

@@ -19,6 +19,12 @@ desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorial
<p>Create and manage your teams</p>
</a>
</li>
<li>
<a href="/user-guide/account-teams/projects-files">
<h2>Projects and Files →</h2>
<p>Organize your work with projects and files</p>
</a>
</li>
<li>
<a href="/user-guide/account-teams/comments/">
<h2>Comments →</h2>

View File

@@ -0,0 +1,107 @@
---
title: Projects and Files
order: 3
desc: Learn how to organize your work in Penpot. Create, manage and organize projects and files, work with drafts, and handle deleted items.
---
<h1 id="projects-files">Projects and Files</h1>
<p class="main-paragraph">Projects and files are the core organizational structure in Penpot. Projects work like folders that contain multiple design files, helping you organize your work efficiently. Files are your actual design documents where you create boards, pages, and all your design elements.</p>
<p class="main-paragraph">Understanding how to manage projects and files will help you keep your workspace organized and make it easier to collaborate with your team.</p>
<h2 id="projects-management">Projects</h2>
<p>Projects are containers that help you organize and group related design files together. Think of them as folders in a file system. You can create as many projects as you need to organize your work by client, product, feature, or any other structure that fits your workflow.</p>
<p>If you're working with others, projects should be created inside a team so that team members can collaborate on the files within them. Projects created in your personal space ("Your Penpot") remain private to you.</p>
<figure>
<img src="/img/files-projects/01-projects.webp" alt="Projects view in dashboard" />
</figure>
<h3 id="create-project">Create a project</h3>
<p>To create a new project, use the <strong>+ New project</strong> button in the dashboard. You can also use the keyboard shortcut <kbd>+</kbd> when you're on the dashboard. A dialog will appear where you can enter the project name. Once created, the project will appear in your projects list.</p>
<p>When you create a project, you can immediately start adding files to it, or create files first and move them into the project later.</p>
<h3 id="edit-project">Edit a project</h3>
<p>To edit a project's name, right-click on the project in the sidebar or click the three-dot menu next to the project name. Select <strong>Edit</strong> or <strong>Rename</strong> to change the project name. You can also update the project's profile picture from the same menu.</p>
<h3 id="pin-project">Pin a project</h3>
<p>Projects can be pinned to the sidebar for quick access. Right-click on a project and select <strong>Pin</strong> to keep it visible in the sidebar even when you have many projects. Pinned projects appear at the top of your projects list for easy access.</p>
<p>To unpin a project, right-click on it and select <strong>Unpin</strong>. The project will remain in your list but won't be pinned to the sidebar anymore.</p>
<figure>
<img src="/img/files-projects/04-pin-project.webp" alt="Pin project option" />
</figure>
<h3 id="move-project">Move a project</h3>
<p>Projects can be moved between teams. To move a project, right-click on it and select <strong>Move to</strong> from the context menu. A dialog will appear showing all available teams where you can move the project. Select the destination team and confirm the move.</p>
<figure>
<img src="/img/files-projects/06-move-project.webp" alt="Move project to another team" />
</figure>
<p>When you move a project to another team, all files within the project are moved along with it. Team members of the destination team will gain access to the project and its files according to their permissions.</p>
<p class="advice">Moving a project to another team changes its ownership and access permissions. Make sure the destination team has the appropriate members and permissions for the work contained in the project.</p>
<h3 id="delete-project">Delete a project</h3>
<p>To delete a project, right-click on it and select <strong>Delete</strong> from the menu. You'll be asked to confirm the deletion. Keep in mind that deleting a project will also delete all files within it. Make sure you have backed up any important files before deleting a project.</p>
<p class="advice">Deleted projects and their files are moved to the trash area where they can be restored or permanently deleted.</p>
<h2 id="files-management">Files</h2>
<p>Files are your design documents in Penpot. Each file contains pages, boards, and all the design elements you create. Files can be created within a project or in the drafts section, and you can move them between projects as needed.</p>
<h3 id="create-file">Create a file</h3>
<p>To create a new file, you have several options:</p>
<ul>
<li>Click the <strong>+</strong> button in a project to create a file inside that project</li>
<li>Use the keyboard shortcut <kbd>+</kbd> when you have a project selected</li>
<li>Create a file directly in the drafts section if you're not ready to organize it into a project yet</li>
</ul>
<figure>
<img src="/img/files-projects/05-create-file.webp" alt="Create a new file" />
</figure>
<p>When creating a file, you'll be asked to give it a name. The file will open in the workspace where you can start designing immediately.</p>
<h3 id="edit-file">Edit a file</h3>
<p>To rename a file, right-click on the file card in the dashboard and select <strong>Rename</strong>, or click on the three-dot menu on the file card. Enter the new name and confirm the change. You can also access file settings and other options from the file's context menu.</p>
<h3 id="move-file">Move a file</h3>
<p>Files can be moved between projects, from drafts to a project, or even to projects in other teams. To move a file, right-click on the file card and select <strong>Move to</strong> from the context menu. A dialog will appear showing all available projects across your teams where you can choose the destination. Select the project where you want to move the file and confirm.</p>
<p>You can also drag and drop files between projects in the dashboard for a quick way to reorganize your files within the same team.</p>
<p>When moving a file to a project in another team, the file becomes accessible to members of that team according to their permissions. Moving a file doesn't affect its content or any shared libraries it might be using. Only its location in your project structure changes.</p>
<p class="advice">When moving files between teams, be aware that this changes who has access to the file. Make sure the destination team has the appropriate members and permissions for the work contained in the file.</p>
<h3 id="duplicate-file">Duplicate a file</h3>
<p>To create a copy of an existing file, right-click on the file card and select <strong>Duplicate</strong>. The duplicated file will be created in the same location (project or drafts) with the same name plus "Copy" added to it. You can then rename or move it as needed.</p>
<p>Duplicating a file creates a complete copy including all pages, boards, and design elements. This is useful when you want to create variations of a design or use a file as a starting point for a new project.</p>
<h3 id="delete-file">Delete a file</h3>
<p>To delete a file, right-click on the file card and select <strong>Delete</strong>. You'll be asked to confirm the deletion. The file will be moved to the trash area where it can be restored or permanently deleted later.</p>
<p class="advice">Deleting a file doesn't immediately remove it permanently. You can recover deleted files from the trash area within a certain time period.</p>
<h2 id="drafts">Drafts</h2>
<p>The drafts section is a fixed, non-deletable space in your dashboard where you can create and store files that aren't part of any specific project yet. This is useful for quick sketches, experimental designs, or files you're not ready to organize into projects.</p>
<figure>
<img src="/img/files-projects/02-drafts.webp" alt="Drafts section" />
</figure>
<p>Drafts appear in a dedicated section in the dashboard sidebar, separate from your projects. All team members can see and access files in the drafts section, depending on their permissions.</p>
<p>You can create files directly in drafts, or move existing files from projects into drafts if you want to temporarily remove them from a project's organization. Files in drafts work exactly like files in projects - they have the same functionality and features.</p>
<p>When you're ready to organize a file from drafts, you can move it into an appropriate project using the move option in the file's context menu.</p>
<h2 id="trash-area">Trash area</h2>
<p>When you delete projects or files, they are not removed permanently. Instead, they are moved to a trash area, a dedicated space for deleted content. This allows you to recover mistakenly deleted content or permanently remove items when you're sure you don't need them anymore.</p>
<p>The trash applies to both files and projects. Items in the trash remain there for a certain period depending on your Penpot subscription plan before being automatically deleted permanently.</p>
<h3 id="access-trash">Access the trash</h3>
<p>A <strong>Trash</strong> section is accessible from the dashboard navigation. When you access it, you'll see all your deleted files and projects, each clearly labeled so you can easily identify what you want to restore or permanently delete.</p>
<figure>
<img src="/img/files-projects/03-trash.webp" alt="Trash area" />
</figure>
<h3 id="trash-permissions">Trash permissions</h3>
<p>Access to the trash and the actions you can perform depend on your role in the team:</p>
<ul>
<li><strong>Owner, Admin, and Editor:</strong> Can view the trash, restore deleted items, and permanently delete items from the trash.</li>
<li><strong>Viewer:</strong> Cannot access the trash or manage deleted content.</li>
</ul>
<h3 id="restore-items">Restore items</h3>
<p>To restore a deleted file or project, access the trash area and find the item you want to recover. Select the item and choose <strong>Restore</strong>. The item will be restored to its original location (the project it belonged to, or the drafts section if it wasn't in a project).</p>
<h3 id="permanently-delete">Permanently delete items</h3>
<p>If you're sure you don't need an item anymore, you can permanently delete it from the trash. Select the item and choose <strong>Permanently delete</strong>. This action cannot be undone, so make sure you really want to remove the item permanently.</p>
<p class="advice">Items in the trash are automatically deleted after a certain period depending on your subscription plan. If you want to keep something, restore it before the auto-deletion period expires.</p>

View File

@@ -455,6 +455,43 @@ ExtraBold Italic
<p>A <strong>Typography composite token</strong> can be applied to a full text layer to set all typography properties at once. This lets you manage complete text styles using a single token instead of combining multiple individual ones.</p>
<p>When applying a Typography composite token to a layer, any previously applied <em>Typography composite token</em> or <em>style</em> will be detached. The same happens in reverse. Only one of them can be active at a time.</p>
<h3 id="design-tokens-shadow">Shadow</h3>
<p>Shadow tokens are composite entities that encapsulate the properties of one or more shadows into a single token definition. This token can contain a single shadow or an array of multiple shadows that can be reordered.</p>
<p>Shadow tokens support both <strong>Drop Shadow</strong> and <strong>Inner Shadow</strong> types. When creating or editing a shadow token, you can select the type of shadow you want to use. The default selection is Drop Shadow.</p>
<figure>
<img src="/img/design-tokens/37-tokens-shadow-individual.webp" alt="Shadow token creation with individual values" />
</figure>
<h4 id="design-tokens-shadow-properties">Shadow properties</h4>
<p>Each shadow within a shadow token contains a set of properties that define how the shadow appears:</p>
<ul>
<li><strong>Color:</strong> The color of the shadow. Accepts the same values as <a href="#design-tokens-color">color tokens</a> (Hex, RGB, RGBA, ARGB, HSL, HSLA), and you can reference existing color tokens. The color picker is available when defining the value.</li>
<li><strong>X offset:</strong> The horizontal offset of the shadow. Can be unit or unitless, and accepts negative values. You can use a number or reference a <a href="#design-tokens-number">number</a> or <a href="#design-tokens-dimensions">dimension</a> token.</li>
<li><strong>Y offset:</strong> The vertical offset of the shadow. Can be unit or unitless, and accepts negative values. You can use a number or reference a <a href="#design-tokens-number">number</a> or <a href="#design-tokens-dimensions">dimension</a> token.</li>
<li><strong>Blur:</strong> The blur radius of the shadow. Can be unit or unitless. You can use a number or reference a <a href="#design-tokens-number">number</a> or <a href="#design-tokens-dimensions">dimension</a> token.</li>
<li><strong>Spread:</strong> The spread radius of the shadow. Can be unit or unitless. You can use a number or reference a <a href="#design-tokens-number">number</a> or <a href="#design-tokens-dimensions">dimension</a> token.</li>
<li><strong>Type:</strong> Whether the shadow is a drop shadow or an inner shadow. Selected via a dropdown menu, with Drop Shadow as the default.</li>
</ul>
<p>Each property within a shadow token can reference existing tokens or be assigned hardcoded values. Shadows can also reference other shadow tokens (the type of shadow must match when using references).</p>
<p class="advice">Not all properties are mandatory to save a shadow token. Some can be empty (and will be computed as 0). Only the color property is mandatory. In an array of shadows, if any shadow does not have the color set, the form cannot be saved.</p>
<h4 id="design-tokens-shadow-create">Creating shadow tokens</h4>
<p>To create a shadow token, click on the <strong>+</strong> next to <strong>Shadow</strong> in the Tokens panel. Shadow tokens can be created in two ways:</p>
<ul>
<li><strong>Individual values:</strong> You can create one shadow or multiple shadows with individual property values. Click the <strong>+</strong> button to add more shadows to the array. New shadows are added at the top of the list.</li>
<li><strong>Single reference:</strong> You can reference another existing shadow token. When using a single reference, you cannot add more than one shadow. The resolved value will display the shadow or list of shadows that the referenced token contains.</li>
</ul>
<figure>
<img src="/img/design-tokens/38-tokens-shadow-reference.webp" alt="Shadow token creation with reference" />
</figure>
<p>When creating a shadow with individual values, the color value starts empty, but the other inputs have default values (X: 4, Y: 4, Blur: 4, Spread: 0). You can reorder shadows by hovering over a shadow form and using the reorder button to drag it to a different position.</p>
<p>You can also reference another existing shadow token instead of defining each property manually. When doing so, Penpot resolves all shadow properties from the referenced token.</p>
<h4 id="design-tokens-shadow-apply">Applying shadow tokens</h4>
<p>Shadow tokens can be applied to any layer type. Clicking on a shadow token will apply it to the selected layer. Right-clicking on a shadow token shows the context menu with the <strong>Shadow</strong> option to apply it.</p>
<p class="advice">Text elements in CSS do not support inner shadows, but Penpot does, since it uses the filter property internally instead of the box-shadow property.</p>
<p>When applying a shadow token, any existing shadow on the layer will be overridden (whether it's a raw shadow or an applied token shadow). If the token contains an array of shadows, each shadow will be added in the same order as in the creation form.</p>
<p class="advice">In Penpot, an element can have multiple shadows, but only one token of the same type can be applied. This means that applying a second shadow token would override the first one, regardless of how many shadows the shape currently has.</p>

8
exporter/scripts/setup Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -e;
corepack enable;
corepack install;
yarn install;
yarn playwright install chromium

View File

@@ -20,12 +20,7 @@ test.describe("Dashboard Deleted Page", () => {
// Navigate directly to deleted page
await dashboardPage.goToDeleted();
// Check for the restore all and clear trash buttons
await expect(
page.getByRole("button", { name: "Restore All" }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Clear trash" }),
).toBeVisible();
// Check for the delete-page-section element
await expect(page.getByTestId("deleted-page-section")).toBeVisible();
});
});

View File

@@ -305,7 +305,7 @@ test.describe("Inspect tab - Styles", () => {
);
await openInspectTab(workspacePage);
const panel = await getPanelByTitle(workspacePage, "Size & position");
const panel = await getPanelByTitle(workspacePage, "Size and position");
await expect(panel).toBeVisible();
const propertyRow = panel.getByTestId("property-row");
@@ -335,7 +335,7 @@ test.describe("Inspect tab - Styles", () => {
);
await openInspectTab(workspacePage);
const panel = await getPanelByTitle(workspacePage, "Size & position");
const panel = await getPanelByTitle(workspacePage, "Size and position");
await expect(panel).toBeVisible();
const propertyRow = panel.getByTestId("property-row");
@@ -375,7 +375,7 @@ test.describe("Inspect tab - Styles", () => {
);
await openInspectTab(workspacePage);
const panel = await getPanelByTitle(workspacePage, "Size & position");
const panel = await getPanelByTitle(workspacePage, "Size and position");
await expect(panel).toBeVisible();
const propertyRow = panel.getByTestId("property-row");

View File

@@ -40,7 +40,6 @@ const setupEmptyTokensFile = async (page, options = {}) => {
tokensUpdateCreateModal: workspacePage.tokensUpdateCreateModal,
tokenThemesSetsSidebar: workspacePage.tokenThemesSetsSidebar,
tokenSetItems: workspacePage.tokenSetItems,
tokensSidebar: workspacePage.tokensSidebar,
tokenSetGroupItems: workspacePage.tokenSetGroupItems,
tokenContextMenuForSet: workspacePage.tokenContextMenuForSet,
};
@@ -111,12 +110,15 @@ const checkInputFieldWithError = async (
).toBeVisible();
};
const checkInputFieldWithoutError = async (inputLocator) => {
const checkInputFieldWithoutError = async (
tokenThemeUpdateCreateModal,
inputLocator,
) => {
expect(await inputLocator.getAttribute("aria-invalid")).toBeNull();
expect(await inputLocator.getAttribute("aria-describedby")).toBeNull();
};
const testTokenCreationFlow = async (
async function testTokenCreationFlow(
page,
{
tokenLabel,
@@ -130,7 +132,7 @@ const testTokenCreationFlow = async (
resolvedValueText,
secondResolvedValueText,
},
) => {
) {
const invalidValueError = "Invalid token value";
const emptyNameError = "Name should be at least 1 character";
const selfReferenceError = "Token has self reference";
@@ -240,45 +242,7 @@ const testTokenCreationFlow = async (
await expect(
tokensTabPanel.getByRole("button", { name: "my-token-2" }),
).toBeEnabled();
};
const unfoldTokenTree = async (tokensTabPanel, type, tokenName) => {
const tokenSegments = tokenName.split(".");
const tokenFolderTree = tokenSegments.slice(0, -1);
const tokenLeafName = tokenSegments.pop();
const typeParentWrapper = tokensTabPanel.getByTestId(`section-${type}`);
const typeSectionButton = typeParentWrapper
.getByRole("button", {
name: type,
})
.first();
const isSectionExpanded =
await typeSectionButton.getAttribute("aria-expanded");
if (isSectionExpanded === "false") {
await typeSectionButton.click();
}
for (const segment of tokenFolderTree) {
const segmentButton = typeParentWrapper
.getByRole("listitem")
.getByRole("button", { name: segment })
.first();
const isExpanded = await segmentButton.getAttribute("aria-expanded");
if (isExpanded === "false") {
await segmentButton.click();
}
}
await expect(
typeParentWrapper.getByRole("button", {
name: tokenLeafName,
}),
).toBeEnabled();
};
}
test.describe("Tokens: Tokens Tab", () => {
test("Clicking tokens tab button opens tokens sidebar tab", async ({
@@ -434,12 +398,15 @@ test.describe("Tokens: Tokens Tab", () => {
const emptyNameError = "Name should be at least 1 character";
const selfReferenceError = "Token has self reference";
const missingReferenceError = "Missing token references";
const { tokensUpdateCreateModal, tokenThemesSetsSidebar, tokensSidebar } =
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
await setupEmptyTokensFile(page);
await tokensSidebar
.getByRole("button", { name: "Add Token: Color" })
.click();
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
const addTokenButton = tokensTabPanel.getByRole("button", {
name: `Add Token: Color`,
});
await addTokenButton.click();
await expect(tokensUpdateCreateModal).toBeVisible();
// Placeholder checks
@@ -504,34 +471,38 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(submitButton).toBeEnabled();
await submitButton.click();
await unfoldTokenTree(tokensSidebar, "color", "color.primary");
await expect(
tokensTabPanel.getByRole("button", {
name: "color.primary",
}),
).toBeEnabled();
// Create token referencing the previous one with keyboard
await tokensSidebar
await tokensTabPanel
.getByRole("button", { name: "Add Token: Color" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
await nameField.click();
await nameField.fill("secondary");
await nameField.fill("color.secondary");
await nameField.press("Tab");
await valueField.click();
await valueField.fill("{color.primary}");
await expect(submitButton).toBeEnabled();
await submitButton.press("Enter");
await nameField.press("Enter");
await expect(
tokensSidebar.getByRole("button", {
name: "secondary",
tokensTabPanel.getByRole("button", {
name: "color.secondary",
}),
).toBeEnabled();
// Tokens tab panel should have two tokens with the color red / #ff0000
await expect(
tokensSidebar.getByRole("button", { name: "#ff0000" }),
tokensTabPanel.getByRole("button", { name: "#ff0000" }),
).toHaveCount(2);
// Global set has been auto created and is active
@@ -547,7 +518,7 @@ test.describe("Tokens: Tokens Tab", () => {
).toHaveAttribute("aria-checked", "true");
// Check color picker
await tokensSidebar
await tokensTabPanel
.getByRole("button", { name: "Add Token: Color" })
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
@@ -1108,7 +1079,7 @@ test.describe("Tokens: Tokens Tab", () => {
const emptyNameError = "Name should be at least 1 character";
const { tokensUpdateCreateModal, tokenThemesSetsSidebar } =
await setupEmptyTokensFile(page, { flags: ["enable-token-shadow"] });
await setupEmptyTokensFile(page, {flags: ["enable-token-shadow"]});
// Open modal
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
@@ -1536,15 +1507,24 @@ test.describe("Tokens: Tokens Tab", () => {
test("User edits token and auto created set show up in the sidebar", async ({
page,
}) => {
const { tokensUpdateCreateModal, tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page);
const {
workspacePage,
tokensUpdateCreateModal,
tokenThemesSetsSidebar,
tokensSidebar,
tokenContextMenuForToken,
} = await setupTokensFile(page);
await expect(tokensSidebar).toBeVisible();
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
const tokensColorGroup = tokensSidebar.getByRole("button", {
name: "Color 92",
});
await expect(tokensColorGroup).toBeVisible();
await tokensColorGroup.click();
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await expect(colorToken).toBeVisible();
await colorToken.click({ button: "right" });
@@ -1561,10 +1541,8 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(tokensUpdateCreateModal).not.toBeVisible();
await unfoldTokenTree(tokensSidebar, "color", "colors.blue.100.changed");
const colorTokenChanged = tokensSidebar.getByRole("button", {
name: "changed",
name: "colors.blue.100.changed",
});
await expect(colorTokenChanged).toBeVisible();
});
@@ -1655,10 +1633,11 @@ test.describe("Tokens: Tokens Tab", () => {
});
test("User creates grouped color token", async ({ page }) => {
const { workspacePage, tokensUpdateCreateModal, tokensSidebar } =
const { workspacePage, tokensUpdateCreateModal, tokenThemesSetsSidebar } =
await setupEmptyTokensFile(page);
await tokensSidebar
const tokensTabPanel = page.getByRole("tabpanel", { name: "tokens" });
await tokensTabPanel
.getByRole("button", { name: "Add Token: Color" })
.click();
@@ -1670,7 +1649,7 @@ test.describe("Tokens: Tokens Tab", () => {
const valueField = tokensUpdateCreateModal.getByLabel("Value");
await nameField.click();
await nameField.fill("dark.primary");
await nameField.fill("color.dark.primary");
await valueField.click();
await valueField.fill("red");
@@ -1681,9 +1660,7 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(submitButton).toBeEnabled();
await submitButton.click();
await unfoldTokenTree(tokensSidebar, "color", "dark.primary");
await expect(tokensSidebar.getByLabel("primary")).toBeEnabled();
await expect(tokensTabPanel.getByLabel("color.dark.primary")).toBeEnabled();
});
test("User cant create regular token with value missing", async ({
@@ -1699,6 +1676,7 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
const valueField = tokensUpdateCreateModal.getByLabel("Value");
const submitButton = tokensUpdateCreateModal.getByRole("button", {
name: "Save",
});
@@ -1708,7 +1686,7 @@ test.describe("Tokens: Tokens Tab", () => {
// Fill in name but leave value empty
await nameField.click();
await nameField.fill("primary");
await nameField.fill("color.primary");
// Submit button should remain disabled when value is empty
await expect(submitButton).toBeDisabled();
@@ -1726,6 +1704,7 @@ test.describe("Tokens: Tokens Tab", () => {
.click();
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
const valueField = tokensUpdateCreateModal.getByLabel("Value");
await valueField.click();
@@ -1775,10 +1754,15 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(tokensSidebar).toBeVisible();
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
const tokensColorGroup = tokensSidebar.getByRole("button", {
name: "Color 92",
});
await expect(tokensColorGroup).toBeVisible();
await tokensColorGroup.click();
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await colorToken.click({ button: "right" });
@@ -1798,10 +1782,15 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(tokensSidebar).toBeVisible();
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
const tokensColorGroup = tokensSidebar.getByRole("button", {
name: "Color 92",
});
await expect(tokensColorGroup).toBeVisible();
await tokensColorGroup.click();
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await expect(colorToken).toBeVisible();
await colorToken.click({ button: "right" });
@@ -1814,7 +1803,8 @@ test.describe("Tokens: Tokens Tab", () => {
});
test("User fold/unfold color tokens", async ({ page }) => {
const { tokensSidebar } = await setupTokensFile(page);
const { tokensSidebar, tokenContextMenuForToken } =
await setupTokensFile(page);
await expect(tokensSidebar).toBeVisible();
@@ -1824,10 +1814,8 @@ test.describe("Tokens: Tokens Tab", () => {
await expect(tokensColorGroup).toBeVisible();
await tokensColorGroup.click();
unfoldTokenTree(tokensSidebar, "color", "colors.blue.100");
const colorToken = tokensSidebar.getByRole("button", {
name: "100",
name: "colors.blue.100",
});
await expect(colorToken).toBeVisible();
await tokensColorGroup.click();
@@ -2230,10 +2218,13 @@ test.describe("Tokens: Apply token", () => {
const tokensTabButton = page.getByRole("tab", { name: "Tokens" });
await tokensTabButton.click();
unfoldTokenTree(tokensSidebar, "color", "colors.black");
await tokensSidebar
.getByRole("button")
.filter({ hasText: "Color" })
.click();
await tokensSidebar
.getByRole("button", { name: "black" })
.getByRole("button", { name: "colors.black" })
.click({ button: "right" });
await tokenContextMenuForToken.getByText("Fill").click();
@@ -2471,7 +2462,7 @@ test.describe("Tokens: Apply token", () => {
await expect(tokensUpdateCreateModal).toBeVisible();
const nameField = tokensUpdateCreateModal.getByLabel("Name");
await nameField.fill("primary");
await nameField.fill("shadow.primary");
// User adds first shadow with a color from the color ramp
const firstShadowFields = tokensUpdateCreateModal.getByTestId(
@@ -2718,11 +2709,9 @@ test.describe("Tokens: Apply token", () => {
await submitButton.click();
await expect(tokensUpdateCreateModal).not.toBeVisible();
unfoldTokenTree(tokensSidebar, "shadow", "primary");
// Verify token appears in sidebar
const shadowToken = tokensSidebar.getByRole("button", {
name: "primary",
name: "shadow.primary",
});
await expect(shadowToken).toBeEnabled();

View File

@@ -667,6 +667,9 @@
}
// UI ELEMENTS
// FIXME: This is used multiple times accross the app. We should design this in
// the DS and create a proper component for it.
.asset-element {
@include bodySmallTypography;
display: flex;

6
frontend/scripts/setup Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
corepack enable;
corepack install;
yarn install;
yarn playwright install chromium;

View File

@@ -1,10 +1,10 @@
#!/usr/bin/env bash
set -ex
corepack enable;
corepack install;
yarn install;
SCRIPT_DIR=$(dirname $0);
set -ex
$SCRIPT_DIR/setup;
yarn run playwright install chromium --with-deps;
yarn run build:storybook
yarn run test:storybook

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env bash
SCRIPT_DIR=$(dirname $0);
set -ex
corepack enable;
corepack install;
yarn install;
yarn run playwright install chromium --with-deps;
$SCRIPT_DIR/setup;
yarn run test:e2e -x --workers=2 --reporter=list "$@";

View File

@@ -236,7 +236,7 @@
Uses `font-size-value` to calculate the relative line-height value.
Returns an error for an invalid font-size value."
[line-height-value font-size-value font-size-errors]
(let [missing-references (seq (some cto/find-token-value-references line-height-value))
(let [missing-references (seq (cto/find-token-value-references line-height-value))
error
(cond
missing-references

View File

@@ -432,10 +432,12 @@
:val position-data
:ignore-touched true
:ignore-geometry true}]})))]
(dch/commit-changes
{:redo-changes changes :undo-changes []
:save-undo? false
:tags #{:position-data}})))))))
(when (d/not-empty? changes)
(dch/commit-changes
{:redo-changes changes :undo-changes []
:save-undo? false
:tags #{:position-data}})))))
(rx/take-until stoper-s))))
(->> stream
(rx/filter dch/commit?)

View File

@@ -1122,7 +1122,7 @@
ref-id (:stroke-color-ref-id stroke)
colors (-> libraries
(get ref-id)
(get ref-file)
(get :data)
(ctl/get-colors))
shared? (contains? colors ref-id)
@@ -1167,7 +1167,7 @@
ref-file (get color :ref-file)
ref-id (get color :ref-id)
colors (-> libraries
(get ref-id)
(get ref-file)
(get :data)
(ctl/get-colors))
shared? (contains? colors ref-id)
@@ -1180,19 +1180,20 @@
:index (:index shadow)}))
(defn- text->color-att
[fill file-id libraries]
[fill file-id libraries & {:keys [has-token-applied token-name]}]
(let [ref-file (:fill-color-ref-file fill)
ref-id (:fill-color-ref-id fill)
colors (-> libraries
(get ref-id)
(get ref-file)
(get :data)
(ctl/get-colors))
shared? (contains? colors ref-id)
attrs (cond-> (types.fills/fill->color fill)
(not (or shared? (= ref-file file-id)))
(dissoc :ref-file :ref-id))]
base-attrs (cond-> (types.fills/fill->color fill)
(not (or shared? (= ref-file file-id)))
(dissoc :ref-file :ref-id))
attrs (cond-> base-attrs
has-token-applied (assoc :has-token-applied true)
token-name (assoc :token-name token-name))]
{:attrs attrs
:prop :content
:shape-id (:shape-id fill)
@@ -1200,13 +1201,18 @@
(defn- extract-text-colors
[text file-id libraries]
(let [treat-node
(let [applied-fill-token (get-in text [:applied-tokens :fill])
treat-node
(fn [node shape-id]
(map-indexed #(assoc %2 :shape-id shape-id :index %1) node))]
(map-indexed (fn [idx fill]
(let [args (cond-> []
(and (= idx 0) applied-fill-token)
(conj :has-token-applied true :token-name applied-fill-token))]
(apply text->color-att (assoc fill :shape-id shape-id :index idx) file-id libraries args)))
node))]
(->> (txt/node-seq txt/is-text-node? (:content text))
(map :fills)
(mapcat #(treat-node % (:id text)))
(map #(text->color-att % file-id libraries)))))
(mapcat #(treat-node % (:id text))))))
(defn- fill->color-att
"Given a fill map enriched with :shape-id, :index, and optionally
@@ -1232,7 +1238,7 @@
ref-id (:fill-color-ref-id fill)
colors (-> libraries
(get ref-id)
(get ref-file)
(get :data)
(ctl/get-colors))
shared? (contains? colors ref-id)

View File

@@ -47,32 +47,31 @@
(ptk/reify ::apply-content-modifiers
ptk/WatchEvent
(watch [it state _]
(let [page-id (get state :current-page-id state)
objects (dsh/lookup-page-objects state)
id (st/get-path-id state)
shape
(st/get-path state)
(let [id (st/get-path-id state)
shape (st/get-path state)
content-modifiers
(dm/get-in state [:workspace-local :edit-path id :content-modifiers])
(dm/get-in state [:workspace-local :edit-path id :content-modifiers])]
(if (or (nil? shape) (nil? content-modifiers))
(rx/of (dwe/clear-edition-mode))
(let [page-id (get state :current-page-id state)
objects (dsh/lookup-page-objects state)
content (get shape :content)
new-content (path/apply-content-modifiers content content-modifiers)
content (get shape :content)
new-content (path/apply-content-modifiers content content-modifiers)
old-points (path.segment/get-points content)
new-points (path.segment/get-points new-content)
point-change (->> (map hash-map old-points new-points) (reduce merge))]
old-points (path.segment/get-points content)
new-points (path.segment/get-points new-content)
point-change (->> (map hash-map old-points new-points) (reduce merge))]
(when (and (some? new-content) (some? shape))
(let [changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
(if (empty? new-content)
(rx/of (dch/commit-changes changes)
(dwe/clear-edition-mode))
(rx/of (dch/commit-changes changes)
(selection/update-selection point-change)
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))))))
(when (and (some? new-content) (some? shape))
(let [changes (changes/generate-path-changes it objects page-id shape (:content shape) new-content)]
(if (empty? new-content)
(rx/of (dch/commit-changes changes)
(dwe/clear-edition-mode))
(rx/of (dch/commit-changes changes)
(selection/update-selection point-change)
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))))))))
(defn modify-content-point
[content {dx :x dy :y} modifiers point]

View File

@@ -4,22 +4,29 @@
//
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
// FIXME: we need this import for .asset-element
@use "refactor/basic-rules.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_utils.scss" as *;
@use "ds/spacing.scss" as *;
.editable-select {
@extend .asset-element;
margin: 0;
padding: 0;
border: deprecated.$s-1 solid var(--input-border-color);
border: $b-1 solid var(--input-border-color);
position: relative;
display: flex;
height: deprecated.$s-32;
height: $sz-32;
width: 100%;
padding: deprecated.$s-8;
border-radius: deprecated.$br-8;
padding: var(--sp-s);
border-radius: $br-8;
cursor: pointer;
.dropdown-button {
@include deprecated.flexCenter;
display: flex;
place-content: center;
svg {
@extend .button-icon-small;
transform: rotate(90deg);
@@ -29,10 +36,11 @@
.custom-select-dropdown {
@extend .dropdown-wrapper;
max-height: deprecated.$s-320;
width: fit-content;
max-height: px2rem(320); // TODO: when this gets addressed in the DS, use a token
.separator {
margin: 0;
height: deprecated.$s-12;
height: $sz-12;
}
.dropdown-element {
@extend .dropdown-element-base;
@@ -43,7 +51,8 @@
}
.check-icon {
@include deprecated.flexCenter;
display: flex;
place-content: center;
svg {
@extend .button-icon-small;
visibility: hidden;

View File

@@ -53,6 +53,6 @@
(mf/defc inspect-title-bar*
[{:keys [class title]}]
[{:keys [class title title-class]}]
[:div {:class [(stl/css :title-bar) class]}
[:div {:class (stl/css :title-only :inspect-title)} title]])
[:div {:class [title-class (stl/css :title-only :inspect-title)]} title]])

View File

@@ -32,6 +32,27 @@
(def ^:private menu-icon
(deprecated-icon/icon-xref :menu (stl/css :menu-icon)))
(defn- on-restore-project
[project]
(let [on-accept #(st/emit! (dd/restore-project-immediately project))]
(st/emit! (modal/show
{:type :confirm
:title (tr "dashboard.restore-project-confirmation.title")
:message (tr "dashboard.restore-project-confirmation.description" (:name project))
:accept-style :primary
:accept-label (tr "labels.continue")
:on-accept on-accept}))))
(defn- on-delete-project
[project]
(let [accept-fn #(st/emit! (dd/delete-project-immediately project))]
(st/emit! (modal/show
{:type :confirm
:title (tr "dashboard.delete-forever-confirmation.title")
:message (tr "dashboard.delete-project-forever-confirmation.description" (:name project))
:accept-label (tr "dashboard.delete-forever-confirmation.title")
:on-accept accept-fn}))))
(mf/defc header*
{::mf/props :obj
::mf/private true}
@@ -40,7 +61,8 @@
[:div#dashboard-deleted-title {:class (stl/css :dashboard-title)}
[:h1 (tr "dashboard.projects-title")]]])
(mf/defc deleted-project-menu*
(mf/defc project-context-menu*
{::mf/private true}
[{:keys [project show on-close top left]}]
(let [top (d/nilv top 0)
left (d/nilv left 0)
@@ -48,25 +70,13 @@
on-restore-project
(mf/use-fn
(mf/deps project)
(fn []
(let [on-accept #(st/emit! (dd/restore-project-immediately project))]
(st/emit! (modal/show {:type :confirm
:title (tr "dashboard.restore-project-confirmation.title")
:message (tr "dashboard.restore-project-confirmation.description" (:name project))
:accept-style :primary
:accept-label (tr "labels.continue")
:on-accept on-accept})))))
(partial on-restore-project project))
on-delete-project
(mf/use-fn
(mf/deps project)
(fn []
(let [accept-fn #(st/emit! (dd/delete-project-immediately project))]
(st/emit! (modal/show {:type :confirm
:title (tr "dashboard.delete-forever-confirmation.title")
:message (tr "dashboard.delete-project-forever-confirmation.description" (:name project))
:accept-label (tr "dashboard.delete-forever-confirmation.title")
:on-accept accept-fn})))))
(partial on-delete-project project))
options
(mf/with-memo [on-restore-project on-delete-project]
[{:name (tr "dashboard.restore-project-button")
@@ -151,7 +161,7 @@
menu-icon]]
(when (:menu-open @local)
[:> deleted-project-menu*
[:> project-context-menu*
{:project project
:show (:menu-open @local)
:left (+ 24 (:x (:menu-pos @local)))
@@ -174,8 +184,8 @@
:limit limit
:selected-files selected-files}])]]))
(mf/defc menu*
{::mf/private true}
[{:keys [team-id section]}]
(let [on-recent-click
(mf/use-fn
@@ -222,7 +232,8 @@
(some #(= (:id project) (:project-id %))
(vals deleted-map)))))
(sort-by :modified-at)
(reverse)))
(reverse)
(not-empty)))
team-id
(get team :id)
@@ -273,37 +284,44 @@
[:*
[:> header* {:team team}]
[:section {:class (stl/css :dashboard-container :no-bg)}
[:section {:class (stl/css :dashboard-container :no-bg)
:data-testid "deleted-page-section"}
[:*
[:div {:class (stl/css :no-bg)}
[:> menu* {:team-id team-id :section :dashboard-deleted}]
[:div {:class (stl/css :deleted-info-content)}
[:p {:class (stl/css :deleted-info)}
(tr "dashboard.trash-info-text-part1")
[:span {:class (stl/css :info-text-highlight)}
(tr "dashboard.trash-info-text-part2" deletion-days)]
(tr "dashboard.trash-info-text-part3")
[:br]
(tr "dashboard.trash-info-text-part4")]
[:div {:class (stl/css :deleted-options)}
[:> button* {:variant "ghost"
:type "button"
:on-click on-restore-all}
(tr "dashboard.restore-all-deleted-button")]
[:> button* {:variant "destructive"
:type "button"
:icon "delete"
:on-click on-delete-all}
(tr "dashboard.clear-trash-button")]]]
(if (seq projects)
[:*
[:div {:class (stl/css :deleted-info-content)}
[:p {:class (stl/css :deleted-info)}
(tr "dashboard.trash-info-text-part1")
[:span {:class (stl/css :info-text-highlight)}
(tr "dashboard.trash-info-text-part2" deletion-days)]
(tr "dashboard.trash-info-text-part3")
[:br]
(tr "dashboard.trash-info-text-part4")]
[:div {:class (stl/css :deleted-options)}
[:> button* {:variant "ghost"
:type "button"
:on-click on-restore-all}
(tr "dashboard.restore-all-deleted-button")]
[:> button* {:variant "destructive"
:type "button"
:icon "delete"
:on-click on-delete-all}
(tr "dashboard.clear-trash-button")]]]
(when (seq projects)
(for [{:keys [id] :as project} projects]
(let [files (when deleted-map
(->> (vals deleted-map)
(filterv #(= id (:project-id %)))
(sort-by :modified-at #(compare %2 %1))))]
[:> deleted-project-item* {:project project
:files files
:key id}])))]]]]))
(for [{:keys [id] :as project} projects]
(let [files (when deleted-map
(->> (vals deleted-map)
(filterv #(= id (:project-id %)))
(sort-by :modified-at #(compare %2 %1))))]
[:> deleted-project-item* {:project project
:files files
:key id}]))]
;; when no deleted projects
[:div {:class (stl/css :deleted-info-content)}
[:p {:class (stl/css :deleted-info)}
(tr "dashboard.deleted.empty-state-description")]])]]]]))

View File

@@ -6,6 +6,7 @@
(ns app.main.ui.dashboard.file-menu
(:require
[app.common.data :as d]
[app.main.data.common :as dcm]
[app.main.data.dashboard :as dd]
[app.main.data.event :as-alias ev]
@@ -89,12 +90,12 @@
on-duplicate
(fn [_]
(apply st/emit! (map dd/duplicate-file files))
(st/emit! (ntf/success (tr "dashboard.success-duplicate-file" (i18n/c (count files))))))
(st/emit! (ntf/success (tr "dashboard.success-duplicate-file" (i18n/c file-count)))))
on-delete-accept
(fn [_]
(apply st/emit! (map dd/delete-file files))
(st/emit! (ntf/success (tr "dashboard.success-delete-file" (i18n/c (count files))))
(st/emit! (ntf/success (tr "dashboard.success-delete-file" (i18n/c file-count)))
(dd/clear-selected-files)))
on-delete
@@ -193,7 +194,7 @@
(fn [_]
(st/emit! (dd/restore-files-immediately
(with-meta {:team-id (:id current-team)
:ids #{(:id file)}}
:ids (into #{} d/xf:map-id files)}
{:on-success #(st/emit! (ntf/success (tr "dashboard.restore-success-notification" (:name file)))
(dd/fetch-projects (:id current-team))
(dd/fetch-deleted-files (:id current-team)))
@@ -201,6 +202,7 @@
on-restore-immediately
(fn []
(prn files)
(st/emit!
(modal/show {:type :confirm
:title (tr "dashboard-restore-file-confirmation.title")
@@ -213,7 +215,7 @@
(fn []
(let [accept-fn #(st/emit! (dd/delete-files-immediately
{:team-id (:id current-team)
:ids #{(:id file)}}))]
:ids (into #{} d/xf:map-id files)}))]
(st/emit!
(modal/show {:type :confirm
:title (tr "dashboard.delete-forever-confirmation.title")
@@ -260,14 +262,12 @@
options
(if can-restore
[(when can-restore
{:name (tr "dashboard.restore-file-button")
:id "restore-file"
:handler on-restore-immediately})
(when can-restore
{:name (tr "dashboard.delete-file-button")
:id "delete-file"
:handler on-delete-immediately})]
[{:name (tr "dashboard.file-menu.restore-files-option" (i18n/c file-count))
:id "restore-file"
:handler on-restore-immediately}
{:name (tr "dashboard.file-menu.delete-files-permanently-option" (i18n/c file-count))
:id "delete-file"
:handler on-delete-immediately}]
(if multi?
[(when can-edit
{:name (tr "dashboard.duplicate-multi" file-count)

View File

@@ -4,35 +4,36 @@
//
// Copyright (c) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as deprecated;
@use "common/refactor/common-dashboard";
@use "../ds/typography.scss" as t;
@use "../ds/_borders.scss" as *;
@use "../ds/spacing.scss" as *;
@use "../ds/_sizes.scss" as *;
@use "../ds/z-index.scss" as *;
@use "ds/typography.scss" as t;
@use "ds/_borders.scss" as *;
@use "ds/spacing.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/z-index.scss" as *;
@use "ds/mixins.scss" as *;
@use "ds/_utils.scss" as *;
.dashboard-container {
flex: 1 0 0;
width: 100%;
inline-size: 100%;
margin-inline-end: var(--sp-l);
border-top: $b-1 solid var(--panel-border-color);
border-block-start: $b-1 solid var(--panel-border-color);
overflow-y: auto;
padding-bottom: var(--sp-xxxl);
padding-block-end: var(--sp-xxxl);
}
.dashboard-projects {
user-select: none;
height: calc(100vh - deprecated.$s-64);
block-size: calc(100vh - px2rem(64));
}
.with-team-hero {
height: calc(100vh - deprecated.$s-280);
block-size: calc(100vh - px2rem(280));
}
.dashboard-shared {
width: calc(100vw - deprecated.$s-320);
margin-inline-end: deprecated.$s-52;
inline-size: calc(100vw - px2rem(320));
margin-inline-end: px2rem(52);
}
.search {
@@ -66,8 +67,8 @@
align-items: center;
justify-content: space-between;
gap: var(--sp-s);
width: 99%;
max-height: $sz-40;
inline-size: 99%;
max-block-size: $sz-40;
padding: var(--sp-s) var(--sp-s) var(--sp-s) var(--sp-l);
margin-block-start: var(--sp-l);
border-radius: $br-4;
@@ -77,19 +78,19 @@
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
min-height: var(--sp-xxxl);
inline-size: 100%;
min-block-size: $sz-32;
margin-inline-start: var(--sp-s);
}
.project-name {
@include textEllipsis;
@include t.use-typography("body-large");
width: fit-content;
margin-inline-end: var(--sp-m);
line-height: 0.8;
color: var(--title-foreground-color-hover);
cursor: pointer;
height: var(--sp-l);
block-size: $sz-16;
line-height: 0.8;
margin-inline-end: var(--sp-m);
}
.info-wrapper {
@@ -116,8 +117,8 @@
.add-file-btn,
.options-btn {
@extend .button-tertiary;
height: var(--sp-xxxl);
width: var(--sp-xxxl);
block-size: $sz-32;
inline-size: $sz-32;
margin: 0 var(--sp-s);
padding: var(--sp-s);
}
@@ -129,7 +130,7 @@
}
.grid-container {
width: 100%;
inline-size: 100%;
padding: 0 var(--sp-xs);
}
@@ -139,11 +140,13 @@
.show-more {
--show-more-color: var(--button-secondary-foreground-color-rest);
@include deprecated.buttonStyle;
@include t.use-typography("body-medium");
border: none;
background: none;
cursor: pointer;
position: absolute;
top: var(--sp-s);
right: deprecated.$s-52;
inset-block-start: var(--sp-s);
inset-inline-end: px2rem(52);
display: flex;
align-items: center;
justify-content: space-between;
@@ -156,8 +159,8 @@
}
.show-more-icon {
height: var(--sp-l);
width: var(--sp-l);
block-size: $sz-16;
inline-size: $sz-16;
fill: none;
stroke: var(--show-more-color);
}
@@ -165,7 +168,7 @@
// Team hero
.team-hero {
background-color: var(--color-background-tertiary);
border-radius: deprecated.$br-8;
border-radius: $br-8;
border: none;
display: flex;
margin: var(--sp-l);
@@ -174,12 +177,11 @@
img {
border-radius: $br-4;
height: var(--sp-xl) 0;
width: auto;
inline-size: auto;
@media (max-width: 1200px) {
display: none;
width: 0;
inline-size: 0;
}
}
}
@@ -193,9 +195,8 @@
}
.title {
font-size: $sz-24;
color: var(--color-foreground-primary);
font-weight: deprecated.$fw400;
font-size: px2rem(24);
}
.info {
@@ -215,8 +216,8 @@
--close-icon-foreground-color: var(--icon-foreground);
position: absolute;
top: var(--sp-xl);
right: var(--sp-xxl);
width: var(--sp-xxl);
inset-inline-end: var(--sp-xxl);
inline-size: var(--sp-xxl);
background-color: transparent;
border: none;
cursor: pointer;
@@ -231,20 +232,20 @@
}
.invite {
height: var(--sp-xxxl);
width: deprecated.$s-180;
block-size: $sz-32;
inline-size: px2rem(180);
}
.img-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: var(--sp-xl) 0;
height: var(--sp-xl) 0;
inline-size: var(--sp-xl) 0;
block-size: var(--sp-xl) 0;
overflow: hidden;
border-radius: deprecated.$br-4;
border-radius: $br-4;
@media (max-width: 1200px) {
display: none;
width: 0;
inline-size: 0;
}
}

View File

@@ -1,49 +0,0 @@
;; 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 app.main.ui.ds.layers.layer-button
(:require-macros
[app.main.style :as stl])
(:require
[app.main.ui.ds.foundations.assets.icon :as i :refer [icon*]]
[rumext.v2 :as mf]))
(def ^:private schema:layer-button
[:map
[:label :string]
[:description {:optional true} [:maybe :string]]
[:class {:optional true} :string]
[:expandable {:optional true} :boolean]
[:expanded {:optional true} :boolean]
[:icon {:optional true} :string]
[:on-toggle-expand fn?]])
(mf/defc layer-button*
{::mf/schema schema:layer-button}
[{:keys [label description class is-expandable expanded icon on-toggle-expand children] :rest props}]
(let [button-props (mf/spread-props props
{:class [class (stl/css-case :layer-button true
:layer-button--expandable is-expandable
:layer-button--expanded expanded)]
:type "button"
:on-click on-toggle-expand})]
[:div {:class (stl/css :layer-button-wrapper)}
[:> "button" button-props
[:div {:class (stl/css :layer-button-content)}
(when is-expandable
(if expanded
[:> icon* {:icon-id i/arrow-down :class (stl/css :folder-node-icon)}]
[:> icon* {:icon-id i/arrow-right :class (stl/css :folder-node-icon)}]))
(when icon
[:> icon* {:icon-id icon :class (stl/css :layer-button-icon)}])
[:span {:class (stl/css :layer-button-name)}
label]
(when description
[:span {:class (stl/css :layer-button-description)}
description])
[:span {:class (stl/css :layer-button-quantity)}]]]
[:div {:class (stl/css :layer-button-actions)}
children]]))

View File

@@ -1,56 +0,0 @@
// 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
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/typography.scss" as *;
@use "ds/colors.scss" as *;
.layer-button-wrapper {
--layer-button-block-size: #{$sz-32};
--layer-button-background: var(--color-background-primary);
--layer-button-text: var(--color-foreground-secondary);
display: flex;
justify-content: space-between;
block-size: var(--layer-button-block-size);
background: var(--layer-button-background);
color: var(--layer-button-text);
}
.layer-button {
@include use-typography("body-small");
appearance: none;
flex: 1;
display: flex;
align-items: center;
border: none;
background: none;
color: inherit;
}
.layer-button--expanded {
& .layer-button-name {
color: var(--color-foreground-primary);
}
}
.layer-button-content {
display: flex;
align-items: center;
gap: var(--sp-xs);
}
.layer-button-description {
padding: var(--sp-xs);
background-color: var(--color-background-tertiary);
border-radius: $br-6;
}

View File

@@ -121,7 +121,7 @@
[:div {:class (stl/css :modal-container)}
[:div {:class (stl/css :modal-header)}
[:h2 {:class (stl/css :modal-title)}
(tr "dashboard.export.title")]
(tr "files-download-modal.title")]
[:button {:class (stl/css :modal-close-btn)
:on-click on-cancel} deprecated-icon/close]]
@@ -129,8 +129,8 @@
(= status :prepare)
[:*
[:div {:class (stl/css :modal-content)}
[:p {:class (stl/css :modal-msg)} (tr "dashboard.export.explain")]
[:p {:class (stl/css :modal-scd-msg)} (tr "dashboard.export.detail")]
[:p {:class (stl/css :modal-msg)} (tr "files-download-modal.description-1")]
[:p {:class (stl/css :modal-scd-msg)} (tr "files-download-modal.description-2")]
(for [type fexp/valid-types]
[:div {:class (stl/css :export-option true)
@@ -138,20 +138,20 @@
[:label {:for (str "export-" type)
:class (stl/css-case :global/checked (= selected type))}
;; Execution time translation strings:
;; (tr "dashboard.export.options.all.message")
;; (tr "dashboard.export.options.all.title")
;; (tr "dashboard.export.options.detach.message")
;; (tr "dashboard.export.options.detach.title")
;; (tr "dashboard.export.options.merge.message")
;; (tr "dashboard.export.options.merge.title")
;; (tr "files-download-modal.options.all.message")
;; (tr "files-download-modal.options.all.title")
;; (tr "files-download-modal.options.detach.message")
;; (tr "files-download-modal.options.detach.title")
;; (tr "files-download-modal.options.merge.message")
;; (tr "files-download-modal.options.merge.title")
[:span {:class (stl/css-case :global/checked (= selected type))}
(when (= selected type)
deprecated-icon/status-tick)]
[:div {:class (stl/css :option-content)}
[:h3 {:class (stl/css :modal-subtitle)}
(tr (dm/str "dashboard.export.options." (d/name type) ".title"))]
(tr (dm/str "files-download-modal.options." (d/name type) ".title"))]
[:p {:class (stl/css :modal-msg)}
(tr (dm/str "dashboard.export.options." (d/name type) ".message"))]]
(tr (dm/str "files-download-modal.options." (d/name type) ".message"))]]
[:input {:type "radio"
:class (stl/css :option-input)

View File

@@ -12,14 +12,17 @@
flex-direction: column;
gap: var(--sp-l);
width: 100%;
height: calc(100vh - px2rem(128)); // TODO: Fix this hardcoded value
max-height: calc(100vh - px2rem(128)); // TODO: Fix this hardcoded value
padding-top: var(--sp-s);
padding-inline: var(--sp-m);
overflow-y: auto;
overflow-x: hidden;
scrollbar-gutter: stable;
background-color: var(--low-emphasis-background);
}
.workspace-element-options {
height: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value
max-height: calc(100vh - px2rem(180)); // TODO: Fix this hardcoded value
padding-inline: var(--sp-m);
background-color: var(--low-emphasis-background);
}

View File

@@ -24,7 +24,8 @@
[:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar*
{:title (tr "inspect.attributes.blur")
:class (stl/css :title-spacing-blur)}
:class (stl/css :title-wrapper)
:title-class (stl/css :blur-attr-title)}
(when (= (count shapes) 1)
[:> copy-button* {:data (css/get-css-property objects (first shapes) :filter)
:class (stl/css :copy-btn-title)}])]

View File

@@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-blur {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.blur-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.blur-row {
@extend .attr-row;
block-size: $sz-36;
}
.button-children {
@@ -23,5 +34,5 @@
}
.copy-btn-title {
max-width: deprecated.$s-28;
max-inline-size: $sz-28;
}

View File

@@ -68,7 +68,8 @@
[:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar*
{:title (tr "inspect.attributes.fill")
:class (stl/css :title-spacing-fill)}]
:class (stl/css :title-wrapper)
:class-title (stl/css :fill-attr-title)}]
[:div {:class (stl/css :attributes-content)}
(for [shape shapes]

View File

@@ -5,16 +5,30 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-fill {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.fill-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.attributes-content {
display: grid;
gap: deprecated.$s-4;
}
.attributes-fill-block {
block-size: $sz-36;
}

View File

@@ -44,7 +44,8 @@
[:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar*
{:title (tr "inspect.attributes.size")
:class (stl/css :title-spacing-geometry)}
:class (stl/css :title-wrapper)
:title-class (stl/css :geometry-attr-title)}
(when (= (count shapes) 1)
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)

View File

@@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-geometry {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.geometry-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.geometry-row {
@extend .attr-row;
block-size: $sz-36;
}
.button-children {
@@ -23,5 +34,5 @@
}
.copy-btn-title {
max-width: deprecated.$s-28;
max-inline-size: $sz-28;
}

View File

@@ -57,7 +57,8 @@
[:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar*
{:title "Layout"
:class (stl/css :title-spacing-layout)}
:class (stl/css :title-wrapper)
:title-class (stl/css :layout-attr-title)}
(when (= (count shapes) 1)
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)

View File

@@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-layout {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.layout-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.layout-row {
@extend .attr-row;
block-size: $sz-36;
}
.button-children {
@@ -23,5 +34,5 @@
}
.copy-btn-title {
max-width: deprecated.$s-28;
max-inline-size: $sz-28;
}

View File

@@ -69,7 +69,8 @@
[:div {:class (stl/css :attributes-block)}
[:> title-bar* {:collapsable false
:title menu-title
:class (stl/css :title-spacing-layout-element)}
:class (stl/css :title-wrapper)
:title-class (stl/css :layout-element-attr-title)}
(when (= (count shapes) 1)
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)
:class (stl/css :copy-btn-title)}])]

View File

@@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-layout-element {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.layout-element-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.layout-element-row {
@extend .attr-row;
block-size: $sz-36;
}
.button-children {
@@ -23,5 +34,6 @@
}
.copy-btn-title {
max-width: deprecated.$s-28;
max-inline-size: $sz-28;
max-inline-size: $sz-28;
}

View File

@@ -63,7 +63,8 @@
[:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar*
{:title (tr "inspect.attributes.shadow")
:class (stl/css :title-spacing-shadow)}]
:class (stl/css :title-wrapper)
:title-class (stl/css :shadow-attr-title)}]
[:div {:class (stl/css :attributes-content)}
(for [shape shapes]

View File

@@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-shadow {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.shadow-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.shadow-row {
@extend .attr-row;
block-size: $sz-36;
}
.button-children {

View File

@@ -88,7 +88,8 @@
[:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar*
{:title (tr "inspect.attributes.stroke")
:class (stl/css :title-spacing-stroke)}]
:class (stl/css :title-wrapper)
:title-class (stl/css :stroke-attr-title)}]
[:div {:class (stl/css :attributes-content)}
(for [shape shapes]

View File

@@ -5,21 +5,34 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-stroke {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.stroke-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.attributes-stroke-block {
@include deprecated.flexColumn;
display: flex;
flex-direction: column;
gap: var(--sp-xs);
}
.stroke-row {
@extend .attr-row;
block-size: $sz-36;
}
.button-children {
@@ -28,5 +41,5 @@
.attributes-content {
display: grid;
gap: deprecated.$s-4;
gap: var(--sp-xs);
}

View File

@@ -54,5 +54,6 @@
[:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar*
{:title (tr "workspace.sidebar.options.svg-attrs.title")
:class (stl/css :title-spacing-svg)}]
:class (stl/css :title-wrapper)
:title-class (stl/css :svg-attr-title)}]
[:& svg-block {:shape shape}]])))

View File

@@ -5,17 +5,29 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/typography.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-svg {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.svg-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.svg-row {
@extend .attr-row;
block-size: $sz-36;
}
.button-children {
@@ -23,12 +35,12 @@
}
.attributes-subtitle {
@include deprecated.uppercaseTitleTipography;
@include use-typography("headline-small");
display: flex;
justify-content: space-between;
height: deprecated.$s-32;
block-size: $sz-32;
span {
height: deprecated.$s-32;
block-size: $sz-32;
display: flex;
align-items: center;
}

View File

@@ -157,7 +157,8 @@
[:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar*
{:title (tr "inspect.attributes.typography")
:class (stl/css :title-spacing-text)}]
:class (stl/css :title-wrapper)
:title-class (stl/css :text-atrr-title)}]
(for [shape shapes]
[:& text-block {:shape shape

View File

@@ -5,23 +5,37 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
@use "ds/_utils.scss" as *;
@use "ds/typography.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-text {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.text-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.attributes-content {
@include deprecated.flexColumn;
display: flex;
flex-direction: column;
gap: var(--sp-xs);
}
.text-row {
@extend .attr-row;
height: unset;
min-height: deprecated.$s-32;
block-size: unset;
min-block-size: $sz-36;
:global(.attr-value) {
align-items: center;
}
@@ -32,20 +46,20 @@
}
.attributes-content-row {
max-width: deprecated.$s-240;
min-height: calc(deprecated.$s-2 + deprecated.$s-32);
border-radius: deprecated.$br-8;
border: deprecated.$s-1 solid var(--menu-border-color-disabled);
margin-top: deprecated.$s-4;
max-inline-size: px2rem(240);
min-block-size: px2rem(34);
border-radius: $br-8;
border: $b-1 solid var(--menu-border-color-disabled);
margin-block-start: var(--sp-xs);
.content {
@include deprecated.bodySmallTypography;
@include use-typography("body-small");
width: 100%;
padding: deprecated.$s-4 0;
padding: var(--sp-xs) 0;
color: var(--color-foreground-secondary);
}
&:hover {
border: deprecated.$s-1 solid var(--color-background-tertiary);
border: $b-1 solid var(--color-background-tertiary);
background-color: var(--menu-background-color);
.content {
color: var(--menu-foreground-color-hover);

View File

@@ -42,7 +42,8 @@
[:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar*
{:title (if is-container? (tr "inspect.attributes.variants") (tr "inspect.attributes.variant"))
:class (stl/css :title-spacing-variant)}]
:class (stl/css :title-wrapper)
:title-class (stl/css :variant-attr-title)}]
(for [[pos property] (map-indexed vector properties)]
[:> variant-block* {:key (dm/str "variant-property-" pos) :name (:name property) :value (:value property)}])]))

View File

@@ -5,18 +5,29 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-variant {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.variant-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.variant-row {
@extend .attr-row;
height: fit-content;
block-size: fit-content;
min-block-size: $sz-36;
}
.button-children {

View File

@@ -51,7 +51,8 @@
[:div {:class (stl/css :attributes-block)}
[:> inspect-title-bar*
{:title "Visibility"
:class (stl/css :title-spacing-visibility)}
:class (stl/css :title-wrapper)
:title-class (stl/css :visibility-attr-title)}
(when (= (count shapes) 1)
[:> copy-button* {:data (css/get-shape-properties-css objects (first shapes) properties)

View File

@@ -5,17 +5,28 @@
// Copyright (c) KALEIDOS INC
@use "refactor/common-refactor.scss" as deprecated;
@use "ds/_borders.scss" as *;
@use "ds/_sizes.scss" as *;
.attributes-block {
@include deprecated.flexColumn;
--box-border-color: var(--color-background-primary);
display: flex;
flex-direction: column;
border-block-end: $b-2 solid var(--box-border-color);
}
.title-spacing-visibility {
@extend .attr-title;
.title-wrapper {
margin-inline-start: 0;
}
.visibility-attr-title {
color: var(--entry-foreground-color-hover);
padding-block: var(--sp-s);
}
.visibility-row {
@extend .attr-row;
block-size: $sz-36;
}
.button-children {
@@ -23,5 +34,5 @@
}
.copy-btn-title {
max-width: deprecated.$s-28;
max-inline-size: $sz-28;
}

View File

@@ -186,6 +186,7 @@
[:> styles-tab* {:color-space color-space
:objects objects
:shapes shapes
:from from
:libraries libraries
:file-id file-id}]
:computed

View File

@@ -24,10 +24,6 @@
}
}
.viewer-code {
padding-inline-start: var(--sp-s);
}
.tool-windows {
block-size: 100%;
display: grid;

View File

@@ -90,7 +90,7 @@
:multiple))
(mf/defc styles-tab*
[{:keys [color-space shapes libraries objects file-id]}]
[{:keys [color-space shapes libraries objects file-id from]}]
(let [data (dm/get-in libraries [file-id :data])
first-shape (first shapes)
first-component (ctkl/get-component data (:component-id first-shape))
@@ -131,7 +131,8 @@
(mf/deps shorthands*)
(fn [shorthand]
(swap! shorthands* assoc (:panel shorthand) (:property shorthand))))]
[:ol {:class (stl/css :styles-tab) :aria-label (tr "labels.styles")}
[:ol {:class (stl/css-case :styles-tab true
:styles-tab-workspace (= from :workspace)) :aria-label (tr "labels.styles")}
;; TOKENS PANEL
(when (or (seq active-themes) (seq active-sets))
[:li

View File

@@ -7,5 +7,9 @@
@use "ds/_utils.scss" as *;
.styles-tab {
block-size: calc(100vh - px2rem(200)); // TODO: Fix this hardcoded value
block-size: calc(100vh - px2rem(140)); // TODO: Fix this hardcoded value
}
.styles-tab-workspace {
block-size: calc(100vh - px2rem(180)); // TODO: Fix this hardcoded value
}

View File

@@ -60,6 +60,7 @@
}
.property-detail-text {
@include use-typography("body-small");
color: var(--detail-color);
}

View File

@@ -19,7 +19,7 @@
(case type
:variant (tr "inspect.tabs.styles.variants-panel")
:token (tr "inspect.tabs.styles.token-panel")
:geometry (tr "inspect.tabs.styles.geometry-panel")
:geometry (tr "inspect.attributes.size")
:fill (tr "labels.fill")
:stroke (tr "labels.stroke")
:text (tr "labels.text")

View File

@@ -33,7 +33,6 @@
display: flex;
align-items: center;
gap: var(--title-gap);
padding-block: var(--title-padding);
}
.disclosure-button {
@@ -52,4 +51,5 @@
@include use-typography("headline-small");
flex: 1;
color: var(--title-color);
padding-block: var(--title-padding);
}

View File

@@ -148,11 +148,12 @@
(mf/use-fn
(mf/deps index prefix is-move)
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
(when (dom/left-mouse? event)
(dom/stop-propagation event)
(dom/prevent-default event)
(when ^boolean is-move
(st/emit! (drp/start-move-handler index prefix)))))]
(when ^boolean is-move
(st/emit! (drp/start-move-handler index prefix))))))]
[:g.handler {:pointer-events (if ^boolean is-draw "none" "visible")}
[:line

View File

@@ -54,7 +54,7 @@
modifiers (dm/get-in modifiers [shape-id :modifiers])
shape (gsh/transform-shape shape modifiers)
props (mf/spread-props props {:shape shape :file-id file-id :page-id page-id})]
props (mf/spread-props props {:shape shape :file-id file-id :page-id page-id :libraries libraries})]
(case shape-type
:frame [:> frame/options* props]

View File

@@ -44,39 +44,6 @@
[(seq (array/sort! empty))
(seq (array/sort! filled))]))))
(mf/defc selected-set-info*
{::mf/private true}
[{:keys [tokens-lib selected-token-set-id]}]
(let [selected-token-set
(mf/with-memo [tokens-lib]
(when selected-token-set-id
(some-> tokens-lib (ctob/get-set selected-token-set-id))))
active-token-sets-names
(mf/with-memo [tokens-lib]
(some-> tokens-lib (ctob/get-active-themes-set-names)))
token-set-active?
(mf/use-fn
(mf/deps active-token-sets-names)
(fn [name]
(contains? active-token-sets-names name)))]
[:div {:class (stl/css :sets-header-container)}
[:> text* {:as "span"
:typography "headline-small"
:class (stl/css :sets-header)}
(tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))]
[:div {:class (stl/css :sets-header-status) :title (tr "workspace.tokens.inactive-set-description")}
;; NOTE: when no set in tokens-lib, the selected-token-set-id
;; will be `nil`, so for properly hide the inactive message we
;; check that at least `selected-token-set-id` has a value
(when (and (some? selected-token-set-id)
(not (token-set-active? (ctob/get-name selected-token-set))))
[:*
[:> icon* {:class (stl/css :sets-header-status-icon) :icon-id i/eye-off}]
[:> text* {:as "span" :typography "body-small" :class (stl/css :sets-header-status-text)}
(tr "workspace.tokens.inactive-set")]])]]))
(mf/defc tokens-section*
{::mf/private true}
[{:keys [tokens-lib active-tokens resolved-active-tokens]}]
@@ -98,7 +65,9 @@
selected-token-set-id
(mf/deref refs/selected-token-set-id)
selected-token-set
(when selected-token-set-id
(some-> tokens-lib (ctob/get-set selected-token-set-id)))
;; If we have not selected any set explicitly we just
;; select the first one from the list of sets
@@ -123,9 +92,15 @@
tokens)]
(ctob/group-by-type tokens)))
active-token-sets-names
(mf/with-memo [tokens-lib]
(some-> tokens-lib (ctob/get-active-themes-set-names)))
token-set-active?
(mf/use-fn
(mf/deps active-token-sets-names)
(fn [name]
(contains? active-token-sets-names name)))
[empty-group filled-group]
(mf/with-memo [tokens-by-type]
@@ -143,27 +118,34 @@
[:*
[:& token-context-menu]
[:& selected-set-info* {:tokens-lib tokens-lib
:selected-token-set-id selected-token-set-id}]
[:div {:class (stl/css :sets-header-container)}
[:> text* {:as "span" :typography "headline-small" :class (stl/css :sets-header)} (tr "workspace.tokens.tokens-section-title" (ctob/get-name selected-token-set))]
[:div {:class (stl/css :sets-header-status) :title (tr "workspace.tokens.inactive-set-description")}
;; NOTE: when no set in tokens-lib, the selected-token-set-id
;; will be `nil`, so for properly hide the inactive message we
;; check that at least `selected-token-set-id` has a value
(when (and (some? selected-token-set-id)
(not (token-set-active? (ctob/get-name selected-token-set))))
[:*
[:> icon* {:class (stl/css :sets-header-status-icon) :icon-id i/eye-off}]
[:> text* {:as "span" :typography "body-small" :class (stl/css :sets-header-status-text)}
(tr "workspace.tokens.inactive-set")]])]]
(for [type filled-group]
(let [tokens (get tokens-by-type type)]
[:> token-group* {:key (name type)
:tokens tokens
:is-expanded (get open-status type false)
:is-open (get open-status type false)
:type type
:selected-ids selected
:selected-shapes selected-shapes
:is-selected-inside-layout is-selected-inside-layout
:active-theme-tokens resolved-active-tokens
:tokens-lib tokens-lib
:selected-token-set-id selected-token-set-id}]))
:tokens tokens}]))
(for [type empty-group]
[:> token-group* {:key (name type)
:tokens []
:type type
:selected-shapes selected-shapes
:is-selected-inside-layout is-selected-inside-layout
:active-theme-tokens resolved-active-tokens}])]))
:is-selected-inside-layout :is-selected-inside-layout
:active-theme-tokens resolved-active-tokens
:tokens []}])]))

View File

@@ -7,6 +7,7 @@
(ns app.main.ui.workspace.tokens.management.forms.controls.input
(:require
[app.common.data :as d]
[app.common.files.tokens :as cft]
[app.common.types.tokens-lib :as ctob]
[app.main.data.style-dictionary :as sd]
[app.main.data.workspace.tokens.format :as dwtf]
@@ -292,7 +293,7 @@
(mf/spread-props props {:hint-formated true})
props)]
(mf/with-effect [resolve-stream tokens token input-name]
(mf/with-effect [resolve-stream tokens token input-name name]
(let [subs (->> resolve-stream
(rx/debounce 300)
(rx/mapcat (partial resolve-value tokens token))
@@ -317,11 +318,21 @@
(reset! hint* {:message error' :type "error"}))
:else
(let [message (tr "workspace.tokens.resolved-value" (dwtf/format-token-value value))
input-value (get-in @form [:data :value input-name] "")]
(let [input-value (get-in @form [:data :value input-name] "")
resolved-value (if (= name :line-height)
(when-let [{:keys [unit value]} (cft/parse-token-value input-value)]
(let [font-size (get-in @form [:data :value :font-size] "")
calculated (case unit
"%" (/ (d/parse-double value) 100)
"px" (/ (d/parse-double value) (d/parse-double font-size))
nil value
nil)]
(dwtf/format-token-value calculated)))
(dwtf/format-token-value value))
message (tr "workspace.tokens.resolved-value" (or resolved-value value))]
(swap! form update :errors dissoc :value)
(swap! form update :extra-errors dissoc :value)
(if (= input-value (str value))
(if (= input-value (str resolved-value))
(reset! hint* {})
(reset! hint* {:message message :type "hint"})))))))]
(fn []
@@ -349,6 +360,7 @@
(let [form (mf/use-ctx fc/context)
input-name name
error
(get-in @form [:errors :value value-subfield index input-name])

View File

@@ -35,8 +35,8 @@
on-change
(mf/use-fn
(mf/deps input-name)
(fn [type]
(let [is-inner? (= type "inner")]
(fn [id]
(let [is-inner? (= id "inner")]
(swap! form assoc-in [:data :value indexed-type index input-name] is-inner?))))
props (mf/spread-props props {:default-selected (if value "inner" "drop")

View File

@@ -76,7 +76,7 @@
[token index prop value-subfield]
(let [value (get-in token [:value value-subfield index prop])]
(d/without-nils
{:type (if (= prop :color) :color :number)
{:type (if (= prop :color) :color :dimensions)
:value value})))
(mf/defc shadow-formset*
@@ -114,7 +114,7 @@
:token inset-token
:tokens tokens
:index index
:value-subfield value-subfield
:indexed-type value-subfield
:name :inset}]
(when show-button
[:> icon-button* {:variant "ghost"
@@ -269,13 +269,25 @@
[:value
[:map
[:shadow {:optinal true}
[:shadow {:optional true}
[:vector
[:map
[:offset-x {:optional true} [:maybe :string]]
[:offset-y {:optional true} [:maybe :string]]
[:blur {:optional true} [:maybe :string]]
[:spread {:optional true} [:maybe :string]]
[:blur {:optional true}
[:and
[:maybe :string]
[:fn {:error/fn #(tr "workspace.tokens.shadow-token-blur-value-error")}
(fn [blur]
(let [n (d/parse-double blur)]
(or (nil? n) (not (< n 0)))))]]]
[:spread {:optional true}
[:and
[:maybe :string]
[:fn {:error/fn #(tr "workspace.tokens.shadow-token-spread-value-error")}
(fn [spread]
(let [n (d/parse-double spread)]
(or (nil? n) (not (< n 0)))))]]]
[:color {:optional true} [:maybe :string]]
[:color-result {:optional true} ::sm/any]
[:inset {:optional true} [:maybe :boolean]]]]]

View File

@@ -93,9 +93,9 @@
line-height-sub-token
(mf/with-memo [token]
(if-let [value (get token :value)]
{:type :number
{:type :dimensions
:value (get value :line-height)}
{:type :number}))
{:type :dimensions}))
text-case-sub-token
(mf/with-memo [token]

View File

@@ -8,9 +8,6 @@
(ns app.main.ui.workspace.tokens.management.group
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.types.tokens-lib :as ctob]
[app.main.data.modal :as modal]
[app.main.data.workspace.tokens.application :as dwta]
[app.main.data.workspace.tokens.library-edit :as dwtl]
@@ -19,70 +16,51 @@
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.ds.layers.layer-button :refer [layer-button*]]
[app.main.ui.workspace.tokens.management.token-tree :refer [token-tree*]]
[app.main.ui.workspace.sidebar.assets.common :as cmm]
[app.main.ui.workspace.tokens.management.token-pill :refer [token-pill*]]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(defn token-section-icon
[type]
(case type
:border-radius i/corner-radius
:color i/drop
:boolean i/boolean-difference
:font-family i/text-font-family
:font-size i/text-font-size
:letter-spacing i/text-letterspacing
:text-case i/text-mixed
:text-decoration i/text-underlined
:font-weight i/text-font-weight
:typography i/text-typography
:opacity i/percentage
:number i/number
:rotation i/rotation
:spacing i/padding-extended
:string i/text-mixed
:stroke-width i/stroke-size
:dimensions i/expand
:sizing i/expand
:shadow i/drop-shadow
:border-radius "corner-radius"
:color "drop"
:boolean "boolean-difference"
:font-family "text-font-family"
:font-size "text-font-size"
:letter-spacing "text-letterspacing"
:text-case "text-mixed"
:text-decoration "text-underlined"
:font-weight "text-font-weight"
:typography "text-typography"
:opacity "percentage"
:number "number"
:rotation "rotation"
:spacing "padding-extended"
:string "text-mixed"
:stroke-width "stroke-size"
:dimensions "expand"
:sizing "expand"
:shadow "drop-shadow"
"add"))
(def ^:private schema:token-group
[:map
[:type :keyword]
[:tokens :any]
[:selected-shapes :any]
[:is-selected-inside-layout {:optional true} [:maybe :boolean]]
[:active-theme-tokens {:optional true} :any]
[:selected-token-set-id {:optional true} :any]
[:tokens-lib {:optional true} :any]
[:on-token-pill-click {:optional true} fn?]
[:on-context-menu {:optional true} fn?]])
(mf/defc token-group*
{::mf/schema schema:token-group}
[{:keys [type tokens selected-shapes is-selected-inside-layout active-theme-tokens selected-token-set-id tokens-lib is-expanded selected-ids]}]
{::mf/private true}
[{:keys [type tokens selected-shapes is-selected-inside-layout active-theme-tokens is-open selected-ids]}]
(let [{:keys [modal title]}
(get dwta/token-properties type)
editing-ref (mf/deref refs/workspace-editor-state)
not-editing? (empty? editing-ref)
is-expanded (d/nilv is-expanded false)
can-edit?
(mf/use-ctx ctx/can-edit?)
is-selected-inside-layout (d/nilv is-selected-inside-layout false)
tokens
(mf/with-memo [tokens]
(vec (sort-by :name tokens)))
expandable? (d/nilv (seq tokens) false)
on-context-menu
(mf/use-fn
(fn [event token]
@@ -95,8 +73,8 @@
on-toggle-open-click
(mf/use-fn
(mf/deps is-expanded type)
#(st/emit! (dwtl/set-token-type-section-open type (not is-expanded))))
(mf/deps is-open type)
#(st/emit! (dwtl/set-token-type-section-open type (not is-open))))
on-popover-open-click
(mf/use-fn
@@ -118,36 +96,33 @@
(mf/use-fn
(mf/deps not-editing? selected-ids)
(fn [event token]
(let [token (ctob/get-token tokens-lib selected-token-set-id (:id token))]
(dom/stop-propagation event)
(when (and not-editing? (seq selected-shapes) (not= (:type token) :number))
(st/emit! (dwta/toggle-token {:token token
:shape-ids selected-ids}))))))]
(dom/stop-propagation event)
(when (and not-editing? (seq selected-shapes) (not= (:type token) :number))
(st/emit! (dwta/toggle-token {:token token
:shape-ids selected-ids})))))]
[:div {:class (stl/css :token-section-wrapper)
:data-testid (dm/str "section-" (name type))}
[:> layer-button* {:label title
:expanded is-expanded
:description (when expandable? (dm/str (count tokens)))
:is-expandable expandable?
:aria-expanded is-expanded
:aria-controls (dm/str "token-tree-" (name type))
:on-toggle-expand on-toggle-open-click
:icon (token-section-icon type)}
(when can-edit?
[:> icon-button* {:id (str "add-token-button-" title)
:icon "add"
:aria-label (tr "workspace.tokens.add-token" title)
:variant "ghost"
:on-click on-popover-open-click
:class (stl/css :token-section-icon)}])]
(when is-expanded
[:> token-tree* {:tokens tokens
:id (dm/str "token-tree-" (name type))
:tokens-lib tokens-lib
:selected-shapes selected-shapes
:active-theme-tokens active-theme-tokens
:selected-token-set-id selected-token-set-id
:is-selected-inside-layout is-selected-inside-layout
:on-token-pill-click on-token-pill-click
:on-context-menu on-context-menu}])]))
[:div {:on-click on-toggle-open-click :class (stl/css :token-section-wrapper)}
[:> cmm/asset-section* {:icon (token-section-icon type)
:title title
:section :tokens
:assets-count (count tokens)
:is-open is-open}
[:> cmm/asset-section-block* {:role :title-button}
(when can-edit?
[:> icon-button* {:on-click on-popover-open-click
:variant "ghost"
:icon i/add
:id (str "add-token-button-" title)
:aria-label (tr "workspace.tokens.add-token" title)}])]
(when is-open
[:> cmm/asset-section-block* {:role :content}
[:div {:class (stl/css :token-pills-wrapper)}
(for [token tokens]
[:> token-pill*
{:key (:name token)
:token token
:selected-shapes selected-shapes
:is-selected-inside-layout is-selected-inside-layout
:active-theme-tokens active-theme-tokens
:on-click on-token-pill-click
:on-context-menu on-context-menu}])]])]]))

View File

@@ -0,0 +1,11 @@
// 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
.token-pills-wrapper {
display: flex;
gap: var(--sp-xs);
flex-wrap: wrap;
}

View File

@@ -307,9 +307,10 @@
:class (stl/css :token-pill-icon)}])
(if contains-path?
(let [[_ last-part] (cpn/split-by-last-period name)]
(let [[first-part last-part] (cpn/split-by-last-period name)]
[:span {:class (stl/css :divided-name-wrapper)
:aria-label name}
[:span {:class (stl/css :first-name-wrapper)} first-part]
[:span {:class (stl/css :last-name-wrapper)} last-part]])
[:span {:class (stl/css :name-wrapper)
:aria-label name}

View File

@@ -1,110 +0,0 @@
;; 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 app.main.ui.workspace.tokens.management.token-tree
(:require-macros [app.main.style :as stl])
(:require
[app.common.path-names :as cpn]
[app.common.types.tokens-lib :as ctob]
[app.main.ui.ds.layers.layer-button :refer [layer-button*]]
[app.main.ui.workspace.tokens.management.token-pill :refer [token-pill*]]
[rumext.v2 :as mf]))
(def ^:private schema:folder-node
[:map
[:node :any]
[:selected-shapes :any]
[:is-selected-inside-layout {:optional true} :boolean]
[:active-theme-tokens {:optional true} :any]
[:selected-token-set-id {:optional true} :any]
[:tokens-lib {:optional true} :any]
[:on-token-pill-click {:optional true} fn?]
[:on-context-menu {:optional true} fn?]])
(mf/defc folder-node*
{::mf/schema schema:folder-node}
[{:keys [node selected-shapes is-selected-inside-layout active-theme-tokens selected-token-set-id tokens-lib on-token-pill-click on-context-menu]}]
(let [expanded* (mf/use-state false)
expanded (deref expanded*)
swap-folder-expanded #(swap! expanded* not)]
[:li {:class (stl/css :folder-node)}
[:> layer-button* {:label (:name node)
:expanded expanded
:aria-expanded expanded
:aria-controls (str "folder-children-" (:path node))
:is-expandable (not (:leaf node))
:on-toggle-expand swap-folder-expanded}]
(when expanded
(let [children-fn (:children-fn node)]
[:div {:class (stl/css :folder-children-wrapper)
:id (str "folder-children-" (:path node))}
(when children-fn
(let [children (children-fn)]
(for [child children]
(if (not (:leaf child))
[:ul {:class (stl/css :node-parent)}
[:> folder-node* {:key (:path child)
:node child
:selected-shapes selected-shapes
:is-selected-inside-layout is-selected-inside-layout
:active-theme-tokens active-theme-tokens
:on-token-pill-click on-token-pill-click
:on-context-menu on-context-menu
:tokens-lib tokens-lib
:selected-token-set-id selected-token-set-id}]]
(let [id (:id (:leaf child))
token (ctob/get-token tokens-lib selected-token-set-id id)]
[:> token-pill*
{:key id
:token token
:selected-shapes selected-shapes
:is-selected-inside-layout is-selected-inside-layout
:active-theme-tokens active-theme-tokens
:on-click on-token-pill-click
:on-context-menu on-context-menu}])))))]))]))
(def ^:private schema:token-tree
[:map
[:tokens :any]
[:selected-shapes :any]
[:is-selected-inside-layout {:optional true} :boolean]
[:active-theme-tokens {:optional true} :any]
[:selected-token-set-id {:optional true} :any]
[:tokens-lib {:optional true} :any]
[:on-token-pill-click {:optional true} fn?]
[:on-context-menu {:optional true} fn?]])
(mf/defc token-tree*
{::mf/schema schema:token-tree}
[{:keys [tokens selected-shapes is-selected-inside-layout active-theme-tokens tokens-lib selected-token-set-id on-token-pill-click on-context-menu]}]
(let [separator "."
tree (mf/use-memo
(mf/deps tokens)
(fn []
(cpn/build-tree-root tokens separator)))]
[:div {:class (stl/css :token-tree-wrapper)}
(for [node tree]
[:ul {:class (stl/css :node-parent)
:key (:path node)
:style {:--node-depth (inc (:depth node))}}
(if (:leaf node)
(let [token (ctob/get-token tokens-lib selected-token-set-id (get-in node [:leaf :id]))]
[:> token-pill*
{:token token
:selected-shapes selected-shapes
:is-selected-inside-layout is-selected-inside-layout
:active-theme-tokens active-theme-tokens
:on-click on-token-pill-click
:on-context-menu on-context-menu}])
;; Render segment folder
[:> folder-node* {:node node
:selected-shapes selected-shapes
:is-selected-inside-layout is-selected-inside-layout
:active-theme-tokens active-theme-tokens
:on-token-pill-click on-token-pill-click
:on-context-menu on-context-menu
:tokens-lib tokens-lib
:selected-token-set-id selected-token-set-id}])])]))

View File

@@ -1,39 +0,0 @@
// 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
@use "ds/_borders.scss" as *;
.token-tree-wrapper {
padding-block-end: var(--sp-s);
}
.node-parent {
--node-spacing: var(--sp-l);
--node-depth: 0;
margin-block-end: 0;
padding-inline-start: calc(var(--node-spacing) * var(--node-depth));
}
.folder-children-wrapper:has(> button) {
margin-inline-start: var(--sp-s);
padding-inline-start: var(--sp-s);
border-inline-start: $b-2 solid var(--color-background-quaternary);
display: flex;
flex-wrap: wrap;
column-gap: var(--sp-xs);
& .node-parent {
flex: 1 0 100%;
&:last-of-type {
margin-block-end: var(--sp-s);
}
}
& .token-pill {
flex: 0 0 auto;
}
}

View File

@@ -40,6 +40,7 @@
;; FIXME: rename; confunsing name
[app.render-wasm.wasm :as wasm]
[app.util.debug :as dbg]
[app.util.dom :as dom]
[app.util.functions :as fns]
[app.util.globals :as ug]
[app.util.modules :as mod]
@@ -1230,6 +1231,13 @@
(re-find #"(?i)edge" user-agent) :edge
:else :unknown)))))
(defn- on-webgl-context-lost
[event]
(dom/prevent-default event)
(reset! wasm/context-lost? true)
(log/warn :hint "WebGL context lost")
(st/emit! (drw/context-lost)))
(defn init-canvas-context
[canvas]
(let [gl (unchecked-get wasm/internal-module "GL")
@@ -1257,14 +1265,8 @@
(set-canvas-size canvas)
;; Add event listeners for WebGL context lost
(let [handler (fn [event]
(.preventDefault event)
(reset! wasm/context-lost? true)
(log/warn :hint "WebGL context lost")
(st/emit! (drw/context-lost)))]
(set! wasm/context-lost-handler handler)
(set! wasm/context-lost-canvas canvas)
(.addEventListener canvas "webglcontextlost" handler))
(set! wasm/canvas canvas)
(.addEventListener canvas "webglcontextlost" on-webgl-context-lost)
(set! wasm/context-initialized? true)))
context-init?))
@@ -1278,10 +1280,9 @@
(h/call wasm/internal-module "_clean_up")
;; Remove event listener for WebGL context lost
(when (and wasm/context-lost-handler wasm/context-lost-canvas)
(.removeEventListener wasm/context-lost-canvas "webglcontextlost" wasm/context-lost-handler)
(set! wasm/context-lost-canvas nil)
(set! wasm/context-lost-handler nil))
(when wasm/canvas
(.removeEventListener wasm/canvas "webglcontextlost" on-webgl-context-lost)
(set! wasm/canvas nil))
;; Ensure the WebGL context is properly disposed so browsers do not keep
;; accumulating active contexts between page switches.

View File

@@ -9,8 +9,20 @@
(defonce internal-frame-id nil)
(defonce internal-module #js {})
;; Reference to the HTML canvas element.
(defonce canvas nil)
;; Reference to the Emscripten GL context wrapper.
(defonce gl-context-handle nil)
;; Reference to the actual WebGL Context returned
;; by the `.getContext` method of the canvas.
(defonce gl-context nil)
(defonce context-initialized? false)
(defonce context-lost? (atom false))
(defonce serializers
#js {:blur-type shared/RawBlurType
:blend-mode shared/RawBlendMode
@@ -44,8 +56,3 @@
:stroke-linecap shared/RawStrokeLineCap
:stroke-linejoin shared/RawStrokeLineJoin
:fill-rule shared/RawFillRule})
(defonce context-initialized? false)
(defonce context-lost? (atom false))
(defonce context-lost-handler nil)
(defonce context-lost-canvas nil)

View File

@@ -199,7 +199,7 @@
:string-or-size-array (format-string-or-size-array value)
:keyword (format-keyword value)
:tracks (format-tracks value)
:shadow (format-shadow value options)
:shadows (format-shadow value options)
:blur (format-blur value)
:matrix (format-matrix value)
(if (keyword? value) (d/name value) value))))

View File

@@ -47,17 +47,18 @@
[acc {:keys [schema in value type] :as problem}]
(let [props (m/properties schema)
tprops (m/type-properties schema)
field (or (first in)
(:error/field props))
field (or (:error/field props)
in)
field (if (vector? field)
field
[field])]
(if (contains? acc field)
(if (and (= 1 (count field))
(contains? acc (first field)))
acc
(cond
(nil? field)
(or (nil? field)
(empty? field))
acc
(or (= type :malli.core/missing-key)

View File

@@ -37,7 +37,7 @@ This command is going to search for the file located in `frontend/src/app/main/u
## How it works?
The text editor divides the content in three elements: `root`, `paragraph` and `inline`. An `inline` in terms of content is a styled element that it is displayed in a line inside a block and an `inline` only can have one child (a Text node). A `paragraph` is a **block** element that can contain multiple `inline`s (**inline** elements).
The text editor divides the content in three elements: `root`, `paragraph` and `textSpan`. In terms of content, a `textSpan` is a styled element displayed on a line within a block. A `textSpan` can only have one child (a Text node). A `paragraph` is a **block** element that can contain multiple `textSpan`s (**textSpan** elements).
```html
<div data-itype="root">
@@ -53,10 +53,10 @@ This way we only need to deal with a structure like this, where circular nodes a
```mermaid
flowchart TB
root((root)) --> paragraph((paragraph))
paragraph --> inline_1((inline))
paragraph --> inline_2((inline))
inline_1 --> text_1[Hello, ]
inline_2 --> text_2[World!]
paragraph --> text_span_1((textSpan))
paragraph --> text_span_2((textSpan))
text_span_1 --> text_1[Hello, ]
text_span_2 --> text_2[World!]
```
This is compatible with the way Penpot stores text content.
@@ -68,6 +68,26 @@ flowchart TB
paragraph --> text((text))
```
## How the TextEditor works?
```mermaid
flowchart TB
TextEditor -->|handles `selectionchange` events| SelectionController
TextEditor -->|handles how the editor dispatches changes| ChangeController
```
The `TextEditor` contains a series of references to DOM elements, one of them is a `contenteditable` element that keeps the sub-elements explained before (root, paragraphs and textspans).
`SelectionController` listens to the `document` event called `selectionchange`. This event is triggered everytime the focus/selection of the browser changes.
`ChangeController` is called by the `TextEditor` instance everytime a change is performed on the content of the `contenteditable` element.
### Events
- `change`: This event is dispatched every time a change is made in the editor. All changes are debounced to prevent dispatching too many change events. This event is also dispatched when there are pending change events and the user blurs the textarea element.
- `stylechange`: This event is dispatched every time the `currentStyle` changes. This normally happens when the user changes the caret position or the selection and the `currentStyle` is re-computed.
## How the code is organized?
- `editor`: contains everything related to the TextEditor. Where `TextEditor.js` is the main file where all the basic code of the editor is handled. This has been designed so that in the future, when the Web Components API is more stable and has features such as handling selection events within shadow roots we will be able to update this class with little effort.

View File

@@ -392,7 +392,7 @@ msgid "dashboard.export-standard-multi"
msgstr "تحميل %s ملفات أساسية (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* ربما يحتوي على مكوّنات، رسوميات والوان و/أو خطوط."
#: src/app/main/ui/exports/files.cljs:155
@@ -403,30 +403,30 @@ msgstr ""
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "صدّر المكتبات المشتركة"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"لن يتم تضمين المكتبات المشتركة في التصدير ولن يتم إضافة أي أصول إلى "
"المكتبة. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "عامل أصول المكتبة المشتركة كعناصر بسيطة"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr "سيتم تصدير ملفك مع دمج جميع الأصول الخارجية في مكتبة الملفات."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "تضمين أصول المكتبة المشتركة في مكتبات الملفات"
#: src/app/main/ui/exports/files.cljs:147

View File

@@ -380,7 +380,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Baixa %s fitxers estàndard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Pot incloure components, gràfics, colors i/o tipografies."
#: src/app/main/ui/exports/files.cljs:155
@@ -390,33 +390,33 @@ msgstr ""
"voleu fer amb els seus recursos*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Els fitxers amb biblioteques compartides sinclouran a lexportació, "
"mantenint la vinculació."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Exporta les biblioteques compartides"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Les biblioteques compartides no s'inclouran en l'exportació i no s'afegiran "
"recursos a la biblioteca. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Tracta els recursos de la biblioteca compartida com a objectes bàsics"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"El fitxer s'exportarà amb tots els recursos externs fusionats a la "
"biblioteca de fitxers."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr ""
"Inclou els recursos de la biblioteca compartida a les biblioteques del "
"fitxer"

View File

@@ -530,7 +530,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Stáhnout %s soubory (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Může obsahovat komponenty, grafiku, barvy a/nebo typografii."
#: src/app/main/ui/exports/files.cljs:155
@@ -540,33 +540,33 @@ msgstr ""
"Co chcete dělat s jejich položkami*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Soubory se sdílenými knihovnami budou zahrnuty do exportu, čímž se zachová "
"jejich propojení."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Exportovat sdílené knihovny"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Sdílené knihovny nebudou zahrnuty do exportu a do knihovny nebudou přidány "
"žádné položky. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Zacházet s položkami sdílené knihovny jako se základními objekty"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Váš soubor bude exportován se všemi externími položkami sloučenými do "
"knihovny souborů."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Zahrnout sdílené položky knihovny do knihoven souborů"
#: src/app/main/ui/exports/files.cljs:147
@@ -6456,10 +6456,6 @@ msgstr "Nástroje"
msgid "workspace.tokens.value-not-valid"
msgstr "Hodnota není platná"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
msgid "workspace.tokens.warning-name-change"
msgstr "Přejmenováním tohoto tokenu se přeruší jakýkoli odkaz na jeho starý název."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Položky"

View File

@@ -573,7 +573,7 @@ msgid "dashboard.export-standard-multi"
msgstr "%s Standarddateien herunterladen (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* kann Komponenten, Grafiken, Farben und/oder Textstile enthalten."
#: src/app/main/ui/exports/files.cljs:155
@@ -585,33 +585,33 @@ msgstr ""
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Dateien mit geteilten Bibliotheken werden exportiert, und ihre Verknüpfungen "
"bleiben erhalten."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Geteilte Bibliotheken exportieren"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Geteilte Bibliotheken werden nicht exportiert und der Bibliothek werden "
"keine Assets hinzugefügt. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Assets aus geteilten Bibliotheken als gewöhnliche Objekte behandeln"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Ihre Datei wird exportiert, und alle externen Assets werden der "
"Dateibibliothek hinzugefügt."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Assets aus geteilten Bibliotheken in die Dateibibliothek aufnehmen"
#: src/app/main/ui/exports/files.cljs:147
@@ -7175,12 +7175,6 @@ msgstr "Der Wert ist nicht gültig"
msgid "workspace.tokens.value-with-percent"
msgstr "Ungültiger Wert: % ist nicht zulässig."
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
msgid "workspace.tokens.warning-name-change"
msgstr ""
"Die Umbenennung dieses Tokens macht jeden Verweis auf seinen alten Namen "
"kaputt."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Assets"

View File

@@ -564,48 +564,48 @@ msgid "dashboard.export-standard-multi"
msgstr "Download %s standard files (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Might include components, graphics, colors and/or typographies."
#: src/app/main/ui/exports/files.cljs:155
msgid "dashboard.export.explain"
msgstr ""
"One or more files that you want to export are using shared libraries. What "
"One or more files that you want to download are using shared libraries. What "
"do you want to do with their assets*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Files with shared libraries will be included in the export, maintaining "
"their linkage."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Export shared libraries"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Shared libraries will not be included in the export and no assets will be "
"added to the library. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Treat shared library assets as basic objects"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Your file will be exported with all external assets merged into the file "
"library."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Include shared library assets in file libraries"
#: src/app/main/ui/exports/files.cljs:147
msgid "dashboard.export.title"
msgstr "Export files"
msgid "files-download-modal.title"
msgstr "Download files"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:317
msgid "dashboard.fonts.deleted-placeholder"
@@ -7543,6 +7543,46 @@ msgstr "Color"
msgid "workspace.tokens.composite-line-height-needs-font-size"
msgstr "Line Height depends on Font Size. Add a Font Size to get the resolved value."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.remap-token-references"
msgstr "Remap Token References"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.renaming-token-from-to"
msgstr "Renaming token from '%s' to '%s'"
#: src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs
msgid "workspace.tokens.warning-name-change"
msgstr "Renaming this token will break any reference to its old name"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.references-found"
msgstr "%s references found in your design"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.remap-explanation"
msgstr "All references to this token will be automatically updated to use the new name."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.no-references-found"
msgstr "No references found"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.no-remap-needed"
msgstr "This token is not currently used in your design, so no remapping is needed."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.remapping-in-progress"
msgstr "Remapping token references..."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.remap-and-rename"
msgstr "Remap & Rename"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.rename-only"
msgstr "Rename"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:78
msgid "workspace.tokens.create-new-theme"
msgstr "Create your first theme now."
@@ -8032,6 +8072,14 @@ msgstr "Name"
msgid "workspace.tokens.token-name-duplication-validation-error"
msgstr "A token already exists at the path: %s"
#: src/app/main/ui/workspace/tokens/management/forms/shadow.cljs
msgid "workspace.tokens.shadow-token-blur-value-error"
msgstr "Blur value cannot be negative"
#: src/app/main/ui/workspace/tokens/management/forms/shadow.cljs
msgid "workspace.tokens.shadow-token-spread-value-error"
msgstr "Spread value cannot be negative"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:42, src/app/main/ui/workspace/tokens/management/create/form.cljs:68
msgid "workspace.tokens.token-name-length-validation-error"
msgstr "Name should be at least 1 character"
@@ -8076,7 +8124,7 @@ msgstr "Type '%s' is not supported (%s)\n"
msgid "workspace.tokens.use-reference"
msgstr "Use a reference"
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:131
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:133
msgid "workspace.tokens.value-not-valid"
msgstr "The value is not valid"
@@ -8088,10 +8136,6 @@ msgstr "Invalid value: % is not allowed."
msgid "workspace.tokens.value-with-units"
msgstr "Invalid value: Units are not allowed."
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
msgid "workspace.tokens.warning-name-change"
msgstr "Renaming this token will break any reference to its old name."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Assets"
@@ -8437,11 +8481,15 @@ msgstr "Restore All"
msgid "dashboard.clear-trash-button"
msgstr "Clear trash"
msgid "dashboard.restore-file-button"
msgstr "Restore file"
msgid "dashboard.file-menu.restore-files-option"
msgid_plural "dashboard.file-menu.restore-files-option"
msgstr[0] "Restore file"
msgstr[1] "Restore files"
msgid "dashboard.delete-file-button"
msgstr "Delete file"
msgid "dashboard.file-menu.delete-files-permanently-option"
msgid_plural "dashboard.file-menu.delete-files-permanently-option"
msgstr[0] "Delete file"
msgstr[1] "Delete files"
msgid "dashboard.restore-project-button"
msgstr "Restore project"
@@ -8532,3 +8580,6 @@ msgstr "Restore unexpectedly slow"
msgid "dashboard.progress-notification.slow-delete"
msgstr "Deletion unexpectedly slow"
msgid "dashboard.deleted.empty-state-description"
msgstr "Your trash is empty. Deleted files and projects will appear here."

View File

@@ -573,48 +573,48 @@ msgid "dashboard.export-standard-multi"
msgstr "Descargar %s archivos estándar (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Pueden incluir components, gráficos, colores y/o tipografias."
#: src/app/main/ui/exports/files.cljs:155
msgid "dashboard.export.explain"
msgstr ""
"Uno o mas ficheros que quieres exportar usan librerias compartidas. ¿Qué "
"Uno o mas ficheros que quieres descargar usan librerias compartidas. ¿Qué "
"quieres hacer con los recursos*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Ficheros con librerias compartidas se inclurán en el paquete de exportación "
"y mantendrán los enlaces."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Exportar librerias compartidas"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Las biblioteca compartidas no se incluirán en la exportación y ningún "
"recurso será incluido en la biblioteca. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Usar los recursos como objetos básicos"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Tu fichero será exportado con todos los recursos dentro de la libreria del "
"propio fichero."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Incluir librerias compartidas dentro de las librerias del fichero"
#: src/app/main/ui/exports/files.cljs:147
msgid "dashboard.export.title"
msgstr "Exportar ficheros"
msgid "files-download-modal.title"
msgstr "Descargar ficheros"
#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:317
msgid "dashboard.fonts.deleted-placeholder"
@@ -4420,6 +4420,46 @@ msgstr "Mostrar/ocultar recursos"
msgid "shortcuts.toggle-colorpalette"
msgstr "Mostrar/ocultar paleta de colores"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.remap-token-references"
msgstr "Actualizar referencias de token"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.renaming-token-from-to"
msgstr "Renombrando el token de '%s' a '%s'"
#: src/app/main/ui/workspace/tokens/management/forms/generic_form.cljs
msgid "workspace.tokens.warning-name-change"
msgstr "Cambiar el nombre de este token romperá cualquier referencia a su nombre anterior."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.references-found"
msgstr "%s referencias encontradas en tu diseño"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.remap-explanation"
msgstr "Todas las referencias a este token se actualizarán automáticamente para usar el nuevo nombre."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.no-references-found"
msgstr "No se encontraron referencias"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.no-remap-needed"
msgstr "Este token no se utiliza actualmente en tu diseño, por lo que no es necesario actualizar referencias."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.remapping-in-progress"
msgstr "Actualizando referencias de token..."
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.remap-and-rename"
msgstr "Actualizar referencias y renombrar"
#: src/app/main/ui/workspace/tokens/remapping_modal.cljs
msgid "workspace.tokens.rename-only"
msgstr "Renombrar"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:185
msgid "shortcuts.toggle-focus-mode"
msgstr "Mostrar/ocultar focus mode"
@@ -7908,6 +7948,14 @@ msgstr "Nombre"
msgid "workspace.tokens.token-name-duplication-validation-error"
msgstr "Ya existe un token en la ruta: %s"
#: src/app/main/ui/workspace/tokens/management/forms/shadow.cljs
msgid "workspace.tokens.shadow-token-blur-value-error"
msgstr "El valor de blur no puede ser negativo"
#: src/app/main/ui/workspace/tokens/management/forms/shadow.cljs
msgid "workspace.tokens.shadow-token-spread-value-error"
msgstr "El valor de spread no puede ser negativo"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:42, src/app/main/ui/workspace/tokens/management/create/form.cljs:68
msgid "workspace.tokens.token-name-length-validation-error"
msgstr "El nombre debería ser de al menos 1 caracter"
@@ -7958,10 +8006,6 @@ msgstr "El valor no es válido"
msgid "workspace.tokens.value-with-units"
msgstr "Valor no válido: No se permiten unidades."
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:181, src/app/main/ui/workspace/tokens/management/create/form.cljs:602
msgid "workspace.tokens.warning-name-change"
msgstr "Al renombrar este token se romperán las referencias al nombre anterior"
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Recursos"
@@ -8290,11 +8334,15 @@ msgstr "Restaurar todo"
msgid "dashboard.clear-trash-button"
msgstr "Vaciar papelera"
msgid "dashboard.restore-file-button"
msgstr "Restaurar archivo"
msgid "dashboard.file-menu.restore-files-option"
msgid_plural "dashboard.file-menu.restore-files-option"
msgstr[0] "Restaurar archivo"
msgstr[1] "Restaurar archivos"
msgid "dashboard.delete-file-button"
msgstr "Eliminar archivo"
msgid "dashboard.file-menu.delete-files-permanently-option"
msgid_plural "dashboard.file-menu.delete-files-permanently-option"
msgstr[0] "Eliminar archivo"
msgstr[1] "Eliminar archivos"
msgid "dashboard.restore-project-button"
msgstr "Restaurar proyecto"
@@ -8314,9 +8362,6 @@ msgstr "Después de eso, serán eliminados permanentemente."
msgid "dashboard.trash-info-text-part4"
msgstr "Si cambias de opinión, puedes restaurarlos o eliminarlos permanentemente desde el menú de cada archivo."
msgid "dashboard.deleted.delete-forever"
msgstr "Eliminar para siempre"
msgid "dashboard.restore-all-confirmation.title"
msgstr "Restaurar todos los proyectos y archivos"
@@ -8365,9 +8410,6 @@ msgstr "Hubo un error al restaurar los archivos."
msgid "dashboard.errors.error-on-restore-file"
msgstr "Hubo un error al restaurar el archivo %s."
msgid "dashboard.errors.error-on-restoring-files"
msgstr "Hubo un error al restaurar archivos."
msgid "dashboard.errors.error-on-delete-files"
msgstr "Hubo un error al eliminar archivos."
@@ -8385,3 +8427,6 @@ msgstr "Restauración inesperadamente lenta"
msgid "dashboard.progress-notification.slow-delete"
msgstr "Eliminación inesperadamente lenta"
msgid "dashboard.deleted.empty-state-description"
msgstr "Tu papelera está vacía. Los archivos y proyectos eliminados aparecerán aquí."

View File

@@ -444,7 +444,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Descargar %s archivos estándar (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Puede incluir componentes, gráficos, colores y/o tipografías."
#: src/app/main/ui/exports/files.cljs:155
@@ -455,33 +455,33 @@ msgstr ""
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Los archivos con bibliotecas compartidas se incluirán en la exportación, "
"manteniendo su vinculación."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Exportar bibliotecas compartidas"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Las bibliotecas compartidas no se incluirán en la exportación y no se "
"agregarán activos a la biblioteca. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Trate los activos de biblioteca compartidos como objetos básicos"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Su archivo se exportará con todos los activos externos combinados en la "
"biblioteca de archivos."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Incluir recursos de biblioteca compartidos en bibliotecas de archivos"
#: src/app/main/ui/dashboard/import.cljs:131

View File

@@ -358,7 +358,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Deskargatu %s fitxategi estandar (.svn + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Osagaiak, grafikoak, koloreak edo/eta tipografiak izan ditzakete."
#: src/app/main/ui/exports/files.cljs:155
@@ -368,31 +368,31 @@ msgstr ""
"darabiltzate. Zer egin nahi duzu baliabideekin*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Partekatutako liburutegiak dituzten fitxategiak esportazio paketean sartuko "
"dira eta loturak mantenduko dituzte."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Esportatu partekatutako liburutegiak"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Partekatutako liburutegiak ez dira esportazioan sartuko eta baliabide bat "
"ere ez da liburutegian sartuko. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Erabili baliabideak oinarrizko objektu bezala"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr "Zure fitxategia baliabide guztiak bere baitan dituela esportatuko da."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Sartu partekatutako liburutegiak fitxategiaren liburutegietan"
#: src/app/main/ui/exports/files.cljs:147

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