Compare commits

...

59 Commits

Author SHA1 Message Date
alonso.torres
498b0b30fe 🐛 Fix problems with layout 2026-01-19 12:17:58 +01:00
Elena Torró
89f40dcda2 🔧 Move WebGL context error message to 'errors' namespace (#8117) 2026-01-19 11:24:19 +01:00
Elena Torró
ccac7bd510 Merge pull request #8108 from penpot/ladybenko-13022-blur-page
🎉 Apply blur effect when switching pages
2026-01-19 11:04:31 +01:00
Andrey Antukh
d73197625d Merge remote-tracking branch 'origin/staging' into staging-render 2026-01-19 10:43:43 +01:00
Belén Albeza
43d1d127dc 🎉 Apply blur effect to previous canvas pixels while setting wasm objects 2026-01-16 13:04:59 +01:00
Belén Albeza
8bd3ef717c 🎉 Apply blur to canvas when switching pages 2026-01-16 13:04:59 +01:00
Elena Torro
53bc647783 🔧 Fix shape selection from canvas to sidebar 2026-01-16 13:02:25 +01:00
Andrey Antukh
1ae7515189 🐛 Set correct defaults for storage configuration on compose (#8060)
* 🔥 Remove mention to legacy and never released admin on compose

* 🐛 Set correct defaults for storage configuration on compose
2026-01-16 08:11:34 +01:00
andrés gonzález
8c401f5346 📚 Add Files and Projects section to user guide (#8095) 2026-01-15 17:01:05 +01:00
Elena Torró
6029f9bb51 Merge pull request #8089 from penpot/superalex-improve-zoom-pan-performance-5
🎉 Performance improvements
2026-01-15 16:46:07 +01:00
Elena Torro
e0fd8bac81 🔧 Optimize sidebar performance for deeply nested shapes
- Batch hover highlights using RAF to avoid long tasks from rapid events
- Run parent expansion asynchronously to not block selection
- Lazy-load children in layer items using IntersectionObserver
- Clarify expand-all-parents logic with explicit bindings
2026-01-15 13:41:54 +01:00
Elena Torro
34737ddfc9 🔧 Always lookup over a set 2026-01-15 13:41:10 +01:00
Elena Torro
a8dfd19338 🔧 Add performance debugging logs 2026-01-15 13:40:58 +01:00
Elena Torro
e33e8a8c3b 🔧 Lookup page objects only when value changes 2026-01-15 13:40:53 +01:00
Alejandro Alonso
c411aefc6c 🐛 Fix rotated shapes extrect calculation 2026-01-15 12:53:21 +01:00
Alejandro Alonso
311e124658 🎉 Reduce extrect work in tile traversal
Avoid repeated extrect calculations and simplify root ordering per tile.
2026-01-15 12:53:21 +01:00
Alejandro Alonso
afc914f486 🎉 Render simple shapes directly on Current
Bypass intermediate surfaces for simple shapes without effects.
2026-01-15 12:53:21 +01:00
andrés gonzález
b72de2dc8f 📚 Add shadow token documentation (#8082) 2026-01-15 09:09:08 +01:00
Alejandro Alonso
84f750da0d 🎉 Skip heavy effects in fast mode
Avoid blur and shadow passes for text and shapes when FAST_MODE is enabled.
2026-01-15 08:45:21 +01:00
Elena Torro
a3119bef5e 🔧 Show message and button to reload the page when WebGL context is lost 2026-01-14 11:10:03 +01:00
Alejandro Alonso
c60d74df62 🐛 Fix nested frames border clipping 2026-01-14 11:10:03 +01:00
Alejandro Alonso
d593e299e3 🐛 Fix mask erros on save/restore optimizations 2026-01-14 11:10:03 +01:00
Alejandro Alonso
4a8e02987f 🐛 Fix mask erros on save/restore optimizations 2026-01-14 11:10:03 +01:00
Alejandro Alonso
ee766e85a0 🎉 Wasm render dirty surfaces 2026-01-14 11:10:03 +01:00
Alejandro Alonso
35e3b7f19a 🎉 Root ids refactor 2026-01-14 11:10:03 +01:00
Alejandro Alonso
1810df232b 🎉 Ignore frames and groups when they have no visual extra information 2026-01-14 11:10:03 +01:00
Alejandro Alonso
3e99ad036c 🎉 Avoid unnecesary saves and restores 2026-01-14 11:10:03 +01:00
Alejandro Alonso
042a3a4080 🐛 Fix wasm playgrounds 2026-01-14 11:10:03 +01:00
Belén Albeza
f0687fd1f7 🎉 Make workspace loader to wait for first render 2026-01-14 11:10:03 +01:00
Aitor Moreno
2c9159288f 🐛 Fix previous styles lost when changing selected text 2026-01-14 11:10:01 +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
105 changed files with 5393 additions and 1985 deletions

View File

@@ -23,7 +23,13 @@
- 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)
- Fix problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
## 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

@@ -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

@@ -526,20 +526,25 @@
ids))
(defn clean-loops
"Clean a list of ids from circular references."
"Clean a list of ids from circular references. Optimized fast-path for single selections."
[objects ids]
(let [parent-selected?
(fn [id]
(let [parents (get-parent-ids objects id)]
(some ids parents)))
(if (<= (count ids) 1)
;; For single selection, there can't be circularity; return as ordered-set.
(into (d/ordered-set) ids)
(let [ids-set (if (set? ids) ids (set ids))
parent-selected?
(fn [id]
;; Stop early as soon as we find any selected parent
(let [parents (get-parent-ids objects id)]
(some #(contains? ids-set %) parents)))
add-element
(fn [result id]
(cond-> result
(not (parent-selected? id))
(conj id)))]
add-element
(fn [result id]
(cond-> result
(not (parent-selected? id))
(conj id)))]
(reduce add-element (d/ordered-set) ids)))
(reduce add-element (d/ordered-set) ids))))
(defn- indexed-shapes
"Retrieves a vector with the indexes for each element in the layer

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

@@ -130,12 +130,6 @@ services:
environment:
<< : [*penpot-flags, *penpot-public-uri, *penpot-http-body-size, *penpot-secret-key]
## The PREPL host. Mainly used for external programatic access to penpot backend
## (example: admin). By default it will listen on `localhost` but if you are going to use
## the `admin`, you will need to uncomment this and set the host to `0.0.0.0`.
# PENPOT_PREPL_HOST: 0.0.0.0
## Database connection parameters. Don't touch them unless you are using custom
## postgresql connection parameters.
@@ -151,8 +145,8 @@ services:
## Default configuration for assets storage: using filesystem based with all files
## stored in a docker volume.
PENPOT_ASSETS_STORAGE_BACKEND: assets-fs
PENPOT_STORAGE_ASSETS_FS_DIRECTORY: /opt/data/assets
PENPOT_OBJECTS_STORAGE_BACKEND: fs
PENPOT_OBJECTS_STORAGE_FS_DIRECTORY: /opt/data/assets
## Also can be configured to to use a S3 compatible storage.

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>

View File

File diff suppressed because it is too large Load Diff

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 25 KiB

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

@@ -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;

View File

@@ -23,7 +23,7 @@
<body>
<canvas id="canvas"></canvas>
<script type="module">
import initWasmModule from '/js/render_wasm.js';
import initWasmModule from '/js/render-wasm.js';
import {
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
getRandomFloat, useShape, setShapeChildren, setupInteraction, addShapeSolidStrokeFill,

View File

@@ -23,7 +23,7 @@
<body>
<canvas id="canvas"></canvas>
<script type="module">
import initWasmModule from '/js/render_wasm.js';
import initWasmModule from '/js/render-wasm.js';
import {
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
getRandomFloat, useShape, setShapeChildren, setupInteraction, set_parent

View File

@@ -23,7 +23,7 @@
<body>
<canvas id="canvas"></canvas>
<script type="module">
import initWasmModule from '/js/render_wasm.js';
import initWasmModule from '/js/render-wasm.js';
import {
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
getRandomFloat, useShape, setShapeChildren, setupInteraction, set_parent, draw_star,

View File

@@ -23,7 +23,7 @@
<body>
<canvas id="canvas"></canvas>
<script type="module">
import initWasmModule from '/js/render_wasm.js';
import initWasmModule from '/js/render-wasm.js';
import {
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
getRandomFloat, useShape, setShapeChildren, setupInteraction, set_parent, allocBytes,

View File

@@ -23,7 +23,7 @@
<body>
<canvas id="canvas"></canvas>
<script type="module">
import initWasmModule from '/js/render_wasm.js';
import initWasmModule from '/js/render-wasm.js';
import {
init, addShapeSolidFill, assignCanvas, hexToU32ARGB, getRandomInt, getRandomColor,
getRandomFloat, useShape, setShapeChildren, setupInteraction, addShapeSolidStrokeFill

View File

@@ -23,7 +23,7 @@
<body>
<canvas id="canvas"></canvas>
<script type="module">
import initWasmModule from '/js/render_wasm.js';
import initWasmModule from '/js/render-wasm.js';
import {
init, assignCanvas, setupInteraction, useShape, setShapeChildren, addTextShape, hexToU32ARGB,getRandomInt, getRandomColor, getRandomFloat, addShapeSolidFill, addShapeSolidStrokeFill
} from './js/lib.js';
@@ -102,4 +102,4 @@
});
</script>
</body>
</html>
</html>

View File

@@ -61,6 +61,11 @@
;; Def micro-benchmark iterations
(def micro-benchmark-iterations 1e6)
;; Performance logs
(defonce ^:private longtask-observer* (atom nil))
(defonce ^:private stall-timer* (atom nil))
(defonce ^:private current-op* (atom nil))
;; --- CONTEXT
(defn- collect-context
@@ -464,3 +469,72 @@
(defn event
[props]
(ptk/data-event ::event props))
;; --- DEVTOOLS PERF LOGGING
(defn install-long-task-observer! []
(when (and (some? (.-PerformanceObserver js/window)) (nil? @longtask-observer*))
(let [observer (js/PerformanceObserver.
(fn [list _]
(doseq [entry (.getEntries list)]
(let [dur (.-duration entry)
start (.-startTime entry)
attrib (.-attribution entry)
attrib-count (when attrib (.-length attrib))
first-attrib (when (and attrib-count (> attrib-count 0)) (aget attrib 0))
attrib-name (when first-attrib (.-name first-attrib))
attrib-ctype (when first-attrib (.-containerType first-attrib))
attrib-cid (when first-attrib (.-containerId first-attrib))
attrib-csrc (when first-attrib (.-containerSrc first-attrib))]
(.warn js/console (str "[perf] long task " (Math/round dur) "ms at " (Math/round start) "ms"
(when first-attrib
(str " attrib:name=" attrib-name
" ctype=" attrib-ctype
" cid=" attrib-cid
" csrc=" attrib-csrc))))))))]
(.observe observer #js{:entryTypes #js["longtask"]})
(reset! longtask-observer* observer))))
(defn start-event-loop-stall-logger!
"Log event loop stalls by measuring setInterval drift.
interval-ms: base interval
threshold-ms: drift over which we report"
[interval-ms threshold-ms]
(when (nil? @stall-timer*)
(let [last (atom (.now js/performance))
id (js/setInterval
(fn []
(let [now (.now js/performance)
expected (+ @last interval-ms)
drift (- now expected)
current-op @current-op*
measures (.getEntriesByType js/performance "measure")
mlen (.-length measures)
last-measure (when (> mlen 0) (aget measures (dec mlen)))
meas-name (when last-measure (.-name last-measure))
meas-detail (when last-measure (.-detail last-measure))
meas-count (when meas-detail (unchecked-get meas-detail "count"))]
(reset! last now)
(when (> drift threshold-ms)
(.warn js/console
(str "[perf] event loop stall: " (Math/round drift) "ms"
(when current-op (str " op=" current-op))
(when meas-name (str " last=" meas-name))
(when meas-count (str " count=" meas-count)))))))
interval-ms)]
(reset! stall-timer* id))))
(defn init!
"Install perf observers in dev builds. Safe to call multiple times."
[]
(when ^boolean js/goog.DEBUG
(install-long-task-observer!)
(start-event-loop-stall-logger! 50 100)
;; Expose simple API on window for manual control in devtools
(let [api #js {:reset (fn []
(try
(.clearMarks js/performance)
(.clearMeasures js/performance)
(catch :default _ nil)))}]
(aset js/window "PenpotPerf" api))))

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

@@ -347,6 +347,12 @@
(with-meta {:team-id team-id
:file-id file-id}))))))
;; Install dev perf observers once the workspace is ready
(->> stream
(rx/filter (ptk/type? ::workspace-initialized))
(rx/take 1)
(rx/map (fn [_] (ev/init!))))
(->> stream
(rx/filter (ptk/type? ::dps/persistence-notification))
(rx/take 1)

View File

@@ -18,13 +18,13 @@
ptk/UpdateEvent
(update [_ state]
(let [expand-fn (fn [expanded]
(merge expanded
(->> ids
(map #(cfh/get-parent-ids objects %))
flatten
(remove #(= % uuid/zero))
(map (fn [id] {id true}))
(into {}))))]
(let [parents-seqs (map (fn [x] (cfh/get-parent-ids objects x)) ids)
flat-parents (apply concat parents-seqs)
non-root-parents (remove #(= % uuid/zero) flat-parents)
distinct-parents (into #{} non-root-parents)]
(merge expanded
(into {}
(map (fn [id] {id true}) distinct-parents)))))]
(update-in state [:workspace-local :expanded] expand-fn)))))

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

@@ -264,10 +264,13 @@
ptk/WatchEvent
(watch [_ state _]
(let [objects (dsh/lookup-page-objects state)]
(rx/of
(dwc/expand-all-parents ids objects)
::dwsp/interrupt)))))
(let [objects (dsh/lookup-page-objects state)
;; Schedule expanding parents asynchronously to avoid blocking
;; the event loop
expand-s (->> (rx/of (dwc/expand-all-parents ids objects))
(rx/observe-on :async))
interrupt-s (rx/of ::dwsp/interrupt)]
(rx/merge expand-s interrupt-s)))))
(defn select-all
[]

View File

@@ -305,7 +305,7 @@
(l/derived #(dsh/lookup-shape % page-id shape-id) st/state =))
(def workspace-page-objects
(l/derived dsh/lookup-page-objects st/state))
(l/derived dsh/lookup-page-objects st/state identical?))
(def workspace-read-only?
(l/derived :read-only? workspace-global))

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

@@ -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

@@ -10,6 +10,7 @@ $z-index-200: 200;
$z-index-300: 300;
$z-index-400: 400;
$z-index-500: 500;
$z-index-600: 600;
:global(:root) {
--z-index-auto: #{$z-index-auto}; // Index for elements such as workspace, rulers ...
@@ -18,4 +19,5 @@ $z-index-500: 500;
--z-index-set: #{$z-index-300}; // Index for configuration elements like modals, color picker, grid edition elements
--z-index-dropdown: #{$z-index-400}; // Index for dropdown like elements, selects, menus, dropdowns
--z-index-notifications: #{$z-index-500}; // Index for notification
--z-index-loaders: #{$z-index-600}; // Index for loaders
}

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

@@ -308,6 +308,16 @@
[:div {:class (stl/css :sign-info)}
[:button {:on-click on-click} (tr "labels.retry")]]]))
(mf/defc webgl-context-lost*
[]
(let [on-reload (mf/use-fn #(js/location.reload))]
[:> error-container* {}
[:div {:class (stl/css :main-message)} (tr "errors.webgl-context-lost.main-message")]
[:div {:class (stl/css :desc-message)} (tr "errors.webgl-context-lost.desc-message")]
[:div {:class (stl/css :buttons-container)}
[:> button* {:variant "primary" :on-click on-reload}
(tr "labels.reload-page")]]]))
(defn- generate-report
[data]
(try
@@ -437,6 +447,7 @@
(rx/of default)
(rx/throw cause)))))))
(mf/defc exception-section*
{::mf/private true}
[{:keys [data route] :as props}]
@@ -469,6 +480,9 @@
:service-unavailable
[:> service-unavailable*]
:webgl-context-lost
[:> webgl-context-lost*]
[:> internal-error* props])))
(mf/defc context-wrapper*

View File

@@ -217,6 +217,10 @@
design-tokens? (features/use-feature "design-tokens/v1")
wasm-renderer-enabled? (features/use-feature "render-wasm/v1")
first-frame-rendered? (mf/use-state false)
background-color (:background-color wglobal)]
(mf/with-effect []
@@ -241,6 +245,17 @@
(when (and file-loaded? (not page-id))
(st/emit! (dcm/go-to-workspace :file-id file-id ::rt/replace true))))
(mf/with-effect [file-id page-id]
(reset! first-frame-rendered? false))
(mf/with-effect []
(let [handle-wasm-render
(fn [_]
(reset! first-frame-rendered? true))
listener-key (events/listen globals/document "penpot:wasm:render" handle-wasm-render)]
(fn []
(events/unlistenByKey listener-key))))
[:> (mf/provider ctx/current-project-id) {:value project-id}
[:> (mf/provider ctx/current-file-id) {:value file-id}
[:> (mf/provider ctx/current-page-id) {:value page-id}
@@ -249,15 +264,22 @@
[:> modal-container*]
[:section {:class (stl/css :workspace)
:style {:background-color background-color
:touch-action "none"}}
:touch-action "none"
:position "relative"}}
[:> context-menu*]
(if (and file-loaded? page-id)
(when (and file-loaded? page-id)
[:> workspace-inner*
{:page-id page-id
:file-id file-id
:file file
:wglobal wglobal
:layout layout}]
:layout layout}])
(when (or (not (and file-loaded? page-id))
;; in wasm renderer, extend the pixel loader until the first frame is rendered
;; but do not apply it when switching pages
(and wasm-renderer-enabled?
(not file-loaded?)
(not @first-frame-rendered?)))
[:> workspace-loader*])]]]]]]))
(mf/defc workspace-page*

View File

@@ -20,7 +20,13 @@
}
.workspace-loader {
grid-area: viewport;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: var(--z-index-loaders);
background-color: var(--color-background-primary);
}
.workspace-content {

View File

@@ -33,9 +33,24 @@
[okulary.core :as l]
[rumext.v2 :as mf]))
;; Coalesce sidebar hover highlights to 1 frame to avoid long tasks
(defonce ^:private sidebar-hover-queue (atom {:enter #{} :leave #{}}))
(defonce ^:private sidebar-hover-pending? (atom false))
(defn- schedule-sidebar-hover-flush []
(when (compare-and-set! sidebar-hover-pending? false true)
(ts/raf
(fn []
(let [{:keys [enter leave]} (swap! sidebar-hover-queue (constantly {:enter #{} :leave #{}}))]
(reset! sidebar-hover-pending? false)
(when (seq leave)
(apply st/emit! (map dw/dehighlight-shape leave)))
(when (seq enter)
(apply st/emit! (map dw/highlight-shape enter))))))))
(mf/defc layer-item-inner
{::mf/wrap-props false}
[{:keys [item depth parent-size name-ref children ref
[{:keys [item depth parent-size name-ref children ref style
;; Flags
read-only? highlighted? selected? component-tree?
filtered? expanded? dnd-over? dnd-over-top? dnd-over-bot? hide-toggle?
@@ -82,7 +97,8 @@
:dnd-over dnd-over?
:dnd-over-top dnd-over-top?
:dnd-over-bot dnd-over-bot?
:root-board parent-board?)}
:root-board parent-board?)
:style style}
[:span {:class (stl/css-case
:tab-indentation true
:filtered filtered?)
@@ -166,10 +182,12 @@
children]))
;; Memoized for performance
(mf/defc layer-item
{::mf/props :obj
::mf/memo true}
[{:keys [index item selected objects sortable? filtered? depth parent-size component-child? highlighted]}]
::mf/wrap [mf/memo]}
[{:keys [index item selected objects sortable? filtered? depth parent-size component-child? highlighted style render-children?]
:or {render-children? true}}]
(let [id (:id item)
blocked? (:blocked item)
hidden? (:hidden item)
@@ -246,13 +264,21 @@
(mf/use-fn
(mf/deps id)
(fn [_]
(st/emit! (dw/highlight-shape id))))
(swap! sidebar-hover-queue (fn [{:keys [enter leave] :as q}]
(-> q
(assoc :enter (conj enter id))
(assoc :leave (disj leave id)))))
(schedule-sidebar-hover-flush)))
on-pointer-leave
(mf/use-fn
(mf/deps id)
(fn [_]
(st/emit! (dw/dehighlight-shape id))))
(swap! sidebar-hover-queue (fn [{:keys [enter leave] :as q}]
(-> q
(assoc :enter (disj enter id))
(assoc :leave (conj leave id)))))
(schedule-sidebar-hover-flush)))
on-context-menu
(mf/use-fn
@@ -338,14 +364,18 @@
component-tree? (or component-child? (ctk/instance-root? item) (ctk/instance-head? item))
enable-drag (mf/use-fn #(reset! drag-disabled* false))
disable-drag (mf/use-fn #(reset! drag-disabled* true))]
disable-drag (mf/use-fn #(reset! drag-disabled* true))
;; Lazy loading of child elements via IntersectionObserver
children-count* (mf/use-state 0)
children-count (deref children-count*)
lazy-ref (mf/use-ref nil)
observer-var (mf/use-var nil)
chunk-size 50]
(mf/with-effect [selected? selected]
(let [single? (= (count selected) 1)
node (mf/ref-val ref)
;; NOTE: Neither get-parent-at nor get-parent-with-selector
;; work if the component template changes, so we need to
;; seek for an alternate solution. Maybe use-context?
scroll-node (dom/get-parent-with-data node "scroll-container")
parent-node (dom/get-parent-at node 2)
first-child-node (dom/get-first-child parent-node)
@@ -363,6 +393,61 @@
#(when (some? subid)
(rx/dispose! subid))))
;; Setup scroll-driven lazy loading when expanded
;; and ensures selected children are loaded immediately
(mf/with-effect [expanded? (:shapes item) selected]
(let [shapes-vec (:shapes item)
total (count shapes-vec)]
(if expanded?
(let [;; Children are rendered in reverse order, so index 0 in render = last in shapes-vec
;; Find if any selected id is a direct child and get its render index
selected-child-render-idx
(when (and (> total chunk-size) (seq selected))
(let [shapes-reversed (vec (reverse shapes-vec))]
(some (fn [sel-id]
(let [idx (.indexOf shapes-reversed sel-id)]
(when (>= idx 0) idx)))
selected)))
;; Load at least enough to include the selected child plus extra
;; for context (so it can be centered in the scroll view)
min-count (if selected-child-render-idx
(+ selected-child-render-idx chunk-size)
chunk-size)
current @children-count*
new-count (min total (max current chunk-size min-count))]
(reset! children-count* new-count))
(reset! children-count* 0)))
(fn []
(when-let [obs ^js @observer-var]
(.disconnect obs)
(reset! observer-var nil))))
;; Re-observe sentinel whenever children-count changes (sentinel moves)
(mf/with-effect [children-count expanded?]
(let [total (count (:shapes item))
node (mf/ref-val ref)
scroll-node (dom/get-parent-with-data node "scroll-container")
lazy-node (mf/ref-val lazy-ref)]
;; Disconnect previous observer
(when-let [obs ^js @observer-var]
(.disconnect obs)
(reset! observer-var nil))
;; Setup new observer if there are more children to load
(when (and expanded?
(< children-count total)
scroll-node
lazy-node)
(let [cb (fn [entries]
(when (and (seq entries)
(.-isIntersecting (first entries)))
;; Load next chunk when sentinel intersects
(let [current @children-count*
next-count (min total (+ current chunk-size))]
(reset! children-count* next-count))))
observer (js/IntersectionObserver. cb #js {:root scroll-node})]
(.observe observer lazy-node)
(reset! observer-var observer)))))
[:& layer-item-inner
{:ref dref
:item item
@@ -387,24 +472,32 @@
:on-enable-drag enable-drag
:on-disable-drag disable-drag
:on-toggle-visibility toggle-visibility
:on-toggle-blocking toggle-blocking}
:on-toggle-blocking toggle-blocking
:style style}
(when (and (:shapes item) expanded?)
(when (and render-children?
(:shapes item)
expanded?)
[:div {:class (stl/css-case
:element-children true
:parent-selected selected?
:sticky-children parent-board?)
:data-testid (dm/str "children-" id)}
(for [[index id] (reverse (d/enumerate (:shapes item)))]
(when-let [item (get objects id)]
[:& layer-item
{:item item
:highlighted highlighted
:selected selected
:index index
:objects objects
:key (dm/str id)
:sortable? sortable?
:depth depth
:parent-size parent-size
:component-child? component-tree?}]))])]))
(let [all-children (reverse (d/enumerate (:shapes item)))
visible (take children-count all-children)]
(for [[index id] visible]
(when-let [item (get objects id)]
[:& layer-item
{:item item
:highlighted highlighted
:selected selected
:index index
:objects objects
:key (dm/str id)
:sortable? sortable?
:depth depth
:parent-size parent-size
:component-child? component-tree?}])))
(when (< children-count (count (:shapes item)))
[:div {:ref lazy-ref
:style {:min-height 1}}])])]))

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]
@@ -116,13 +116,29 @@
(->> (dm/get-in grid-edition [edition :selected])
(map #(dm/get-in objects [edition :layout-grid-cells %])))
shapes-with-children
(mf/with-memo [selected objects shapes]
(let [xform (comp (remove nil?)
(mapcat #(cfh/get-children-ids objects %)))
selected (into selected xform selected)]
(sequence (keep (d/getf objects)) selected)))
shapes-with-children*
(mf/use-state nil)
_ (mf/use-effect
(mf/deps selected objects shapes)
(fn []
(reset! shapes-with-children* nil)
(let [result
(loop [queue (into #queue [] selected)
visited selected]
(if-let [id (peek queue)]
(let [shape (get objects id)
children (:shapes shape)]
(if (seq children)
(let [new-children (remove visited children)]
(recur (into (pop queue) new-children)
(into visited new-children)))
(recur (pop queue) visited)))
(sequence (keep (d/getf objects)) visited)))]
(reset! shapes-with-children* result))))
shapes-with-children
(deref shapes-with-children*)
total-selected
(count selected)]

View File

@@ -13,6 +13,7 @@
[app.main.data.helpers :as dsh]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.title-bar :refer [title-bar*]]
@@ -22,9 +23,11 @@
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.notifications.badge :refer [badge-notification]]
[app.render-wasm.api :as wasm.api]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[app.util.timers :as timers]
[cuerdas.core :as str]
[okulary.core :as l]
[rumext.v2 :as mf]))
@@ -52,6 +55,8 @@
refs/workspace-data
=))
;; --- Page Item
(mf/defc page-item
@@ -63,6 +68,22 @@
navigate-fn (mf/use-fn (mf/deps id) #(st/emit! :interrupt (dcm/go-to-workspace :page-id id)))
read-only? (mf/use-ctx ctx/workspace-read-only?)
on-click
(mf/use-fn
(mf/deps id)
(fn []
;; when using the wasm renderer, apply a blur effect to the viewport canvas
(if (features/active-feature? @st/state "render-wasm/v1")
(do
(wasm.api/capture-canvas-pixels)
(wasm.api/apply-canvas-blur)
;; NOTE: it seems we need two RAF so the blur is actually applied and visible
;; in the canvas :(
(timers/raf
(fn []
(timers/raf navigate-fn))))
(navigate-fn))))
on-delete
(mf/use-fn
(mf/deps id)
@@ -155,7 +176,7 @@
:selected selected?)
:data-testid (dm/str "page-" id)
:tab-index "0"
:on-click navigate-fn
:on-click on-click
:on-double-click on-double-click
:on-context-menu on-context-menu}
[:div {:class (stl/css :page-icon)}

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

@@ -312,6 +312,11 @@
(js/console.error "Error initializing canvas context:" e)
false))]
(reset! canvas-init? init?)
(when init?
;; Restore previous canvas pixels immediately after context initialization
;; This happens before initialize-viewport is called
(wasm.api/apply-canvas-blur)
(wasm.api/restore-previous-canvas-pixels))
(when-not init?
(js/alert "WebGL not supported")
(st/emit! (dcm/go-to-dashboard-recent))))))))
@@ -340,6 +345,7 @@
(mf/with-effect [@canvas-init? zoom vbox background]
(when (and @canvas-init? (not @initialized?))
(wasm.api/clear-canvas-pixels)
(wasm.api/initialize-viewport base-objects zoom vbox background)
(reset! initialized? true)))

View File

@@ -10,6 +10,7 @@
["react-dom/server" :as rds]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.files.helpers :as cfh]
[app.common.logging :as log]
[app.common.math :as mth]
@@ -21,7 +22,6 @@
[app.common.types.text :as txt]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.render-wasm :as drw]
[app.main.refs :as refs]
[app.main.render :as render]
[app.main.store :as st]
@@ -29,6 +29,7 @@
[app.main.worker :as mw]
[app.render-wasm.api.fonts :as f]
[app.render-wasm.api.texts :as t]
[app.render-wasm.api.webgl :as webgl]
[app.render-wasm.deserializers :as dr]
[app.render-wasm.helpers :as h]
[app.render-wasm.mem :as mem]
@@ -37,7 +38,6 @@
[app.render-wasm.serializers :as sr]
[app.render-wasm.serializers.color :as sr-clr]
[app.render-wasm.svg-filters :as svg-filters]
;; FIXME: rename; confunsing name
[app.render-wasm.wasm :as wasm]
[app.util.debug :as dbg]
[app.util.dom :as dom]
@@ -279,30 +279,6 @@
[string]
(+ (count string) 1))
(defn- create-webgl-texture-from-image
"Creates a WebGL texture from an HTMLImageElement or ImageBitmap and returns the texture object"
[gl image-element]
(let [texture (.createTexture ^js gl)]
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-element)
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
texture))
(defn- get-webgl-context
"Gets the WebGL context from the WASM module"
[]
(when wasm/context-initialized?
(let [gl-obj (unchecked-get wasm/internal-module "GL")]
(when gl-obj
;; Get the current WebGL context from Emscripten
;; The GL object has a currentContext property that contains the context handle
(let [current-ctx (.-currentContext ^js gl-obj)]
(when current-ctx
(.-GLctx ^js current-ctx)))))))
(defn- get-texture-id-for-gl-object
"Registers a WebGL texture with Emscripten's GL object system and returns its ID"
@@ -332,8 +308,8 @@
(->> (retrieve-image url)
(rx/map
(fn [img]
(when-let [gl (get-webgl-context)]
(let [texture (create-webgl-texture-from-image gl img)
(when-let [gl (webgl/get-webgl-context)]
(let [texture (webgl/create-webgl-texture-from-image gl img)
texture-id (get-texture-id-for-gl-object texture)
width (.-width ^js img)
height (.-height ^js img)
@@ -979,6 +955,7 @@
(set-shape-grow-type grow-type))
(set-shape-layout shape)
(set-layout-data shape)
(set-shape-selrect selrect)
(let [pending_thumbnails (into [] (concat
@@ -1236,7 +1213,8 @@
(dom/prevent-default event)
(reset! wasm/context-lost? true)
(log/warn :hint "WebGL context lost")
(st/emit! (drw/context-lost)))
(ex/raise :type :webgl-context-lost
:hint "WebGL context lost"))
(defn init-canvas-context
[canvas]
@@ -1447,6 +1425,12 @@
result)))
(defn apply-canvas-blur
[]
(when wasm/canvas
(dom/set-style! wasm/canvas "filter" "blur(4px)")))
(defn init-wasm-module
[module]
(let [default-fn (unchecked-get module "default")
@@ -1468,3 +1452,8 @@
(js/console.error cause)
(p/resolved false)))))
(p/resolved false))))
;; Re-export public WebGL functions
(def capture-canvas-pixels webgl/capture-canvas-pixels)
(def restore-previous-canvas-pixels webgl/restore-previous-canvas-pixels)
(def clear-canvas-pixels webgl/clear-canvas-pixels)

View File

@@ -0,0 +1,166 @@
;; 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.render-wasm.api.webgl
"WebGL utilities for pixel capture and rendering"
(:require
[app.common.logging :as log]
[app.render-wasm.wasm :as wasm]
[app.util.dom :as dom]))
(defn get-webgl-context
"Gets the WebGL context from the WASM module"
[]
(when wasm/context-initialized?
(let [gl-obj (unchecked-get wasm/internal-module "GL")]
(when gl-obj
;; Get the current WebGL context from Emscripten
;; The GL object has a currentContext property that contains the context handle
(let [current-ctx (.-currentContext ^js gl-obj)]
(when current-ctx
(.-GLctx ^js current-ctx)))))))
(defn create-webgl-texture-from-image
"Creates a WebGL texture from an HTMLImageElement or ImageBitmap and returns the texture object"
[gl image-element]
(let [texture (.createTexture ^js gl)]
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-element)
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
texture))
;; FIXME: temporary function until we are able to keep the same <canvas> across pages.
(defn- draw-imagedata-to-webgl
"Draws ImageData to a WebGL2 context by creating a texture"
[gl image-data]
(let [width (.-width ^js image-data)
height (.-height ^js image-data)
texture (.createTexture ^js gl)]
;; Bind texture and set parameters
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_S ^js gl) (.-CLAMP_TO_EDGE ^js gl))
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_WRAP_T ^js gl) (.-CLAMP_TO_EDGE ^js gl))
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MIN_FILTER ^js gl) (.-LINEAR ^js gl))
(.texParameteri ^js gl (.-TEXTURE_2D ^js gl) (.-TEXTURE_MAG_FILTER ^js gl) (.-LINEAR ^js gl))
(.texImage2D ^js gl (.-TEXTURE_2D ^js gl) 0 (.-RGBA ^js gl) (.-RGBA ^js gl) (.-UNSIGNED_BYTE ^js gl) image-data)
;; Set up viewport
(.viewport ^js gl 0 0 width height)
;; Vertex & Fragment shaders
;; Since we are only calling this function once (on page switch), we don't need
;; to cache the compiled shaders somewhere else (cannot be reused in a differen context).
(let [vertex-shader-source "#version 300 es
in vec2 a_position;
in vec2 a_texCoord;
out vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}"
fragment-shader-source "#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform sampler2D u_texture;
out vec4 fragColor;
void main() {
fragColor = texture(u_texture, v_texCoord);
}"
vertex-shader (.createShader ^js gl (.-VERTEX_SHADER ^js gl))
fragment-shader (.createShader ^js gl (.-FRAGMENT_SHADER ^js gl))
program (.createProgram ^js gl)]
(.shaderSource ^js gl vertex-shader vertex-shader-source)
(.compileShader ^js gl vertex-shader)
(when-not (.getShaderParameter ^js gl vertex-shader (.-COMPILE_STATUS ^js gl))
(log/error :hint "Vertex shader compilation failed"
:log (.getShaderInfoLog ^js gl vertex-shader)))
(.shaderSource ^js gl fragment-shader fragment-shader-source)
(.compileShader ^js gl fragment-shader)
(when-not (.getShaderParameter ^js gl fragment-shader (.-COMPILE_STATUS ^js gl))
(log/error :hint "Fragment shader compilation failed"
:log (.getShaderInfoLog ^js gl fragment-shader)))
(.attachShader ^js gl program vertex-shader)
(.attachShader ^js gl program fragment-shader)
(.linkProgram ^js gl program)
(if (.getProgramParameter ^js gl program (.-LINK_STATUS ^js gl))
(do
(.useProgram ^js gl program)
;; Create full-screen quad vertices (normalized device coordinates)
(let [position-location (.getAttribLocation ^js gl program "a_position")
texcoord-location (.getAttribLocation ^js gl program "a_texCoord")
position-buffer (.createBuffer ^js gl)
texcoord-buffer (.createBuffer ^js gl)
positions #js [-1.0 -1.0 1.0 -1.0 -1.0 1.0 -1.0 1.0 1.0 -1.0 1.0 1.0]
texcoords #js [0.0 0.0 1.0 0.0 0.0 1.0 0.0 1.0 1.0 0.0 1.0 1.0]]
;; Set up position buffer
(.bindBuffer ^js gl (.-ARRAY_BUFFER ^js gl) position-buffer)
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. positions) (.-STATIC_DRAW ^js gl))
(.enableVertexAttribArray ^js gl position-location)
(.vertexAttribPointer ^js gl position-location 2 (.-FLOAT ^js gl) false 0 0)
;; Set up texcoord buffer
(.bindBuffer ^js gl (.-ARRAY_BUFFER ^js gl) texcoord-buffer)
(.bufferData ^js gl (.-ARRAY_BUFFER ^js gl) (js/Float32Array. texcoords) (.-STATIC_DRAW ^js gl))
(.enableVertexAttribArray ^js gl texcoord-location)
(.vertexAttribPointer ^js gl texcoord-location 2 (.-FLOAT ^js gl) false 0 0)
;; Set texture uniform
(.activeTexture ^js gl (.-TEXTURE0 ^js gl))
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) texture)
(let [texture-location (.getUniformLocation ^js gl program "u_texture")]
(.uniform1i ^js gl texture-location 0))
;; draw
(.drawArrays ^js gl (.-TRIANGLES ^js gl) 0 6)
;; cleanup
(.deleteBuffer ^js gl position-buffer)
(.deleteBuffer ^js gl texcoord-buffer)
(.deleteShader ^js gl vertex-shader)
(.deleteShader ^js gl fragment-shader)
(.deleteProgram ^js gl program)))
(log/error :hint "Program linking failed"
:log (.getProgramInfoLog ^js gl program)))
(.bindTexture ^js gl (.-TEXTURE_2D ^js gl) nil)
(.deleteTexture ^js gl texture))))
(defn restore-previous-canvas-pixels
"Restores previous canvas pixels into the new canvas"
[]
(when-let [previous-canvas-pixels wasm/canvas-pixels]
(when-let [gl wasm/gl-context]
(draw-imagedata-to-webgl gl previous-canvas-pixels)
(set! wasm/canvas-pixels nil))))
(defn clear-canvas-pixels
[]
(when wasm/canvas
(let [context wasm/gl-context]
(.clearColor ^js context 0 0 0 0.0)
(.clear ^js context (.-COLOR_BUFFER_BIT ^js context))
(.clear ^js context (.-DEPTH_BUFFER_BIT ^js context))
(.clear ^js context (.-STENCIL_BUFFER_BIT ^js context)))
(dom/set-style! wasm/canvas "filter" "none")
(set! wasm/canvas-pixels nil)))
(defn capture-canvas-pixels
"Captures the pixels of the viewport canvas"
[]
(when wasm/canvas
(let [context wasm/gl-context
width (.-width wasm/canvas)
height (.-height wasm/canvas)
buffer (js/Uint8ClampedArray. (* width height 4))
_ (.readPixels ^js context 0 0 width height (.-RGBA ^js context) (.-UNSIGNED_BYTE ^js context) buffer)
image-data (js/ImageData. buffer width height)]
(set! wasm/canvas-pixels image-data))))

View File

@@ -12,6 +12,8 @@
;; Reference to the HTML canvas element.
(defonce canvas nil)
;; Reference to the captured pixels of the canvas (for page switching effect)
(defonce canvas-pixels nil)
;; Reference to the Emscripten GL context wrapper.
(defonce gl-context-handle nil)
@@ -56,3 +58,4 @@
:stroke-linecap shared/RawStrokeLineCap
:stroke-linejoin shared/RawStrokeLineJoin
:fill-rule shared/RawFillRule})

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

@@ -242,7 +242,6 @@ export class SelectionController extends EventTarget {
continue;
}
let styleValue = element.style.getPropertyValue(styleName);
if (styleName === "font-family") {
styleValue = sanitizeFontFamily(styleValue);
}
@@ -277,22 +276,29 @@ export class SelectionController extends EventTarget {
this.#applyDefaultStylesToCurrentStyle();
const root = startNode.parentElement.parentElement.parentElement;
this.#applyStylesFromElementToCurrentStyle(root);
// FIXME: I don't like this approximation. Having to iterate nodes twice
// is bad for performance. I think we need another way of "computing"
// the cascade.
for (const textNode of this.#textNodeIterator.iterateFrom(
startNode,
endNode,
)) {
const paragraph = textNode.parentElement.parentElement;
if (startNode === endNode) {
const paragraph = startNode.parentElement.parentElement;
this.#applyStylesFromElementToCurrentStyle(paragraph);
}
for (const textNode of this.#textNodeIterator.iterateFrom(
startNode,
endNode,
)) {
const textSpan = textNode.parentElement;
this.#mergeStylesFromElementToCurrentStyle(textSpan);
const textSpan = startNode.parentElement;
this.#applyStylesFromElementToCurrentStyle(textSpan);
} else {
// FIXME: I don't like this approximation. Having to iterate nodes twice
// is bad for performance. I think we need another way of "computing"
// the cascade.
for (const textNode of this.#textNodeIterator.iterateFrom(
startNode,
endNode,
)) {
const paragraph = textNode.parentElement.parentElement;
this.#applyStylesFromElementToCurrentStyle(paragraph);
}
for (const textNode of this.#textNodeIterator.iterateFrom(
startNode,
endNode,
)) {
const textSpan = textNode.parentElement;
this.#mergeStylesFromElementToCurrentStyle(textSpan);
}
}
return this;
}

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"
@@ -1559,6 +1559,12 @@ msgstr "Old password is incorrect"
msgid "feedback.description"
msgstr "Description"
msgid "errors.webgl-context-lost.main-message"
msgstr "Oops! The canvas context was lost"
msgid "errors.webgl-context-lost.desc-message"
msgstr "WebGL has stopped working. Please reload the page to reset it"
#: src/app/main/ui/settings/feedback.cljs:122
msgid "feedback.description-placeholder"
msgstr "Please describe the reason of your feedback"
@@ -2521,6 +2527,9 @@ msgstr "Release notes"
msgid "labels.reload-file"
msgstr "Reload file"
msgid "labels.reload-page"
msgstr "Reload page"
#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs
#, unused
msgid "labels.remove"
@@ -7543,6 +7552,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 +8081,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 +8133,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 +8145,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 +8490,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 +8589,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"
@@ -1552,6 +1552,12 @@ msgstr "El email o la contraseña son incorrectos."
msgid "errors.wrong-old-password"
msgstr "La contraseña anterior no es correcta"
msgid "errors.webgl-context-lost.main-message"
msgstr "Ups! Se ha perdido el contexto del canvas"
msgid "errors.webgl-context-lost.desc-message"
msgstr "WebGL ha dejado de funcionar. Por favor, recarga la página para restaurarlo"
#: src/app/main/ui/settings/feedback.cljs:120
msgid "feedback.description"
msgstr "Descripción"
@@ -2502,6 +2508,9 @@ msgstr "Notas de versión"
msgid "labels.reload-file"
msgstr "Recargar archivo"
msgid "labels.reload-page"
msgstr "Recargar página"
#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs
#, unused
msgid "labels.remove"
@@ -4420,6 +4429,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 +7957,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 +8015,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 +8343,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 +8371,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 +8419,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 +8436,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

View File

@@ -491,7 +491,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
@@ -502,33 +502,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 ""
"فایل‌های دارای کتابخانه‌های مشترک در اکسپورت گنجانده می‌شوند و پیوند خود را حفظ "
"می‌کنند."
#: 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

@@ -381,7 +381,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Heinta %s standarafílur (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Útflyt deild søvn"
#: src/app/main/ui/exports/files.cljs:147

View File

@@ -573,7 +573,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Télécharger %s fichiers standard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr ""
"* Peut inclure les composants, les éléments graphiques, les couleurs et/ou "
"les polices de caractère."
@@ -586,35 +586,35 @@ msgstr ""
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Les fichiers avec des bibliothèques partagées seront inclus dans "
"l'exportation, en maintenant leur liaison."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Exporter les bibliothèques partagées"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Les bibliothèques partagées ne seront pas incluses dans l'exportation et "
"aucune ressource ne sera ajoutée à la bibliothèque. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr ""
"Considérer les ressources des bibliothèques partagées comme des objets "
"basiques"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Votre fichier sera exporté avec toutes les ressources externes fusionnées "
"dans la bibliothèque de fichiers."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr ""
"Inclure les ressources des bibliothèques partagées dans les bibliothèques "
"de fichiers"
@@ -7893,10 +7893,6 @@ msgstr "Valeur non valide : % n'est pas autorisé."
msgid "workspace.tokens.value-with-units"
msgstr "Valeur non valide : les unités ne sont pas autorisées."
#: 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 "Si vous renommez ce token, toute référence à son ancien nom sera incorrecte."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Ressources"

View File

File diff suppressed because it is too large Load Diff

View File

@@ -356,7 +356,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Descargar %s ficheiros estándar (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Pode incluir compoñentes, gráficos, cores e/ou fontes."
#: src/app/main/ui/exports/files.cljs:155
@@ -366,33 +366,33 @@ msgstr ""
"Que queres facer cos recursos?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Os ficheiros con bibliotecas compartidas incluiranse na exportación "
"mantendo os vínculos."
#: 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 ""
"As bibliotecas compartidas non se incluirán na exportación e non se "
"engadirán recursos á biblioteca. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Tratar os recursos da biblioteca compartida coma obxetos básicos"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"O teu ficheiro exportarase con todos os recursos externos metidos na "
"biblioteca do ficheiro."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Incluir os recursos de bibliotecas compartidas na biblioteca do ficheiro"
#: src/app/main/ui/exports/files.cljs:147

View File

@@ -421,7 +421,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Sauke %s cikakken kundi (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "*akwai sassan,hotuna,launuka,da/kozane-zane."
#: src/app/main/ui/exports/files.cljs:155
@@ -429,29 +429,29 @@ msgid "dashboard.export.explain"
msgstr "za ka iya fitar da kundi daya ko fiye ta hanyar tura taska. \"me \"*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr "Manhajar tura kundi ta kunshi fitarwa, tattali mahaxarsu."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "fitar da manhajar tura kundi"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr "manhajar tura kundi ba ta shiga cikin fitarwa, wani amfaniqarawa a taska. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "lura da bayanan da ke cikin manhajar tura kundi"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"za ka iya fitar da kundi tare da haxe muhimman abubuwa, na waje a "
"kunditaskira."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "tura taska ya qunshi bayanan da ke cikin kundin taskoki"
#: src/app/main/ui/exports/files.cljs:147

View File

@@ -1,6 +1,6 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"PO-Revision-Date: 2026-01-03 20:01+0000\n"
"Last-Translator: Yaron Shahrabani <sh.yaron@gmail.com>\n"
"Language-Team: Hebrew <https://hosted.weblate.org/projects/penpot/frontend/"
"he/>\n"
@@ -554,7 +554,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
@@ -564,28 +564,27 @@ 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
@@ -1775,9 +1774,8 @@ msgid "inspect.empty.select"
msgstr "ניתן לבחור צורה, לוח או קבוצה ולראות את המאפיינים והקוד שלהם"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "בחירת לשונית חקירה"
msgstr "פרטי שכבה"
#: src/app/main/ui/inspect/right_sidebar.cljs:137
msgid "inspect.multiple-selected"
@@ -7810,10 +7808,6 @@ msgstr "ערך שגוי: אסור %."
msgid "workspace.tokens.value-with-units"
msgstr "ערך שגוי: אסור יחידות."
#: 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 "שינוי שם האסימון הזה יפגע בכל הפניה לשם הישן שלו."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "משאבים"
@@ -8352,3 +8346,25 @@ msgstr "אורך השם חייב להיות תו אחד לפחות"
#: src/app/main/data/workspace/tokens/errors.cljs:85
msgid "workspace.tokens.invalid-text-decoration-token-value"
msgstr "ערך אסימון שגוי: מותר רק none, underline ו־strike-through"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more"
msgstr "פרטים נוספים"
msgid "subscription.settings.management-dialog.step-2-description"
msgstr ""
"אפשר להוסיף את פרטי התשלום שלך היית כדי להשאיר את המינוי שלך פעיל ונטול "
"תקלות גם אחרי תקופת הניסיון וגם כדי להמשיך לתמוך במיזם הקוד הפתוח שלנו. לא "
"יתבצע שום חיוב עדיין."
#: src/app/main/data/workspace/tokens/errors.cljs:93
msgid "workspace.tokens.invalid-font-family-token-value"
msgstr "ערך אסימון שגוי: אפשר להפנות לאסימון font-family (משפחת גופנים) בלבד"
#: src/app/main/data/workspace/tokens/errors.cljs
msgid "workspace.tokens.invalid-token-value-shadow"
msgstr "ערך שגוי: יש להפנות לאסימון הצללה מורכבת."
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:775
msgid "workspace.tokens.reference-composite-shadow"
msgstr "נא למלא כינוי הצללת אסימון"

View File

@@ -1,6 +1,6 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"PO-Revision-Date: 2025-12-27 05:00+0000\n"
"Last-Translator: VKing9 <vaibhavrathod2282@gmail.com>\n"
"Language-Team: Hindi <https://hosted.weblate.org/projects/penpot/frontend/"
"hi/>\n"
@@ -559,7 +559,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
@@ -569,34 +569,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 ""
"साझा की गई लाइब्रेरीज़ वाली फ़ाइलें निर्यात में शामिल की जाएँगी, और उनका लिंक बनाए रखा "
"जाएगा।"
#: 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 ""
"आपकी फ़ाइल निर्यात की जाएगी और सभी बाहरी एसेट्स(assets) फ़ाइल लाइब्रेरी में "
"विलय कर दिए जाएँगे।"
#: 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
@@ -7300,10 +7299,6 @@ msgstr "मान मान्य नहीं है"
msgid "workspace.tokens.value-with-units"
msgstr "अमान्य मान: इकाइयाँ अनुमति नहीं हैं।"
#: 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 "इस टोकन का नाम बदलने से इसके पुराने नाम के किसी भी संदर्भ टूट जाएंगे।"
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "एसेट्स"
@@ -7752,9 +7747,8 @@ msgid "inspect.color-space-label"
msgstr "रंग स्थान चुनें"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "निरीक्षण टैब चुनें"
msgstr "परत जानकारी"
#: src/app/main/ui/inspect/styles/panels/tokens_panel.cljs:26
msgid "inspect.tabs.styles.active-sets"
@@ -8444,3 +8438,19 @@ msgstr "यह संस्करण आपके द्वारा लॉक
#, unused
msgid "workspace.versions.tooltip.locked-version"
msgstr "लॉक किया गया संस्करण - केवल निर्माता ही इसे संशोधित कर सकता है"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more"
msgstr "और जानकारी"
#: src/app/main/data/workspace/tokens/errors.cljs:93
msgid "workspace.tokens.invalid-font-family-token-value"
msgstr "Invalid token value: आप केवल फ़ॉन्ट-परिवार token का संदर्भ ले सकते हैं"
#: src/app/main/data/workspace/tokens/errors.cljs
msgid "workspace.tokens.invalid-token-value-shadow"
msgstr "अमान्य मूल्य: एक समग्र छाया टोकन का संदर्भ देना चाहिए।।"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:775
msgid "workspace.tokens.reference-composite-shadow"
msgstr "एक token छाया आलिया दर्ज करें"

View File

@@ -529,7 +529,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Preuzmi %s standardne datoteke (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Može uključivati komponente, grafike, boje i/ili tipografije."
#: src/app/main/ui/exports/files.cljs:155
@@ -539,33 +539,33 @@ msgstr ""
"Što želiš učiniti s njihovim stavkama*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Datoteke sa zajedničkim bibliotekama bit će uključene u izvoz, održavajući "
"njihovu poveznicu."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Izvezi zajedničke biblioteke"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Zajedničke biblioteke neće biti uključene u izvoz i nikakve stavke neće "
"biti dodani u biblioteku. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Tretiraj stavke zajedničke biblioteke kao osnovne objekte"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Tvoja će datoteka biti izvezena sa svim vanjskim stavkama spojenim u "
"biblioteku datoteka."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Uključi stavke zajedničke biblioteke u biblioteke datoteka"
#: src/app/main/ui/exports/files.cljs:147
@@ -6477,10 +6477,6 @@ msgstr "Alati"
msgid "workspace.tokens.value-not-valid"
msgstr "Vrijednost nije važeća"
#: 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 "Preimenovanje ovog tokena prekinut će sve reference na njegov stari naziv."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Stavke"

View File

@@ -562,7 +562,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Unduh %s berkas standar (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Dapat mencakup komponen, grafik, warna dan/atau tipografi."
#: src/app/main/ui/exports/files.cljs:155
@@ -572,31 +572,31 @@ msgstr ""
"Apa yang ingin Anda lakukan dengan asetnya*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr "Berkas dengan pustaka bersama akan dimasukkan dalam hasil ekspor."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Ekspor pustaka bersama"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Pustaka bersama tidak akan dimasukkan dalam hasil ekspor dan tidak ada aset "
"yang akan ditambahkan ke pustaka. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Perlakukan aset pustaka terbagi sebagai objek dasar"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Berkas Anda akan diekspor dengan semua aset eksternal tergabung dalam "
"pustaka berkas."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Termasuk aset pustaka terbagi dalam pustaka berkas"
#: src/app/main/ui/exports/files.cljs:147
@@ -6855,10 +6855,6 @@ msgstr "Peralatan"
msgid "workspace.tokens.value-not-valid"
msgstr "Nilai tidak valid"
#: 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 "Mengubah nama token ini akan merusak referensi nama lamanya."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Aset"

View File

@@ -398,28 +398,28 @@ msgid "dashboard.export-standard-multi"
msgstr "Buto %s ederede tozuru etozu (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Nwere Ike ịgụnye ngwa , esereese gasị, agwụgwara na/ma ọ bụ akara nkụpụta."
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"A ga-atinye ederede nwere ọba ederede nkekọrịta ma gụnyere mbupu , ma "
"jidekwa ụkpụrụ njikọ ha"
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Bupu ọba ederede nkekọrịta"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Agaghị a tinye ọba ederede nkekọrịta na mbupu ma o nweghị ihe nnwe a ga a "
"tinye ọ a ederede . "
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Tinye ihe nnwe ọba ederede nkekọrịta n'ime ọ a ederede."
#: src/app/main/ui/exports/files.cljs:147

View File

@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2025-12-31 22:01+0000\n"
"Last-Translator: Nicola Bortoletto <nicola.bortoletto@live.com>\n"
"Language-Team: Italian <https://hosted.weblate.org/projects/penpot/frontend/"
"it/>\n"
"Language: it\n"
@@ -570,7 +570,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Scarica %s file standard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr ""
"* Può includere componenti, elementi grafici, colori e/o elementi "
"tipografici."
@@ -582,34 +582,33 @@ msgstr ""
"cosa desideri fare con le loro risorse*?"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"I file con librerie condivise verranno inclusi nell'esportazione, mantenendo "
"il loro collegamento."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Esporta le librerie condivise"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Le librerie condivise non saranno incluse nell'esportazione e nessuna "
"risorsa verrà aggiunta alla libreria. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Considera le risorse delle librerie condivise come oggetti di base"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Il tuo file verrà esportato con tutte le risorse esterne riunite nella "
"libreria dei file."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Includi le risorse delle librerie condivise nelle librerie del file"
#: src/app/main/ui/exports/files.cljs:147
@@ -1832,7 +1831,6 @@ msgstr ""
"loro proprietà e il loro codice"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "Informazioni livello"
@@ -7940,12 +7938,6 @@ msgstr "Valore non valido: % non è consentito."
msgid "workspace.tokens.value-with-units"
msgstr "Valore non valido: le unità non sono consentite."
#: 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 ""
"Rinominare questo token interromperà qualsiasi riferimento al suo vecchio "
"nome."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Risorse"
@@ -8506,3 +8498,13 @@ msgstr "Il nome deve essere di almeno 1 carattere"
msgid "subscription.settings.management-dialog.step-2-skip-button"
msgstr "Salta per ora e inizia la prova"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more"
msgstr "Maggiori informazioni"
#: src/app/main/data/workspace/tokens/errors.cljs:93
msgid "workspace.tokens.invalid-font-family-token-value"
msgstr ""
"Valore del token non valido: puoi fare riferimento solo a un token font-"
"family"

View File

@@ -299,7 +299,7 @@ msgid "dashboard.export-shapes.title"
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/dashboard/fonts.cljs:206

View File

@@ -334,7 +334,7 @@ msgid "dashboard.export-shapes.title"
msgstr "Eksportuoti pažymėtą sritį"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Gali apimti komponentus, grafiką, spalvas ir (arba) tipografiją."
#: src/app/main/ui/exports/files.cljs:155
@@ -344,35 +344,35 @@ msgstr ""
"bendros bibliotekos. Ką norite daryti su jų komponentais*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Failai su bendromis bibliotekomis bus įtraukti į eksportą, išlaikant jų "
"susiejimą."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Eksportuoti bendrai naudojamas bibliotekas"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Bendrai naudojamos bibliotekos nebus įtrauktos į eksportą ir į biblioteką "
"nebus pridėta jokių išteklių. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr ""
"Bendrai naudojamus bibliotekos komponentus traktuokite kaip pagrindinius "
"objektus"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Jūsų failas bus eksportuotas su visais išoriniais komponentais, sujungtais "
"į failų biblioteką."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Įtraukti bendrai naudojamus bibliotekos komponentus į failų bibliotekas"
#: src/app/main/ui/exports/files.cljs:147

View File

@@ -1,6 +1,6 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-11-25 16:51+0000\n"
"PO-Revision-Date: 2025-12-29 14:34+0000\n"
"Last-Translator: Edgars Andersons <Edgars+Weblate@gaitenis.id.lv>\n"
"Language-Team: Latvian <https://hosted.weblate.org/projects/penpot/frontend/"
"lv/>\n"
@@ -8,10 +8,9 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural="
"(n % 10 == 0 || n % 100 >= 11 && n % 100 <= 19) ? 0 : "
"((n % 10 == 1 && n % 100 != 11) ? 1 : 2);\n"
"X-Generator: Weblate 5.15-dev\n"
"Plural-Forms: nplurals=3; plural=(n % 10 == 0 || n % 100 >= 11 && n % 100 <= "
"19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2);\n"
"X-Generator: Weblate 5.15.1\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
@@ -572,7 +571,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Lejupielādēt %s standarta datnes (. svg +. json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* var ietvert sastāvdaļas, attēlus, krāsas un/vai burtu stilus un veidus."
#: src/app/main/ui/exports/files.cljs:155
@@ -582,33 +581,33 @@ msgstr ""
"iesākt ar to līdzekļiem*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Izguvē tiks iekļautas datnes ar koplietojamām bibliotēkām, saglabājot to "
"sasaisti."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Izgūt koplietojamās bibliotēkas"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Koplietojamās bibliotēkas netiks iekļautas izguvē, un bibliotēkai netiks "
"pievienoti līdzekļi. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Attiekties pret koplietojamo bibliotēku līdzekļiem kā pret pamatobjektiem"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Datne tiks izgūta ar visiem ārējiem līdzekļiem, kas tiks apvienoti datnes "
"bibliotēkā."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Iekļaut koplietojamos bibliotēkas līdzekļus datņu bibliotēkās"
#: src/app/main/ui/exports/files.cljs:147
@@ -7582,12 +7581,6 @@ msgstr "Vērtība nav derīga"
msgid "workspace.tokens.value-with-units"
msgstr "Nederīga vērtība: mērvienības nav atļautas."
#: 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 ""
"Šīs tekstvienības pārdēvēšana salauzīs visas atsauces uz tās iepriekšējo "
"nosaukumu."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Līdzekļi"
@@ -7971,3 +7964,7 @@ msgstr "Pagaidām izlaist un uzsākt izmēģinājumu"
msgid "subscription.settings.management-dialog.step-2-add-payment-button"
msgstr "Pievienot maksājumu informāciju"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more"
msgstr "Vairāk informācijas"

View File

@@ -226,7 +226,7 @@ msgid "dashboard.export-shapes"
msgstr "എക്സ്പോർട്ട്"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* ഘടകങ്ങൾ, ഗ്രാഫിക്സ്, നിറങ്ങൾ അല്ലെങ്കിൽ മുദ്രണകലകൾ എന്നിവ ഉൾപ്പെടാം."
#: src/app/main/ui/auth/recovery.cljs:32

View File

@@ -426,7 +426,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Muat turun %s fail standard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Mungkin termasuk komponen, grafik, warna dan/atau tipografi."
#: src/app/main/ui/exports/files.cljs:155
@@ -436,33 +436,33 @@ msgstr ""
"kongsi. Apa yang anda mahu lakukan dengan aset mereka*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Fail dengan perpustakaan kongsi akan disertakan dalam eksport, mengekalkan "
"hubungannya."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Eksport perpustakaan kongsi"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Perpustakaan kongsi tidak akan disertakan dalam eksport dan tiada aset akan "
"ditambahkan ke perpustakaan. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Anggap aset perpustakaan kongsi sebagai objek asas"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Fail anda akan dieksport dengan semua aset luaran digabungkan ke dalam "
"pustaka fail."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Sertakan aset perpustakaan kongsi dalam pustaka fail"
#: src/app/main/ui/exports/files.cljs:147

View File

@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2025-12-23 17:00+0000\n"
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>\n"
"Language-Team: Dutch <https://hosted.weblate.org/projects/penpot/frontend/"
"nl/>\n"
"Language: nl\n"
@@ -570,7 +570,7 @@ msgid "dashboard.export-standard-multi"
msgstr "%s standaardbestanden downloaden (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Kan componenten, afbeeldingen, kleuren en/of typografie bevatten."
#: src/app/main/ui/exports/files.cljs:155
@@ -580,34 +580,33 @@ msgstr ""
"bibliotheken. Wat wil je doen met hun assets*?"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Bestanden met gedeelde bibliotheken worden opgenomen in de export en hun "
"koppelingen worden behouden."
"Bestanden met gedeelde bibliotheken worden meegenomen in de export, incl. "
"behoud van hun koppelingen."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Gedeelde bibliotheken exporteren"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Gedeelde bibliotheken worden niet opgenomen in de export en er worden geen "
"assets aan de bibliotheek toegevoegd. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Gedeelde bibliotheek-assets als basisobjecten behandelen"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Je bestand wordt geëxporteerd met alle externe assets samengevoegd in de "
"bestandsbibliotheek."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Inclusief gedeelde bibliotheek-assets in bestandsbibliotheken"
#: src/app/main/ui/exports/files.cljs:147
@@ -1833,7 +1832,6 @@ msgstr ""
"inspecteren"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "Laaginfo"
@@ -7968,12 +7966,6 @@ msgstr "Ongeldige waarde: % is niet toegestaan."
msgid "workspace.tokens.value-with-units"
msgstr "Ongeldige waarde: Eenheden zijn niet toegestaan."
#: 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 ""
"Met het wijzigen van de naam van dit token, worden alle verwijzingen naar "
"de oude naam verbroken."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Assets"
@@ -8487,3 +8479,20 @@ msgstr "Nu overslaan en proefperiode starten"
msgid "subscription.settings.management-dialog.step-2-add-payment-button"
msgstr "Betalingsgegevens toevoegen"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more"
msgstr "Meer info"
#: src/app/main/data/workspace/tokens/errors.cljs:93
msgid "workspace.tokens.invalid-font-family-token-value"
msgstr ""
"Ongeldige tokenwaarde: je kunt alleen verwijzen naar een font-family-token"
#: src/app/main/data/workspace/tokens/errors.cljs
msgid "workspace.tokens.invalid-token-value-shadow"
msgstr "Ongeldige waarde: moet verwijzen naar een samengesteld schaduwtoken."
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:775
msgid "workspace.tokens.reference-composite-shadow"
msgstr "Voer een alias voor een schaduwtoken in"

View File

@@ -360,7 +360,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Pobierz %s plików standardowych (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Może zawierać komponenty, grafikę, kolory i/lub typografię."
#: src/app/main/ui/exports/files.cljs:155
@@ -370,33 +370,33 @@ msgstr ""
"udostępnionych. Co chcesz zrobić z ich zasobami*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Pliki z bibliotekami współdzielonymi zostaną uwzględnione w eksporcie, z "
"zachowaniem ich powiązania."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Eksportuj biblioteki udostępnione"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Biblioteki udostępnione nie zostaną uwzględnione w eksporcie i żadne zasoby "
"nie zostaną dodane do biblioteki. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Traktuj zasoby biblioteki współdzielonej jako podstawowe obiekty"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Twój plik zostanie wyeksportowany ze wszystkimi zasobami zewnętrznymi "
"połączonymi z biblioteką plików."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Uwzględnij zasoby bibliotek współdzielonych w bibliotekach plików"
#: src/app/main/ui/exports/files.cljs:147

View File

@@ -569,7 +569,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Baixar %s arquivos padrões (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Pode incluir componentes, gráficos, cores e/ou tipografias."
#: src/app/main/ui/exports/files.cljs:155
@@ -579,33 +579,33 @@ msgstr ""
"compartilhadas. O que você quer fazer com seus recursos*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Arquivos com bibliotecas compartilhadas serão incluídos na exportação, "
"mantendo seu vínculo."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Exportar bibliotecas compartilhadas"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Bibliotecas compartilhadas não serão incluídas na exportação e nenhum ativo "
"será adicionado a biblioteca. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Trate os ativos da biblioteca compartilhada 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 ""
"Seu arquivo será exportado com todos os ativos externos mesclados na "
"biblioteca de ativos do arquivo."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr ""
"Incluir ativos da biblioteca compartilhada na biblioteca de ativos do "
"arquivo"
@@ -5661,10 +5661,6 @@ msgstr "Ferramentas"
msgid "workspace.tokens.value-not-valid"
msgstr "O valor não é válido"
#: 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 "Renomear este token quebrará quaisquer referência para o nome antigo."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Ativos"

View File

@@ -543,7 +543,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Descarregar %s ficheiros standard (svg + json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Pode incluir componentes, gráficos, cores e/ou tipografia."
#: src/app/main/ui/exports/files.cljs:155
@@ -553,33 +553,33 @@ msgstr ""
"partilhadas. O que queres fazer com os recursos*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Ficheiros com bibliotecas partilhadas serão incluídos na exportação, "
"mantendo as ligações."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Exportar bibliotecas partilhadas"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Bibliotecas partilhadas não serão incluídas na exportação e nenhum recurso "
"será adicionado à biblioteca. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Trata os recursos da biblioteca partilhada 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 ""
"Os teus ficheiros serão exportados com todos os recursos externos "
"incorporados na biblioteca de ficheiros."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Incluir recursos da biblioteca partilhada em bibliotecas de ficheiros"
#: src/app/main/ui/exports/files.cljs:147

View File

@@ -578,7 +578,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Descarcă %s fișiere standard (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Poate include componente, elemente grafice, culori și/sau tipografii."
#: src/app/main/ui/exports/files.cljs:155
@@ -588,33 +588,33 @@ msgstr ""
"biblioteci partajate. Ce vrei să faci cu obiectele lor*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Fișierele cu biblioteci partajate vor fi incluse în export, menținându-le "
"legătura."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Exportă biblioteci partajate"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Bibliotecile partajate nu vor fi incluse în export și nu vor fi adăugate "
"elemente în bibliotecă. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Tratează obiectele bibliotecii partajate ca obiecte de bază"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Fișierul dvs. va fi exportat cu toate activele externe îmbinate în "
"biblioteca de fișiere."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Include obiectele bibliotecii partajate în bibliotecile de fișiere"
#: src/app/main/ui/exports/files.cljs:147
@@ -7980,10 +7980,6 @@ msgstr "Valoare invalidă: % nu este permis."
msgid "workspace.tokens.value-with-units"
msgstr "Valoare invalidă: Unitățile nu sunt permise."
#: 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 "Redenumirea acestui token va distruge orice referință la numele său vechi."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Obiecte"

View File

@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2025-12-30 16:00+0000\n"
"Last-Translator: Vin <k3kelm4vw@mozmail.com>\n"
"Language-Team: Russian <https://hosted.weblate.org/projects/penpot/frontend/"
"ru/>\n"
"Language: ru\n"
@@ -563,7 +563,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Скачать стандартные файлы (.svg + .json) (%s)"
#: 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
@@ -573,33 +573,32 @@ 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
@@ -932,7 +931,7 @@ msgstr "Снять статус общей библиотеки"
#: src/app/main/data/common.cljs:224
msgid "dashboard.removed-from-team"
msgstr "Вы больше не являетесь частью команды “%s“."
msgstr "Вы больше не являетесь частью команды \"%s\"."
#: src/app/main/ui/settings/profile.cljs:78
msgid "dashboard.save-settings"
@@ -1389,7 +1388,7 @@ msgstr "Изображение слишком большое для вставк
#: src/app/main/data/media.cljs:70, src/app/main/data/workspace/media.cljs:193
msgid "errors.media-type-mismatch"
msgstr "Формат медиа не соответует расширению файла."
msgstr "Формат медиа не соответствует расширению файла."
#: src/app/main/data/media.cljs:67, src/app/main/data/workspace/media.cljs:178, src/app/main/data/workspace/media.cljs:181, src/app/main/data/workspace/media.cljs:184, src/app/main/data/workspace/media.cljs:187
msgid "errors.media-type-not-allowed"
@@ -1554,7 +1553,7 @@ msgstr "Сообщество Penpot"
#: src/app/main/ui/settings/feedback.cljs:105
msgid "feedback.subject"
msgstr "Краткое описание"
msgstr "Тема"
#: src/app/main/ui/settings/feedback.cljs:102
msgid "feedback.subtitle"
@@ -1860,7 +1859,7 @@ msgstr "Копировать в буфер обмена"
#: src/app/main/ui/inspect/styles/style_box.cljs:22
msgid "inspect.tabs.styles.geometry-panel"
msgstr "Размер и Положение"
msgstr "Размер и положение"
#: src/app/main/ui/inspect/styles/style_box.cljs:60, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:179
msgid "inspect.tabs.styles.toggle-style"
@@ -1868,7 +1867,7 @@ msgstr "Переключить панель %s"
#: src/app/main/ui/inspect/styles/style_box.cljs:21
msgid "inspect.tabs.styles.token-panel"
msgstr "Наборы токенов и Темы"
msgstr "Наборы токенов и темы"
#: src/app/main/ui/inspect/styles/rows/color_properties_row.cljs:102, src/app/main/ui/inspect/styles/rows/properties_row.cljs:60
msgid "inspect.tabs.styles.token-resolved-value"
@@ -2013,7 +2012,7 @@ msgstr "Копировать цвет"
#: src/app/main/ui/dashboard/team.cljs:650
msgid "labels.copy-invitation-link"
msgstr "Копировать ссылку"
msgstr "Скопировать ссылку"
#: src/app/main/ui/workspace/sidebar/assets/groups.cljs:167, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:204
msgid "labels.create"
@@ -2173,7 +2172,7 @@ msgstr "Помощь"
#: src/app/main/ui/dashboard/sidebar.cljs:1020
msgid "labels.help-learning"
msgstr "Помощь и Обучение"
msgstr "Помощь и обучение"
#: src/app/main/ui/dashboard/templates.cljs:91
msgid "labels.hide"
@@ -3000,7 +2999,7 @@ msgstr ""
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
#, unused
msgid "modals.remove-shared-confirm.message"
msgstr "Удалить “%s” из общей библиотеки"
msgstr "Удалить \"%s\" из общей библиотеки"
#: src/app/main/ui/workspace/nudge.cljs:52
msgid "modals.small-nudge"
@@ -6491,3 +6490,209 @@ msgstr "Автосохранённые версии будут хранитьс
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "Нажмите для замыкания контура"
#: src/app/main/ui/dashboard/import.cljs:479
msgid "dashboard.import.import-message"
msgid_plural "dashboard.import.import-message"
msgstr[0] "1 файл был успешно импортирован."
msgstr[1] "Успешно импортировано файлов: %s"
#: src/app/main/ui/dashboard/subscription.cljs:114, src/app/main/ui/dashboard/subscription.cljs:157
msgid "subscription.dashboard.power-up.enterprise-plan"
msgstr "Корпоративный тариф"
#: src/app/main/ui/dashboard/subscription.cljs:109
msgid "subscription.dashboard.power-up.enterprise-trial.top-title"
msgstr "Корпоративный тариф (пробный)"
#: src/app/main/ui/dashboard/subscription.cljs:84
msgid "subscription.dashboard.power-up.professional.bottom-button"
msgstr "Прокачаться!"
#: src/app/main/ui/dashboard/subscription.cljs:82
msgid "subscription.dashboard.power-up.professional.top-title"
msgstr "Профессиональный тариф"
#: src/app/main/ui/dashboard/subscription.cljs:93
msgid "subscription.dashboard.power-up.trial.top-title"
msgstr "Безлимитный тариф (пробный)"
#: src/app/main/ui/dashboard/subscription.cljs:100, src/app/main/ui/dashboard/subscription.cljs:156
msgid "subscription.dashboard.power-up.unlimited-plan"
msgstr "Безлимитный тариф"
#: src/app/main/ui/dashboard/subscription.cljs:81, src/app/main/ui/dashboard/subscription.cljs:92, src/app/main/ui/dashboard/subscription.cljs:99, src/app/main/ui/dashboard/subscription.cljs:108, src/app/main/ui/dashboard/subscription.cljs:113
msgid "subscription.dashboard.power-up.your-subscription"
msgstr "Ваша подписка:"
#: src/app/main/ui/dashboard/subscription.cljs:137
msgid "subscription.dashboard.team-plan"
msgstr "Командный тариф"
#: src/app/main/ui/settings/subscription.cljs:202
msgid "subscription.settings.management.dialog.payment-explanation"
msgstr "Оплата после пробного периода. Кредитная карта сейчас не требуется."
#: src/app/main/ui/settings/subscription.cljs:154
msgid "subscription.settings.management.dialog.currently-editors-title"
msgid_plural "subscription.settings.management.dialog.currently-editors-title"
msgstr[0] ""
"В настоящее время в вашей команде есть %s человек, который может "
"редактировать."
msgstr[1] ""
"В настоящее время в ваших командах есть %s человек(а), которые могут "
"редактировать."
#: src/app/main/ui/dashboard/subscription.cljs:143, src/app/main/ui/settings/subscription.cljs:92, src/app/main/ui/settings/subscription.cljs:368, src/app/main/ui/settings/subscription.cljs:402, src/app/main/ui/settings/subscription.cljs:464
msgid "subscription.settings.unlimited"
msgstr "Безлимитный"
#: src/app/main/ui/dashboard/subscription.cljs:142, src/app/main/ui/settings/subscription.cljs:88, src/app/main/ui/settings/subscription.cljs:367, src/app/main/ui/settings/subscription.cljs:390
msgid "subscription.settings.unlimited-trial"
msgstr "Безлимитный (пробный)"
#: src/app/main/ui/settings/subscription.cljs:394, src/app/main/ui/settings/subscription.cljs:406, src/app/main/ui/settings/subscription.cljs:470
msgid "subscription.settings.unlimited.autosave-benefit"
msgstr "30-дневное автосохранение версий и восстановление файлов"
#: src/app/main/ui/dashboard/sidebar.cljs:879
msgid "labels.penpot-changelog"
msgstr "Журнал изменений Penpot"
#: src/app/main/ui/dashboard/subscription.cljs:83
msgid "subscription.dashboard.power-up.professional.bottom-description"
msgstr ""
"Дополнительное хранилище, восстановление файлов и многое другое для команд."
#: src/app/main/ui/settings/subscription.cljs:447
msgid "subscription.settings.member-since"
msgstr "Участник Penpot с: %s"
#: src/app/main/ui/settings/subscription.cljs:50
msgid "subscription.settings.recommended"
msgstr "Рекомендуется"
#: src/app/main/errors.cljs:309
msgid "errors.deprecated.contact.text"
msgstr "Связаться с нами"
#: src/app/main/ui/settings/feedback.cljs:143
msgid "feedback.other-ways-contact"
msgstr "Другие способы связаться с нами"
#: src/app/main/ui/settings/feedback.cljs:101
msgid "feedback.title-contact-us"
msgstr "Связаться с нами"
#: src/app/main/ui/settings/sidebar.cljs:136
msgid "labels.contact-us"
msgstr "Связаться с нами"
#: src/app/main/ui/settings/feedback.cljs:110, src/app/main/ui/settings/feedback.cljs:111
msgid "feedback.type"
msgstr "Тип"
#: src/app/main/ui/settings/feedback.cljs:115
msgid "feedback.type.doubt"
msgstr "Сомнение"
#: src/app/main/ui/settings/feedback.cljs:113
msgid "feedback.type.idea"
msgstr "Идея"
#: src/app/main/ui/settings/feedback.cljs:114
msgid "feedback.type.issue"
msgstr "Вопрос"
#: src/app/main/ui/settings/feedback.cljs:122
msgid "feedback.description-placeholder"
msgstr "Пожалуйста, опишите причину вашего обращения"
#: src/app/main/ui/workspace/sidebar/assets.cljs:172
msgid "workspace.assets.add-library"
msgstr "Добавить библиотеку"
#: src/app/main/ui/workspace/tokens/themes.cljs:36
msgid "labels.themes"
msgstr "Темы"
#: src/app/main/ui/workspace/tokens/themes/theme_selector.cljs:86
msgid "workspace.tokens.active-themes"
msgstr "Активных тем: %s"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:376
msgid "workspace.tokens.back-to-themes"
msgstr "К списку тем"
#: src/app/main/ui/workspace/tokens/themes/theme_selector.cljs:74
msgid "workspace.tokens.edit-themes"
msgstr "Изменить темы"
#: src/app/main/ui/workspace/context_menu.cljs:215
msgid "workspace.shape.menu.copy-paste-as"
msgstr "Скопировать/Вставить как..."
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:96
msgid "shortcuts.copy-link"
msgstr "Скопировать ссылку"
#: src/app/main/ui/workspace/context_menu.cljs:202
msgid "workspace.shape.menu.copy-link"
msgstr "Скопировать ссылку"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:165
msgid "shortcuts.rename"
msgstr "Переименовать"
#: src/app/main/ui/workspace/context_menu.cljs:328
msgid "workspace.shape.menu.rename"
msgstr "Переименовать"
#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:432
msgid "settings.remove-color"
msgstr "Удалить цвет"
#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs:118
msgid "workspace.options.blur-options.remove-blur"
msgstr "Удалить размытие"
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:226, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs:261
msgid "workspace.options.export.remove-export"
msgstr "Удалить экспорт"
#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs:223
msgid "workspace.options.fill.remove-fill"
msgstr "Удалить заливку"
#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs:156, src/app/main/ui/workspace/sidebar/options/rows/shadow_row.cljs:160
msgid "workspace.options.shadow-options.remove-shadow"
msgstr "Удалить тень"
#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs:201
msgid "workspace.options.stroke.remove-stroke"
msgstr "Удалить обводку"
#: src/app/main/ui/workspace/plugins.cljs:88
msgid "workspace.plugins.remove-plugin"
msgstr "Удалить плагин"
#: src/app/main/ui/inspect/right_sidebar.cljs:107, src/app/main/ui/inspect/styles.cljs:134
msgid "labels.styles"
msgstr "Стили"
#: src/app/main/ui/workspace/sidebar/versions.cljs:56
#, markdown
msgid "subscription.workspace.versions.warning.enterprise.subtext-owner"
msgstr "Если вы хотите увеличить этот лимит, напишите нам на [%s](mailto)"
#: src/app/main/ui/workspace/sidebar/versions.cljs:58
#, markdown
msgid "subscription.workspace.versions.warning.subtext-member"
msgstr ""
"Если вы хотите увеличить этот лимит, свяжитесь с владельцем команды: [%s]"
"(mailto)"
#: src/app/main/ui/workspace/sidebar/versions.cljs:57
#, markdown
msgid "subscription.workspace.versions.warning.subtext-owner"
msgstr "Если вы хотите увеличить этот лимит, [обновите тариф|target:self](%s)"

View File

@@ -479,7 +479,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
@@ -489,33 +489,33 @@ msgstr ""
"Шта желите да урадите са њиховим средстрвима*?"
#: src/app/main/ui/exports/files.cljs:164
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

@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2025-12-30 16:00+0000\n"
"Last-Translator: Anton Palmqvist <apq@users.noreply.hosted.weblate.org>\n"
"Language-Team: Swedish <https://hosted.weblate.org/projects/penpot/frontend/"
"sv/>\n"
"Language: sv\n"
@@ -570,7 +570,7 @@ msgid "dashboard.export-standard-multi"
msgstr "Ladda ner %s standardfiler (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Kan inkludera komponenter, grafik, färger och/eller typografier."
#: src/app/main/ui/exports/files.cljs:155
@@ -580,33 +580,33 @@ msgstr ""
"vill du göra med deras tillgångar*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Filer med delade bibliotek kommer att ingå i exporten, bibehåller deras "
"koppling."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Exportera delade bibliotek"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Delade bibliotek ingår inte i exporten och inga tillgångar kommer att "
"läggas till biblioteket. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Behandla delade bibliotekstillgångar som grundobjekt"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Din fil kommer att exporteras med alla externa tillgångar som sammanfogade "
"i filbiblioteket."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Inkludera delade bibliotekstillgångar i filbibliotek"
#: src/app/main/ui/exports/files.cljs:147
@@ -1809,7 +1809,6 @@ msgstr ""
"och kod"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "Lagerinfo"
@@ -4722,9 +4721,8 @@ msgid "subscription.settings.sucess.dialog.title"
msgstr "Du är %!"
#: src/app/main/ui/settings/subscription.cljs:440
#, fuzzy
msgid "subscription.settings.support-us-since"
msgstr "Du har stöttat oss med detta abonnemang sedan: %"
msgstr "Du har stöttat oss med detta abonnemang sedan: %s"
#: src/app/main/ui/settings/subscription.cljs:472, src/app/main/ui/settings/subscription.cljs:488
msgid "subscription.settings.try-it-free"
@@ -7756,7 +7754,6 @@ msgstr ""
"0,5)."
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:120
#, fuzzy
msgid "workspace.tokens.original-value"
msgstr "Ursprungligt värde: %s"
@@ -7782,7 +7779,6 @@ msgid "workspace.tokens.reference-error"
msgstr "Referensfel: "
#: src/app/main/data/workspace/tokens/warnings.cljs:15, src/app/main/data/workspace/tokens/warnings.cljs:19, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:56, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:84, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:102, src/app/main/ui/workspace/tokens/management/create/form_input_token.cljs:109, src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs:41, src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs:46, src/app/main/ui/workspace/tokens/management/token_pill.cljs:121
#, fuzzy
msgid "workspace.tokens.resolved-value"
msgstr "Uträknat värde: %s"
@@ -7856,7 +7852,6 @@ msgid "workspace.tokens.themes-list"
msgstr "Temalista"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:194, src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:195, src/app/main/ui/workspace/tokens/management/create/form.cljs:629, src/app/main/ui/workspace/tokens/management/create/form.cljs:630
#, fuzzy
msgid "workspace.tokens.token-description"
msgstr "Beskrivning"
@@ -7928,10 +7923,6 @@ msgstr "Ogiltigt värde: % är inte tillåtet."
msgid "workspace.tokens.value-with-units"
msgstr "Ogiltigt värde: Enheter är ej tillåtna."
#: 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 "Om du byter namn på denna token bryts alla referenser till dess gamla namn."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Tillgångar"
@@ -8441,3 +8432,20 @@ msgstr "En token finns redan på denna sökväg: %s"
#: 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 "Namnet måste innehålla minst 1 tecken"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more"
msgstr "Mer info"
#: src/app/main/data/workspace/tokens/errors.cljs:93
msgid "workspace.tokens.invalid-font-family-token-value"
msgstr ""
"Ogiltigt token-värde: du kan bara referera till en teckensnittsfamiljs-token"
#: src/app/main/data/workspace/tokens/errors.cljs
msgid "workspace.tokens.invalid-token-value-shadow"
msgstr "Ogiltigt värde: måste referera till en sammansatt skuggnings-token."
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:775
msgid "workspace.tokens.reference-composite-shadow"
msgstr "Ange ett token-skuggalias"

View File

@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2025-12-23 17:00+0000\n"
"Last-Translator: Oğuz Ersen <oguz@ersen.moe>\n"
"Language-Team: Turkish <https://hosted.weblate.org/projects/penpot/frontend/"
"tr/>\n"
"Language: tr\n"
@@ -571,7 +571,7 @@ msgid "dashboard.export-standard-multi"
msgstr "%s standart dosyayı indir (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* Bileşenleri, grafikleri, renkleri ve/veya tipografileri içerebilir."
#: src/app/main/ui/exports/files.cljs:155
@@ -581,33 +581,33 @@ msgstr ""
"kütüphaneleri kullanıyor. Bunların varlıklarıyla ne yapmak istiyorsunuz*?"
#: src/app/main/ui/exports/files.cljs:164
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"Paylaşılan kütüphanelere sahip dosyalar, bağlantılarını koruyarak dışarı "
"aktarmaya dahil edilecek."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "Paylaşılan kütüphaneleri dışarı aktar"
#: src/app/main/ui/exports/files.cljs:166
msgid "dashboard.export.options.detach.message"
msgid "files-download-modal.options.detach.message"
msgstr ""
"Paylaşılan kütüphaneler dışarı aktarmaya dahil edilmeyecek ve kütüphaneye "
"hiçbir varlık eklenmeyecek. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "Paylaşılan kütüphane varlıklarını temel nesneler olarak ele al"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"Dosyanız, tüm harici varlıklar kütüphane dosyasına birleştirilmiş olarak "
"dışarı aktarılacak."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "Dosya kütüphanelerine paylaşılan kütüphane varlıklarını dahil et"
#: src/app/main/ui/exports/files.cljs:147
@@ -1816,7 +1816,6 @@ msgstr ""
"seçin"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "Katman bilgisi"
@@ -7935,12 +7934,6 @@ msgstr "Geçersiz değer: % izin verilmiyor."
msgid "workspace.tokens.value-with-units"
msgstr "Geçersiz değer: Birimlere izin verilmiyor."
#: 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 ""
"Bu tokenin adını değiştirmek, eski adına yapılan tüm referansları "
"bozacaktır."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Varlıklar"
@@ -8455,3 +8448,20 @@ msgstr "Şimdilik atlayın ve deneme sürümünü başlatın"
msgid "subscription.settings.management-dialog.step-2-add-payment-button"
msgstr "Ödeme bilgilerini ekleyin"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more"
msgstr "Daha fazla bilgi"
#: src/app/main/data/workspace/tokens/errors.cljs:93
msgid "workspace.tokens.invalid-font-family-token-value"
msgstr ""
"Geçersiz token değeri: yalnızca font-family tokenine referans verebilirsiniz"
#: src/app/main/data/workspace/tokens/errors.cljs
msgid "workspace.tokens.invalid-token-value-shadow"
msgstr "Geçersiz değer: bileşik gölge tokenine referans vermelidir."
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:775
msgid "workspace.tokens.reference-composite-shadow"
msgstr "Bir token gölge takma adı girin"

View File

@@ -565,7 +565,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
@@ -575,33 +575,33 @@ msgstr ""
"плануєте зробити з їхніми ресурсами*?"
#: src/app/main/ui/exports/files.cljs:164
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
@@ -7437,10 +7437,6 @@ msgstr "Значення не є дійсним"
msgid "workspace.tokens.value-with-units"
msgstr "Помилкове значення: Одиниці не дозволені."
#: 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 "Якщо перейменувати токен, посилання на старе імʼя буде розірвано."
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "Ресурси"

View File

@@ -418,39 +418,39 @@ msgid "dashboard.export-standard-multi"
msgstr "danlóòdù %s àwọn ojúlówó fáìli (.svg + .json)"
#: src/app/main/ui/exports/files.cljs:156
msgid "dashboard.export.detail"
msgid "files-download-modal.description-2"
msgstr "* ó le ní àwọn ẹ̀yà ara, àwòrán àti àwon àwọ̀ àti / tàbí àtẹ̀jáde."
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgid "files-download-modal.options.all.message"
msgstr ""
"àwọn fáìli tí ó wà nínú yàrá ìkàwé pípín á dàpọ̀ mọ́ ti ìfiráńṣẹ́, fún "
"síṣetọ́jútheir linkage ìsopọ̀ wọn."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
msgid "files-download-modal.options.all.title"
msgstr "ṣe ìfiráńṣẹ́ yàrá ìkàwé pípín"
#: 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 "
"yàrá ìkàwé pípín kò ní sí nínú ti ìfiráńṣẹ́ àti pé kò ni ohun ìní kan tí "
"a ó fi kún yàrá ìkàwé. "
#: src/app/main/ui/exports/files.cljs:167
msgid "dashboard.export.options.detach.title"
msgid "files-download-modal.options.detach.title"
msgstr "ṣe ìtọ́jú ohun ìní iyàrá ìkàwé pípín bí i nǹkan tó jẹ́ kókó"
#: src/app/main/ui/exports/files.cljs:168
msgid "dashboard.export.options.merge.message"
msgid "files-download-modal.options.merge.message"
msgstr ""
"á fi fáìlì rẹ ráńṣẹ́ pẹ̀lú gbogbo àwọn ohun ìní tó wà láyìíka ni a ó papọ̀ "
"sínú fáìlì yàrá ìkàwé."
#: src/app/main/ui/exports/files.cljs:169
msgid "dashboard.export.options.merge.title"
msgid "files-download-modal.options.merge.title"
msgstr "da ohun ìní yàrá ìkàwé pípín pọ̀ mọ́ fáìlì yàrá ìkàwé"
#: src/app/main/ui/exports/files.cljs:147

View File

@@ -537,7 +537,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
@@ -546,27 +546,27 @@ 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

@@ -508,7 +508,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
@@ -517,27 +517,27 @@ 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
@@ -6295,10 +6295,6 @@ msgstr "工具"
msgid "workspace.tokens.value-not-valid"
msgstr "該值無效"
#: 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 "重新命名此權杖(token)將會中斷對其舊名稱的任何參照。"
#: src/app/main/ui/workspace/sidebar.cljs:139, src/app/main/ui/workspace/sidebar.cljs:146
msgid "workspace.toolbar.assets"
msgstr "資源"

View File

@@ -10,7 +10,6 @@ mod shadows;
mod strokes;
mod surfaces;
pub mod text;
mod ui;
use skia_safe::{self as skia, Matrix, RRect, Rect};
@@ -53,6 +52,25 @@ pub struct NodeRenderState {
mask: bool,
}
/// Get simplified children of a container, flattening nested flattened containers
fn get_simplified_children<'a>(tree: ShapesPoolRef<'a>, shape: &'a Shape) -> Vec<Uuid> {
let mut result = Vec::new();
for child_id in shape.children_ids_iter(false) {
if let Some(child) = tree.get(child_id) {
if child.can_flatten() {
// Child is flattened: recursively get its simplified children
result.extend(get_simplified_children(tree, child));
} else {
// Child is not flattened: add it directly
result.push(*child_id);
}
}
}
result
}
impl NodeRenderState {
pub fn is_root(&self) -> bool {
self.id.is_nil()
@@ -398,12 +416,7 @@ impl RenderState {
}
fn frame_clip_layer_blur(shape: &Shape) -> Option<Blur> {
match shape.shape_type {
Type::Frame(_) if shape.clip() => shape.blur.filter(|blur| {
!blur.hidden && blur.blur_type == BlurType::LayerBlur && blur.value > 0.
}),
_ => None,
}
shape.frame_clip_layer_blur()
}
/// Runs `f` with `ignore_nested_blurs` temporarily forced to `true`.
@@ -521,38 +534,59 @@ impl RenderState {
let paint = skia::Paint::default();
self.surfaces
.draw_into(SurfaceId::TextDropShadows, SurfaceId::Current, Some(&paint));
// Only draw surfaces that have content (dirty flag optimization)
if self.surfaces.is_dirty(SurfaceId::TextDropShadows) {
self.surfaces
.draw_into(SurfaceId::TextDropShadows, SurfaceId::Current, Some(&paint));
}
self.surfaces
.draw_into(SurfaceId::Fills, SurfaceId::Current, Some(&paint));
if self.surfaces.is_dirty(SurfaceId::Fills) {
self.surfaces
.draw_into(SurfaceId::Fills, SurfaceId::Current, Some(&paint));
}
let mut render_overlay_below_strokes = false;
if let Some(shape) = shape {
render_overlay_below_strokes = shape.has_fills();
}
if render_overlay_below_strokes {
if render_overlay_below_strokes && self.surfaces.is_dirty(SurfaceId::InnerShadows) {
self.surfaces
.draw_into(SurfaceId::InnerShadows, SurfaceId::Current, Some(&paint));
}
self.surfaces
.draw_into(SurfaceId::Strokes, SurfaceId::Current, Some(&paint));
if self.surfaces.is_dirty(SurfaceId::Strokes) {
self.surfaces
.draw_into(SurfaceId::Strokes, SurfaceId::Current, Some(&paint));
}
if !render_overlay_below_strokes {
if !render_overlay_below_strokes && self.surfaces.is_dirty(SurfaceId::InnerShadows) {
self.surfaces
.draw_into(SurfaceId::InnerShadows, SurfaceId::Current, Some(&paint));
}
let surface_ids = SurfaceId::Strokes as u32
| SurfaceId::Fills as u32
| SurfaceId::InnerShadows as u32
| SurfaceId::TextDropShadows as u32;
// Build mask of dirty surfaces that need clearing
let mut dirty_surfaces_to_clear = 0u32;
if self.surfaces.is_dirty(SurfaceId::Strokes) {
dirty_surfaces_to_clear |= SurfaceId::Strokes as u32;
}
if self.surfaces.is_dirty(SurfaceId::Fills) {
dirty_surfaces_to_clear |= SurfaceId::Fills as u32;
}
if self.surfaces.is_dirty(SurfaceId::InnerShadows) {
dirty_surfaces_to_clear |= SurfaceId::InnerShadows as u32;
}
if self.surfaces.is_dirty(SurfaceId::TextDropShadows) {
dirty_surfaces_to_clear |= SurfaceId::TextDropShadows as u32;
}
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas().clear(skia::Color::TRANSPARENT);
});
if dirty_surfaces_to_clear != 0 {
self.surfaces.apply_mut(dirty_surfaces_to_clear, |s| {
s.canvas().clear(skia::Color::TRANSPARENT);
});
// Clear dirty flags for surfaces we just cleared
self.surfaces.clear_dirty(dirty_surfaces_to_clear);
}
}
pub fn clear_focus_mode(&mut self) {
@@ -605,11 +639,90 @@ impl RenderState {
| strokes_surface_id as u32
| innershadows_surface_id as u32
| text_drop_shadows_surface_id as u32;
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas().save();
});
// Only save canvas state if we have clipping or transforms
// For simple shapes without clipping, skip expensive save/restore
let needs_save =
clip_bounds.is_some() || offset.is_some() || !shape.transform.is_identity();
if needs_save {
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas().save();
});
}
let antialias = shape.should_use_antialias(self.get_scale());
let fast_mode = self.options.is_fast_mode();
let has_nested_fills = self
.nested_fills
.last()
.is_some_and(|fills| !fills.is_empty());
let has_inherited_blur = !self.ignore_nested_blurs
&& self.nested_blurs.iter().flatten().any(|blur| {
!blur.hidden && blur.blur_type == BlurType::LayerBlur && blur.value > 0.0
});
let can_render_directly = apply_to_current_surface
&& clip_bounds.is_none()
&& offset.is_none()
&& parent_shadows.is_none()
&& !shape.needs_layer()
&& shape.blur.is_none()
&& !has_inherited_blur
&& shape.shadows.is_empty()
&& shape.transform.is_identity()
&& matches!(
shape.shape_type,
Type::Rect(_) | Type::Circle | Type::Path(_) | Type::Bool(_)
)
&& !(shape.fills.is_empty() && has_nested_fills)
&& !shape
.svg_attrs
.as_ref()
.is_some_and(|attrs| attrs.fill_none);
if can_render_directly {
let scale = self.get_scale();
let translation = self
.surfaces
.get_render_context_translation(self.render_area, scale);
self.surfaces.apply_mut(SurfaceId::Current as u32, |s| {
let canvas = s.canvas();
canvas.save();
canvas.scale((scale, scale));
canvas.translate(translation);
});
for fill in shape.fills().rev() {
fills::render(self, shape, fill, antialias, SurfaceId::Current);
}
for stroke in shape.visible_strokes().rev() {
strokes::render(
self,
shape,
stroke,
Some(SurfaceId::Current),
None,
antialias,
);
}
self.surfaces.apply_mut(SurfaceId::Current as u32, |s| {
s.canvas().restore();
});
if self.options.is_debug_visible() {
let shape_selrect_bounds = self.get_shape_selrect_bounds(shape);
debug::render_debug_shape(self, Some(shape_selrect_bounds), None);
}
if needs_save {
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas().restore();
});
}
return;
}
// set clipping
if let Some(clips) = clip_bounds.as_ref() {
@@ -666,6 +779,9 @@ impl RenderState {
} else if shape_has_blur {
shape.to_mut().set_blur(None);
}
if fast_mode {
shape.to_mut().set_blur(None);
}
let center = shape.center();
let mut matrix = shape.transform;
@@ -683,16 +799,18 @@ impl RenderState {
matrix.pre_concat(&svg_transform);
}
self.surfaces.canvas(fills_surface_id).concat(&matrix);
self.surfaces
.canvas_and_mark_dirty(fills_surface_id)
.concat(&matrix);
if let Some(svg) = shape.svg.as_ref() {
svg.render(self.surfaces.canvas(fills_surface_id))
svg.render(self.surfaces.canvas_and_mark_dirty(fills_surface_id));
} else {
let font_manager = skia::FontMgr::from(self.fonts().font_provider().clone());
let dom_result = skia::svg::Dom::from_str(&sr.content, font_manager);
match dom_result {
Ok(dom) => {
dom.render(self.surfaces.canvas(fills_surface_id));
dom.render(self.surfaces.canvas_and_mark_dirty(fills_surface_id));
shape.to_mut().set_svg(dom);
}
Err(e) => {
@@ -708,18 +826,8 @@ impl RenderState {
});
let text_content = text_content.new_bounds(shape.selrect());
let mut drop_shadows = shape.drop_shadow_paints();
if let Some(inherited_shadows) = self.get_inherited_drop_shadows() {
drop_shadows.extend(inherited_shadows);
}
let inner_shadows = shape.inner_shadow_paints();
let blur_filter = shape.image_filter(1.);
let count_inner_strokes = shape.count_visible_inner_strokes();
let mut paragraph_builders = text_content.paragraph_builder_group_from_text(None);
let mut paragraphs_with_shadows =
text_content.paragraph_builder_group_from_text(Some(true));
let mut stroke_paragraphs_list = shape
.visible_strokes()
.rev()
@@ -733,62 +841,8 @@ impl RenderState {
)
})
.collect::<Vec<_>>();
let mut stroke_paragraphs_with_shadows_list = shape
.visible_strokes()
.rev()
.map(|stroke| {
text::stroke_paragraph_builder_group_from_text(
&text_content,
stroke,
&shape.selrect(),
count_inner_strokes,
Some(true),
)
})
.collect::<Vec<_>>();
if let Some(parent_shadows) = parent_shadows {
if !shape.has_visible_strokes() {
for shadow in parent_shadows {
text::render(
Some(self),
None,
&shape,
&mut paragraphs_with_shadows,
text_drop_shadows_surface_id.into(),
Some(&shadow),
blur_filter.as_ref(),
);
}
} else {
shadows::render_text_shadows(
self,
&shape,
&mut paragraphs_with_shadows,
&mut stroke_paragraphs_with_shadows_list,
text_drop_shadows_surface_id.into(),
&parent_shadows,
&blur_filter,
);
}
} else {
// 1. Text drop shadows
if !shape.has_visible_strokes() {
for shadow in &drop_shadows {
text::render(
Some(self),
None,
&shape,
&mut paragraphs_with_shadows,
text_drop_shadows_surface_id.into(),
Some(shadow),
blur_filter.as_ref(),
);
}
}
// 2. Text fills
if fast_mode {
// Fast path: render fills and strokes only (skip shadows/blur).
text::render(
Some(self),
None,
@@ -796,21 +850,9 @@ impl RenderState {
&mut paragraph_builders,
Some(fills_surface_id),
None,
blur_filter.as_ref(),
None,
);
// 3. Stroke drop shadows
shadows::render_text_shadows(
self,
&shape,
&mut paragraphs_with_shadows,
&mut stroke_paragraphs_with_shadows_list,
text_drop_shadows_surface_id.into(),
&drop_shadows,
&blur_filter,
);
// 4. Stroke fills
for stroke_paragraphs in stroke_paragraphs_list.iter_mut() {
text::render(
Some(self),
@@ -819,34 +861,134 @@ impl RenderState {
stroke_paragraphs,
Some(strokes_surface_id),
None,
blur_filter.as_ref(),
None,
);
}
} else {
let mut drop_shadows = shape.drop_shadow_paints();
// 5. Stroke inner shadows
shadows::render_text_shadows(
self,
&shape,
&mut paragraphs_with_shadows,
&mut stroke_paragraphs_with_shadows_list,
Some(innershadows_surface_id),
&inner_shadows,
&blur_filter,
);
if let Some(inherited_shadows) = self.get_inherited_drop_shadows() {
drop_shadows.extend(inherited_shadows);
}
// 6. Fill Inner shadows
if !shape.has_visible_strokes() {
for shadow in &inner_shadows {
let inner_shadows = shape.inner_shadow_paints();
let blur_filter = shape.image_filter(1.);
let mut paragraphs_with_shadows =
text_content.paragraph_builder_group_from_text(Some(true));
let mut stroke_paragraphs_with_shadows_list = shape
.visible_strokes()
.rev()
.map(|stroke| {
text::stroke_paragraph_builder_group_from_text(
&text_content,
stroke,
&shape.selrect(),
count_inner_strokes,
Some(true),
)
})
.collect::<Vec<_>>();
if let Some(parent_shadows) = parent_shadows {
if !shape.has_visible_strokes() {
for shadow in parent_shadows {
text::render(
Some(self),
None,
&shape,
&mut paragraphs_with_shadows,
text_drop_shadows_surface_id.into(),
Some(&shadow),
blur_filter.as_ref(),
);
}
} else {
shadows::render_text_shadows(
self,
&shape,
&mut paragraphs_with_shadows,
&mut stroke_paragraphs_with_shadows_list,
text_drop_shadows_surface_id.into(),
&parent_shadows,
&blur_filter,
);
}
} else {
// 1. Text drop shadows
if !shape.has_visible_strokes() {
for shadow in &drop_shadows {
text::render(
Some(self),
None,
&shape,
&mut paragraphs_with_shadows,
text_drop_shadows_surface_id.into(),
Some(shadow),
blur_filter.as_ref(),
);
}
}
// 2. Text fills
text::render(
Some(self),
None,
&shape,
&mut paragraph_builders,
Some(fills_surface_id),
None,
blur_filter.as_ref(),
);
// 3. Stroke drop shadows
shadows::render_text_shadows(
self,
&shape,
&mut paragraphs_with_shadows,
&mut stroke_paragraphs_with_shadows_list,
text_drop_shadows_surface_id.into(),
&drop_shadows,
&blur_filter,
);
// 4. Stroke fills
for stroke_paragraphs in stroke_paragraphs_list.iter_mut() {
text::render(
Some(self),
None,
&shape,
&mut paragraphs_with_shadows,
Some(innershadows_surface_id),
Some(shadow),
stroke_paragraphs,
Some(strokes_surface_id),
None,
blur_filter.as_ref(),
);
}
// 5. Stroke inner shadows
shadows::render_text_shadows(
self,
&shape,
&mut paragraphs_with_shadows,
&mut stroke_paragraphs_with_shadows_list,
Some(innershadows_surface_id),
&inner_shadows,
&blur_filter,
);
// 6. Fill Inner shadows
if !shape.has_visible_strokes() {
for shadow in &inner_shadows {
text::render(
Some(self),
None,
&shape,
&mut paragraphs_with_shadows,
Some(innershadows_surface_id),
Some(shadow),
blur_filter.as_ref(),
);
}
}
}
}
}
@@ -886,16 +1028,25 @@ impl RenderState {
None,
antialias,
);
shadows::render_stroke_inner_shadows(
if !fast_mode {
shadows::render_stroke_inner_shadows(
self,
shape,
stroke,
antialias,
innershadows_surface_id,
);
}
}
if !fast_mode {
shadows::render_fill_inner_shadows(
self,
shape,
stroke,
antialias,
innershadows_surface_id,
);
}
shadows::render_fill_inner_shadows(self, shape, antialias, innershadows_surface_id);
// bools::debug_render_bool_paths(self, shape, shapes, modifiers, structure);
}
};
@@ -908,9 +1059,13 @@ impl RenderState {
if apply_to_current_surface {
self.apply_drawing_to_render_canvas(Some(&shape));
}
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas().restore();
});
// Only restore if we saved (optimization for simple shapes)
if needs_save {
self.surfaces.apply_mut(surface_ids, |s| {
s.canvas().restore();
});
}
}
pub fn update_render_context(&mut self, tile: tiles::Tile) {
@@ -1031,6 +1186,7 @@ impl RenderState {
// reorder by distance to the center.
self.current_tile = None;
self.render_in_progress = true;
self.apply_drawing_to_render_canvas(None);
if sync_render {
@@ -1117,18 +1273,6 @@ impl RenderState {
self.nested_fills.push(Vec::new());
}
let mut paint = skia::Paint::default();
paint.set_blend_mode(element.blend_mode().into());
paint.set_alpha_f(element.opacity());
if let Some(frame_blur) = Self::frame_clip_layer_blur(element) {
let scale = self.get_scale();
let sigma = frame_blur.value * scale;
if let Some(filter) = skia::image_filters::blur((sigma, sigma), None, None, None) {
paint.set_image_filter(filter);
}
}
// When we're rendering the mask shape we need to set a special blend mode
// called 'destination-in' that keeps the drawn content within the mask.
// @see https://skia.org/docs/user/api/skblendmode_overview/
@@ -1141,16 +1285,40 @@ impl RenderState {
.save_layer(&mask_rec);
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
self.surfaces
.canvas(SurfaceId::Current)
.save_layer(&layer_rec);
// Only create save_layer if actually needed
// For simple shapes with default opacity and blend mode, skip expensive save_layer
// Groups with masks need a layer to properly handle the mask rendering
let needs_layer = element.needs_layer();
if needs_layer {
let mut paint = skia::Paint::default();
paint.set_blend_mode(element.blend_mode().into());
paint.set_alpha_f(element.opacity());
if let Some(frame_blur) = Self::frame_clip_layer_blur(element) {
let scale = self.get_scale();
let sigma = frame_blur.value * scale;
if let Some(filter) = skia::image_filters::blur((sigma, sigma), None, None, None) {
paint.set_image_filter(filter);
}
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
self.surfaces
.canvas(SurfaceId::Current)
.save_layer(&layer_rec);
}
self.focus_mode.enter(&element.id);
}
#[inline]
pub fn render_shape_exit(&mut self, element: &Shape, visited_mask: bool) {
pub fn render_shape_exit(
&mut self,
element: &Shape,
visited_mask: bool,
clip_bounds: Option<ClipStack>,
) {
if visited_mask {
// Because masked groups needs two rendering passes (first drawing
// the content and then drawing the mask), we need to do an
@@ -1206,7 +1374,7 @@ impl RenderState {
element_strokes.to_mut().clip_content = false;
self.render_shape(
&element_strokes,
None,
clip_bounds,
SurfaceId::Fills,
SurfaceId::Strokes,
SurfaceId::InnerShadows,
@@ -1217,7 +1385,14 @@ impl RenderState {
);
}
self.surfaces.canvas(SurfaceId::Current).restore();
// Only restore if we created a layer (optimization for simple shapes)
// Groups with masks need restore to properly handle the mask rendering
let needs_layer = element.needs_layer();
if needs_layer {
self.surfaces.canvas(SurfaceId::Current).restore();
}
self.focus_mode.exit(&element.id);
}
@@ -1450,6 +1625,8 @@ impl RenderState {
"Error: Element with root_id {} not found in the tree.",
node_render_state.id
))?;
let scale = self.get_scale();
let mut extrect: Option<Rect> = None;
// If the shape is not in the tile set, then we add them.
if self.tiles.get_tiles_of(node_id).is_none() {
@@ -1457,22 +1634,44 @@ impl RenderState {
}
if visited_children {
self.render_shape_exit(element, visited_mask);
// Skip render_shape_exit for flattened containers
if !element.can_flatten() {
self.render_shape_exit(element, visited_mask, clip_bounds);
}
continue;
}
if !node_render_state.is_root() {
let transformed_element: Cow<Shape> = Cow::Borrowed(element);
let scale = self.get_scale();
let extrect = transformed_element.extrect(tree, scale);
let is_visible = extrect.intersects(self.render_area)
&& !transformed_element.hidden
&& !transformed_element.visually_insignificant(scale, tree);
// Aggressive early exit: check hidden first (fastest check)
if transformed_element.hidden {
continue;
}
// For frames and groups, we must use extrect because they can have nested content
// that extends beyond their selrect. Using selrect for early exit would incorrectly
// skip frames/groups that have nested content in the current tile.
let is_container = matches!(
transformed_element.shape_type,
crate::shapes::Type::Frame(_) | crate::shapes::Type::Group(_)
);
let has_effects = transformed_element.has_effects_that_extend_bounds();
let is_visible = if is_container || has_effects {
let element_extrect =
extrect.get_or_insert_with(|| transformed_element.extrect(tree, scale));
element_extrect.intersects(self.render_area)
&& !transformed_element.visually_insignificant(scale, tree)
} else {
let selrect = transformed_element.selrect();
selrect.intersects(self.render_area)
&& !transformed_element.visually_insignificant(scale, tree)
};
if self.options.is_debug_visible() {
let shape_extrect_bounds =
self.get_shape_extrect_bounds(&transformed_element, tree);
let shape_extrect_bounds = self.get_shape_extrect_bounds(element, tree);
debug::render_debug_shape(self, None, Some(shape_extrect_bounds));
}
@@ -1481,10 +1680,14 @@ impl RenderState {
}
}
self.render_shape_enter(element, mask);
// Skip render_shape_enter/exit for flattened containers
// If a container was flattened, it doesn't affect children visually, so we skip
// the expensive enter/exit operations and process children directly
if !element.can_flatten() {
self.render_shape_enter(element, mask);
}
if !node_render_state.is_root() && self.focus_mode.is_active() {
let scale: f32 = self.get_scale();
let translation = self
.surfaces
.get_render_context_translation(self.render_area, scale);
@@ -1524,9 +1727,11 @@ impl RenderState {
.save_layer(&layer_rec);
// First pass: Render shadow in black to establish alpha mask
let element_extrect =
extrect.get_or_insert_with(|| element.extrect(tree, scale));
self.render_drop_black_shadow(
element,
&element.extrect(tree, scale),
element_extrect,
shadow,
clip_bounds.clone(),
scale,
@@ -1541,7 +1746,6 @@ impl RenderState {
if shadow_shape.hidden {
continue;
}
let clip_bounds = node_render_state
.get_nested_shadow_clip_bounds(element, shadow);
@@ -1682,14 +1886,18 @@ impl RenderState {
self.apply_drawing_to_render_canvas(Some(element));
}
match element.shape_type {
Type::Frame(_) if Self::frame_clip_layer_blur(element).is_some() => {
self.nested_blurs.push(None);
// Skip nested state updates for flattened containers
// Flattened containers don't affect children, so we don't need to track their state
if !element.can_flatten() {
match element.shape_type {
Type::Frame(_) if Self::frame_clip_layer_blur(element).is_some() => {
self.nested_blurs.push(None);
}
Type::Frame(_) | Type::Group(_) => {
self.nested_blurs.push(element.blur);
}
_ => {}
}
Type::Frame(_) | Type::Group(_) => {
self.nested_blurs.push(element.blur);
}
_ => {}
}
// Set the node as visited_children before processing children
@@ -1704,24 +1912,35 @@ impl RenderState {
if element.is_recursive() {
let children_clip_bounds =
node_render_state.get_children_clip_bounds(element, None);
let mut children_ids: Vec<_> = element.children_ids_iter(false).collect();
let children_ids: Vec<_> = if element.can_flatten() {
// Container was flattened: get simplified children (which skip this level)
get_simplified_children(tree, element)
} else {
// Container not flattened: use original children
element.children_ids_iter(false).copied().collect()
};
// Z-index ordering on Layouts
if element.has_layout() {
let children_ids = if element.has_layout() {
let mut ids = children_ids;
if element.is_flex() && !element.is_flex_reverse() {
children_ids.reverse();
ids.reverse();
}
children_ids.sort_by(|id1, id2| {
ids.sort_by(|id1, id2| {
let z1 = tree.get(id1).map(|s| s.z_index()).unwrap_or(0);
let z2 = tree.get(id2).map(|s| s.z_index()).unwrap_or(0);
z2.cmp(&z1)
});
}
ids
} else {
children_ids
};
for child_id in children_ids.iter() {
self.pending_nodes.push(NodeRenderState {
id: **child_id,
id: *child_id,
visited_children: false,
clip_bounds: children_clip_bounds.clone(),
visited_mask: false,
@@ -1747,6 +1966,16 @@ impl RenderState {
allow_stop: bool,
) -> Result<(), String> {
let mut should_stop = false;
let root_ids = {
if let Some(shape_id) = base_object {
vec![*shape_id]
} else {
let Some(root) = tree.get(&Uuid::nil()) else {
return Err(String::from("Root shape not found"));
};
root.children_ids(false)
}
};
while !should_stop {
if let Some(current_tile) = self.current_tile {
@@ -1803,17 +2032,6 @@ impl RenderState {
.canvas(SurfaceId::Current)
.clear(self.background_color);
let root_ids = {
if let Some(shape_id) = base_object {
vec![*shape_id]
} else {
let Some(root) = tree.get(&Uuid::nil()) else {
return Err(String::from("Root shape not found"));
};
root.children_ids(false)
}
};
// If we finish processing every node rendering is complete
// let's check if there are more pending nodes
if let Some(next_tile) = self.pending_tiles.pop() {
@@ -1821,21 +2039,13 @@ impl RenderState {
if !self.surfaces.has_cached_tile_surface(next_tile) {
if let Some(ids) = self.tiles.get_shapes_at(next_tile) {
let root_ids_map: std::collections::HashMap<Uuid, usize> = root_ids
.iter()
.enumerate()
.map(|(i, id)| (*id, i))
.collect();
// We only need first level shapes
let mut valid_ids: Vec<Uuid> = ids
.iter()
.filter(|id| root_ids_map.contains_key(id))
.copied()
.collect();
// These shapes for the tile should be ordered as they are in the parent node
valid_ids.sort_by_key(|id| root_ids_map.get(id).unwrap_or(&usize::MAX));
// We only need first level shapes, in the same order as the parent node
let mut valid_ids = Vec::with_capacity(ids.len());
for root_id in root_ids.iter() {
if ids.contains(root_id) {
valid_ids.push(*root_id);
}
}
self.pending_nodes.extend(valid_ids.into_iter().map(|id| {
NodeRenderState {

View File

@@ -18,7 +18,7 @@ fn draw_image_fill(
}
let size = image.unwrap().dimensions();
let canvas = render_state.surfaces.canvas(surface_id);
let canvas = render_state.surfaces.canvas_and_mark_dirty(surface_id);
let container = &shape.selrect;
let path_transform = shape.to_path_transform();

View File

@@ -41,7 +41,7 @@ where
F: FnOnce(&mut RenderState, SurfaceId),
{
if let Some((image, scale)) = render_into_filter_surface(render_state, bounds, draw_fn) {
let canvas = render_state.surfaces.canvas(target_surface);
let canvas = render_state.surfaces.canvas_and_mark_dirty(target_surface);
// If we scaled down, we need to scale the source rect and adjust the destination
if scale < 1.0 {

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