Compare commits
31 Commits
niwinz-dev
...
2.13.0-RC7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e53f335204 | ||
|
|
2574ad3315 | ||
|
|
e6b5364a84 | ||
|
|
656f81f89f | ||
|
|
01a4ffeb8b | ||
|
|
b8c70be9a2 | ||
|
|
525adcfcbe | ||
|
|
7cce4c6532 | ||
|
|
a3fdd8b691 | ||
|
|
b6a9579c98 | ||
|
|
8c7fd0af4b | ||
|
|
cf46051f56 | ||
|
|
6393330ee1 | ||
|
|
8252bc485e | ||
|
|
47775a9e2c | ||
|
|
8191d04114 | ||
|
|
88f0f75174 | ||
|
|
1ffa956251 | ||
|
|
31054099ff | ||
|
|
983487d73c | ||
|
|
3ecf509f3b | ||
|
|
7f395b2642 | ||
|
|
2a62bd2586 | ||
|
|
1ae7515189 | ||
|
|
8c401f5346 | ||
|
|
b72de2dc8f | ||
|
|
51635770ce | ||
|
|
18a4e63da0 | ||
|
|
5c71c57dd9 | ||
|
|
5abc1aafb4 | ||
|
|
935728aa39 |
101
.github/workflows/plugins-deploy-api-doc.yml
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
name: Plugins/api-doc deployer
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- staging
|
||||
- main
|
||||
paths:
|
||||
- "plugins/libs/plugin-types/index.d.ts"
|
||||
- "plugins/libs/plugin-types/REAME.md"
|
||||
- "plugins/tools/typedoc.css"
|
||||
- "plugins/CHANGELOG.md"
|
||||
- "plugins/wrangle-penpot-plugins-api-doc.toml"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gh_ref:
|
||||
description: 'Name of the branch'
|
||||
type: choice
|
||||
required: true
|
||||
default: 'develop'
|
||||
options:
|
||||
- develop
|
||||
- staging
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Extract some useful variables
|
||||
id: vars
|
||||
run: |
|
||||
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.vars.outputs.gh_ref }}
|
||||
|
||||
# START: Setup Node and PNPM enabling cache
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
|
||||
- name: Enable PNPM
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable;
|
||||
corepack install;
|
||||
|
||||
- name: Get pnpm store path
|
||||
id: pnpm-store
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
# END: Setup Node and PNPM enabling cache
|
||||
|
||||
- name: Install deps
|
||||
working-directory: ./plugins
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm install --no-frozen-lockfile;
|
||||
pnpm add -D -w wrangler@latest;
|
||||
|
||||
- name: Build docs
|
||||
working-directory: plugins
|
||||
shell: bash
|
||||
run: pnpm run build:doc
|
||||
|
||||
- name: Select Worker name
|
||||
run: |
|
||||
REF="${{ steps.vars.outputs.gh_ref }}"
|
||||
case "$REF" in
|
||||
main) echo "WORKER_NAME=penpot-plugins-api-doc-pro" >> $GITHUB_ENV ;;
|
||||
staging) echo "WORKER_NAME=penpot-plugins-api-doc-pre" >> $GITHUB_ENV ;;
|
||||
develop) echo "WORKER_NAME=penpot-plugins-api-doc-hourly" >> $GITHUB_ENV ;;
|
||||
*) echo "Unsupported branch ${REF}" && exit 1 ;;
|
||||
esac
|
||||
|
||||
- name: Deploy to Cloudflare Workers
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
workingDirectory: plugins
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: deploy --config wrangle-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}
|
||||
40
.travis.yml
@@ -1,40 +0,0 @@
|
||||
dist: xenial
|
||||
|
||||
language: generic
|
||||
sudo: required
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
|
||||
install:
|
||||
- curl -O https://download.clojure.org/install/linux-install-1.10.1.447.sh
|
||||
- chmod +x linux-install-1.10.1.447.sh
|
||||
- sudo ./linux-install-1.10.1.447.sh
|
||||
|
||||
before_script:
|
||||
- env | sort
|
||||
|
||||
script:
|
||||
- ./manage.sh build-devenv
|
||||
- ./manage.sh run-frontend-tests
|
||||
- ./manage.sh run-backend-tests
|
||||
- ./manage.sh build-images
|
||||
- ./manage.sh run
|
||||
|
||||
after_script:
|
||||
- docker images
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
env:
|
||||
- NODE_VERSION=10.16.0
|
||||
@@ -29,7 +29,7 @@
|
||||
- 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 unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
|
||||
|
||||
## 2.12.1
|
||||
|
||||
|
||||
@@ -124,8 +124,6 @@
|
||||
(throw (IllegalArgumentException. "invalid email body provided")))
|
||||
|
||||
(doseq [[name content] attachments]
|
||||
|
||||
(prn "attachment" name)
|
||||
(let [attachment-part (MimeBodyPart.)]
|
||||
(.setFileName attachment-part ^String name)
|
||||
(.setContent attachment-part ^String content (str "text/plain; charset=" charset))
|
||||
|
||||
@@ -223,7 +223,7 @@ http {
|
||||
add_header X-Cache-Status $upstream_cache_status;
|
||||
}
|
||||
|
||||
location ~* \.(jpg|png|svg|ttf|woff|woff2)$ {
|
||||
location ~* \.(jpg|png|svg|ttf|woff|woff2|gif)$ {
|
||||
add_header Cache-Control "public, max-age=604800" always; # 7 days
|
||||
}
|
||||
|
||||
|
||||
@@ -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,16 +145,16 @@ 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.
|
||||
|
||||
# AWS_ACCESS_KEY_ID: <KEY_ID>
|
||||
# AWS_SECRET_ACCESS_KEY: <ACCESS_KEY>
|
||||
# PENPOT_ASSETS_STORAGE_BACKEND: assets-s3
|
||||
# PENPOT_STORAGE_ASSETS_S3_ENDPOINT: <ENDPOINT>
|
||||
# PENPOT_STORAGE_ASSETS_S3_BUCKET: <BUKET_NAME>
|
||||
# PENPOT_OBJECTS_STORAGE_BACKEND: s3
|
||||
# PENPOT_OBJECTS_STORAGE_S3_ENDPOINT: <ENDPOINT>
|
||||
# PENPOT_OBJECTS_STORAGE_S3_BUCKET: <BUKET_NAME>
|
||||
|
||||
## Telemetry. When enabled, a periodical process will send anonymous data about this
|
||||
## instance. Telemetry data will enable us to learn how the application is used,
|
||||
|
||||
@@ -144,7 +144,7 @@ http {
|
||||
location / {
|
||||
include /etc/nginx/overrides/location.d/*.conf;
|
||||
|
||||
location ~* \.(js|css|jpg|png|svg|ttf|woff|woff2|wasm)$ {
|
||||
location ~* \.(js|css|jpg|png|svg|gif|ttf|woff|woff2|wasm)$ {
|
||||
add_header Cache-Control "public, max-age=604800" always; # 7 days
|
||||
}
|
||||
|
||||
|
||||
BIN
docs/img/design-tokens/37-tokens-shadow-individual.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/img/design-tokens/38-tokens-shadow-reference.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/img/files-projects/01-projects.webp
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
docs/img/files-projects/02-drafts.webp
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
docs/img/files-projects/03-trash.webp
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/img/files-projects/04-pin-project.webp
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
docs/img/files-projects/05-create-file.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
docs/img/files-projects/06-move-project.webp
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
@@ -114,14 +114,7 @@ configuration.
|
||||
The callback has the following format:
|
||||
|
||||
```html
|
||||
https://<your_domain>/api/auth/oauth/<oauth_provider>/callback
|
||||
```
|
||||
|
||||
You will need to change <your_domain> and <oauth_provider> according to your setup.
|
||||
This is how it looks with Gitlab provider:
|
||||
|
||||
```html
|
||||
https://<your_domain>/api/auth/oauth/gitlab/callback
|
||||
https://<your_domain>/api/auth/oidc/callback
|
||||
```
|
||||
|
||||
#### Google
|
||||
|
||||
@@ -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>
|
||||
|
||||
107
docs/user-guide/account-teams/projects-files.njk
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -48,13 +48,13 @@
|
||||
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
|
||||
"clear:shadow-cache": "rm -rf .shadow-cljs",
|
||||
"watch": "exit 0",
|
||||
"watch:app": "yarn run clear:shadow-cache && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
|
||||
"watch:app": "yarn run clear:shadow-cache && yarn run build:wasm && concurrently --kill-others-on-fail \"yarn run watch:app:assets\" \"yarn run watch:app:main\" \"yarn run watch:app:libs\"",
|
||||
"watch:storybook": "yarn run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@penpot/draft-js": "portal:./packages/draft-js",
|
||||
"@penpot/mousetrap": "portal:./packages/mousetrap",
|
||||
"@penpot/plugins-runtime": "1.3.2",
|
||||
"@penpot/plugins-runtime": "1.4.2",
|
||||
"@penpot/svgo": "penpot/svgo#v3.2",
|
||||
"@penpot/text-editor": "portal:./text-editor",
|
||||
"@playwright/test": "1.57.0",
|
||||
|
||||
BIN
frontend/resources/images/features/2.13-shadow-tokens.gif
Normal file
|
After Width: | Height: | Size: 389 KiB |
BIN
frontend/resources/images/features/2.13-slide-0.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
frontend/resources/images/features/2.13-trash.gif
Normal file
|
After Width: | Height: | Size: 284 KiB |
@@ -17,17 +17,18 @@
|
||||
<meta name="twitter:site" content="@penpotapp">
|
||||
<meta name="twitter:creator" content="@penpotapp">
|
||||
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
|
||||
<link id="theme" href="css/main.css?version={{& version}}" rel="stylesheet" type="text/css" />
|
||||
<link id="theme" href="css/main.css?version={{& version_tag}}" rel="stylesheet" type="text/css" />
|
||||
{{#isDebug}}
|
||||
<link href="css/debug.css?version={{& version}}" rel="stylesheet" type="text/css" />
|
||||
<link href="css/debug.css?version={{& version_tag}}" rel="stylesheet" type="text/css" />
|
||||
{{/isDebug}}
|
||||
|
||||
<link rel="icon" href="images/favicon.png" />
|
||||
<link rel="icon" href="images/favicon.png?version={{& version_tag }}" />
|
||||
|
||||
<script type="importmap">{{& manifest.importmap }}</script>
|
||||
|
||||
<script type="module">
|
||||
globalThis.penpotVersion = "{{& version}}";
|
||||
globalThis.penpotVersionTag = "{{& version_tag}}";
|
||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||
globalThis.penpotWorkerURI = "{{& manifest.worker_main}}";
|
||||
</script>
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Penpot - Rasterizer</title>
|
||||
<link rel="icon" href="images/favicon.png" />
|
||||
<link rel="icon" href="images/favicon.png?version={{& version_tag }}" />
|
||||
|
||||
<script>
|
||||
globalThis.penpotVersion = "{{& version}}";
|
||||
globalThis.penpotVersionTag = "{{& version_tag}}";
|
||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||
globalThis.penpotWorkerURI = "{{& manifest.worker_main}}";
|
||||
</script>
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>Penpot - Render</title>
|
||||
<link rel="icon" href="images/favicon.png" />
|
||||
|
||||
<link rel="icon" href="images/favicon.png?version={{& version_tag }}" />
|
||||
|
||||
<script>
|
||||
globalThis.penpotVersion = "{{& version}}";
|
||||
globalThis.penpotVersionTag = "{{& version_tag}}";
|
||||
globalThis.penpotBuildDate = "{{& build_date}}";
|
||||
</script>
|
||||
|
||||
|
||||
@@ -27,9 +27,11 @@ export function startWorker() {
|
||||
});
|
||||
}
|
||||
|
||||
export const isDebug = process.env.NODE_ENV !== "production";
|
||||
export const CURRENT_VERSION = process.env.CURRENT_VERSION || "develop";
|
||||
export const BUILD_DATE = process.env.BUILD_DATE || "" + new Date();
|
||||
export const IS_DEBUG = process.env.NODE_ENV !== "production";
|
||||
export const BUILD_DATE = process.env.BUILD_DATE || (new Date().toString()) ;
|
||||
export const BUILD_TS = process.env.BUILD_TS || Date.now();
|
||||
export const VERSION = process.env.VERSION || "develop";
|
||||
export const VERSION_TAG = process.env.VERSION_TAG || VERSION;
|
||||
|
||||
async function findFiles(basePath, predicate, options = {}) {
|
||||
predicate =
|
||||
@@ -172,6 +174,7 @@ export async function watch(baseDir, predicate, callback) {
|
||||
const watcher = new Watcher(baseDir, {
|
||||
persistent: true,
|
||||
recursive: true,
|
||||
debounce: 500
|
||||
});
|
||||
|
||||
watcher.on("change", (path) => {
|
||||
@@ -179,8 +182,19 @@ export async function watch(baseDir, predicate, callback) {
|
||||
callback(path);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
watcher.on("error", (cause) => {
|
||||
console.log("WATCHER ERROR", cause);
|
||||
});
|
||||
}
|
||||
|
||||
export async function ensureDirectories() {
|
||||
await fs.mkdir("./resources/public/js/worker/", { recursive: true });
|
||||
await fs.mkdir("./resources/public/css/", { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
async function readManifestFile(resource) {
|
||||
const manifestPath = "resources/public/" + resource;
|
||||
let content = await fs.readFile(manifestPath, { encoding: "utf8" });
|
||||
@@ -193,25 +207,25 @@ async function generateManifest() {
|
||||
render_main: "./js/render.js",
|
||||
rasterizer_main: "./js/rasterizer.js",
|
||||
|
||||
config: "./js/config.js?version=" + CURRENT_VERSION,
|
||||
polyfills: "./js/polyfills.js?version=" + CURRENT_VERSION,
|
||||
libs: "./js/libs.js?version=" + CURRENT_VERSION,
|
||||
worker_main: "./js/worker/main.js?version=" + CURRENT_VERSION,
|
||||
default_translations: "./js/translation.en.js?version=" + CURRENT_VERSION,
|
||||
config: "./js/config.js?version=" + VERSION_TAG,
|
||||
polyfills: "./js/polyfills.js?version=" + VERSION_TAG,
|
||||
libs: "./js/libs.js?version=" + VERSION_TAG,
|
||||
worker_main: "./js/worker/main.js?version=" + VERSION_TAG,
|
||||
default_translations: "./js/translation.en.js?version=" + VERSION_TAG,
|
||||
|
||||
importmap: JSON.stringify({
|
||||
"imports": {
|
||||
"./js/shared.js": "./js/shared.js?version=" + CURRENT_VERSION,
|
||||
"./js/main.js": "./js/main.js?version=" + CURRENT_VERSION,
|
||||
"./js/render.js": "./js/render.js?version=" + CURRENT_VERSION,
|
||||
"./js/render-wasm.js": "./js/render-wasm.js?version=" + CURRENT_VERSION,
|
||||
"./js/rasterizer.js": "./js/rasterizer.js?version=" + CURRENT_VERSION,
|
||||
"./js/main-dashboard.js": "./js/main-dashboard.js?version=" + CURRENT_VERSION,
|
||||
"./js/main-auth.js": "./js/main-auth.js?version=" + CURRENT_VERSION,
|
||||
"./js/main-viewer.js": "./js/main-viewer.js?version=" + CURRENT_VERSION,
|
||||
"./js/main-settings.js": "./js/main-settings.js?version=" + CURRENT_VERSION,
|
||||
"./js/main-workspace.js": "./js/main-workspace.js?version=" + CURRENT_VERSION,
|
||||
"./js/util-highlight.js": "./js/util-highlight.js?version=" + CURRENT_VERSION
|
||||
"./js/shared.js": "./js/shared.js?version=" + VERSION_TAG,
|
||||
"./js/main.js": "./js/main.js?version=" + VERSION_TAG,
|
||||
"./js/render.js": "./js/render.js?version=" + VERSION_TAG,
|
||||
"./js/render-wasm.js": "./js/render-wasm.js?version=" + VERSION_TAG,
|
||||
"./js/rasterizer.js": "./js/rasterizer.js?version=" + VERSION_TAG,
|
||||
"./js/main-dashboard.js": "./js/main-dashboard.js?version=" + VERSION_TAG,
|
||||
"./js/main-auth.js": "./js/main-auth.js?version=" + VERSION_TAG,
|
||||
"./js/main-viewer.js": "./js/main-viewer.js?version=" + VERSION_TAG,
|
||||
"./js/main-settings.js": "./js/main-settings.js?version=" + VERSION_TAG,
|
||||
"./js/main-workspace.js": "./js/main-workspace.js?version=" + VERSION_TAG,
|
||||
"./js/util-highlight.js": "./js/util-highlight.js?version=" + VERSION_TAG
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -222,11 +236,12 @@ async function generateManifest() {
|
||||
async function renderTemplate(path, context = {}, partials = {}) {
|
||||
const content = await fs.readFile(path, { encoding: "utf-8" });
|
||||
|
||||
const ts = Math.floor(new Date());
|
||||
|
||||
context = Object.assign({}, context, {
|
||||
ts: ts,
|
||||
isDebug,
|
||||
isDebug: IS_DEBUG,
|
||||
version: VERSION,
|
||||
version_tag: VERSION_TAG,
|
||||
build_date: BUILD_DATE,
|
||||
build_ts: BUILD_TS,
|
||||
});
|
||||
|
||||
return mustache.render(content, context, partials);
|
||||
@@ -257,6 +272,9 @@ const markedOptions = {
|
||||
marked.use(markedOptions);
|
||||
|
||||
export async function compileTranslations() {
|
||||
const outputDir = "resources/public/js/";
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
|
||||
const langs = [
|
||||
"ar",
|
||||
"ca",
|
||||
@@ -338,7 +356,6 @@ export async function compileTranslations() {
|
||||
}
|
||||
|
||||
const esm = `export default ${JSON.stringify(result, null, 0)};\n`;
|
||||
const outputDir = "resources/public/js/";
|
||||
const outputFile = ph.join(outputDir, "translation." + lang + ".js");
|
||||
await fs.writeFile(outputFile, esm);
|
||||
}
|
||||
@@ -390,7 +407,6 @@ async function generateSvgSprites() {
|
||||
}
|
||||
|
||||
async function generateTemplates() {
|
||||
const isDebug = process.env.NODE_ENV !== "production";
|
||||
await fs.mkdir("./resources/public/", { recursive: true });
|
||||
|
||||
const manifest = await generateManifest();
|
||||
@@ -415,10 +431,7 @@ async function generateTemplates() {
|
||||
};
|
||||
|
||||
const context = {
|
||||
manifest: manifest,
|
||||
version: CURRENT_VERSION,
|
||||
build_date: BUILD_DATE,
|
||||
isDebug,
|
||||
manifest: manifest
|
||||
};
|
||||
|
||||
content = await renderTemplate(
|
||||
@@ -487,7 +500,7 @@ export async function compileStyles() {
|
||||
await fs.mkdir("./resources/public/css", { recursive: true });
|
||||
await fs.writeFile("./resources/public/css/main.css", result);
|
||||
|
||||
if (isDebug) {
|
||||
if (IS_DEBUG) {
|
||||
let debugCSS = await compileSassDebug(worker);
|
||||
await fs.writeFile("./resources/public/css/debug.css", debugCSS);
|
||||
}
|
||||
@@ -500,17 +513,43 @@ export async function compileStyles() {
|
||||
export async function compileSvgSprites() {
|
||||
const start = process.hrtime();
|
||||
log.info("init: compile svgsprite");
|
||||
await generateSvgSprites();
|
||||
let error = false;
|
||||
|
||||
try {
|
||||
await generateSvgSprites();
|
||||
} catch (cause) {
|
||||
error = cause;
|
||||
}
|
||||
|
||||
const end = process.hrtime(start);
|
||||
log.info("done: compile svgsprite", `(${ppt(end)})`);
|
||||
|
||||
if (error) {
|
||||
log.error("error: compile svgsprite", `(${ppt(end)})`);
|
||||
console.error(error);
|
||||
} else {
|
||||
log.info("done: compile svgsprite", `(${ppt(end)})`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function compileTemplates() {
|
||||
const start = process.hrtime();
|
||||
let error = false;
|
||||
log.info("init: compile templates");
|
||||
await generateTemplates();
|
||||
|
||||
try {
|
||||
await generateTemplates();
|
||||
} catch (cause) {
|
||||
error = cause;
|
||||
}
|
||||
|
||||
const end = process.hrtime(start);
|
||||
log.info("done: compile templates", `(${ppt(end)})`);
|
||||
|
||||
if (error) {
|
||||
log.error("error: compile templates", `(${ppt(end)})`);
|
||||
console.error(error);
|
||||
} else {
|
||||
log.info("done: compile templates", `(${ppt(end)})`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function compilePolyfills() {
|
||||
|
||||
@@ -28,14 +28,12 @@ async function compileFile(path) {
|
||||
],
|
||||
sourceMap: false,
|
||||
});
|
||||
// console.dir(result);
|
||||
resolve({
|
||||
inputPath: path,
|
||||
outputPath: dest,
|
||||
css: result.css,
|
||||
});
|
||||
} catch (cause) {
|
||||
console.error(cause);
|
||||
reject(cause);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,26 +2,26 @@
|
||||
# NOTE: this script should be called from the parent directory to
|
||||
# properly work.
|
||||
|
||||
set -ex
|
||||
|
||||
export INCLUDE_STORYBOOK=${BUILD_STORYBOOK:-no};
|
||||
export INCLUDE_WASM=${BUILD_WASM:-yes};
|
||||
export CURRENT_VERSION=$1;
|
||||
export BUILD_DATE=$(date -R);
|
||||
export CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)};
|
||||
export EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS;
|
||||
export TS=$(date +%s);
|
||||
|
||||
export BUILD_DATE=$(date -R);
|
||||
export BUILD_TS=$(date +%s);
|
||||
|
||||
export VERSION=${1:-develop};
|
||||
export VERSION_TAG="${VERSION}-${BUILD_TS}";
|
||||
|
||||
# Some cljs reacts on this environment variable for define more
|
||||
# performant code on macros (example: rumext)
|
||||
export NODE_ENV=production;
|
||||
|
||||
echo "Current path:"
|
||||
echo $PATH
|
||||
|
||||
set -ex
|
||||
|
||||
corepack enable;
|
||||
corepack install;
|
||||
yarn install || exit 1;
|
||||
yarn install;
|
||||
|
||||
rm -rf target/dist;
|
||||
rm -rf resources/public;
|
||||
@@ -37,7 +37,7 @@ yarn run build:app:main $EXTRA_PARAMS;
|
||||
yarn run build:app:libs;
|
||||
yarn run build:app:assets;
|
||||
|
||||
sed -i "s/\.\/render.js/.\/render.js?version=$CURRENT_VERSION/g" resources/public/js/worker/main*.js
|
||||
sed -i "s/\.\/render.js/.\/render.js?version=$VERSION_TAG/g" resources/public/js/worker/main*.js
|
||||
|
||||
rsync -avr resources/public/ target/dist/
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as h from "./_helpers.js";
|
||||
|
||||
await h.ensureDirectories();
|
||||
await h.compileStyles();
|
||||
await h.copyAssets();
|
||||
await h.copyWasmPlayground();
|
||||
|
||||
@@ -2,18 +2,16 @@
|
||||
# NOTE: this script should be called from the parent directory to
|
||||
# properly work.
|
||||
|
||||
export CURRENT_VERSION=$1;
|
||||
set -ex
|
||||
|
||||
export BUILD_TS=$(date +%s);
|
||||
export BUILD_DATE=$(date -R);
|
||||
export CURRENT_HASH=${CURRENT_HASH:-$(git rev-parse --short HEAD)};
|
||||
export TS=$(date +%s);
|
||||
|
||||
export VERSION=${1:-develop};
|
||||
export VERSION_TAG="${VERSION}-${BUILD_TS}";
|
||||
|
||||
export NODE_ENV=production;
|
||||
|
||||
echo "Current path:"
|
||||
echo $PATH
|
||||
|
||||
set -ex
|
||||
|
||||
corepack enable;
|
||||
corepack install || exit 1;
|
||||
yarn install || exit 1;
|
||||
|
||||
@@ -12,19 +12,31 @@ let sass = null;
|
||||
|
||||
async function compileSassAll() {
|
||||
const start = process.hrtime();
|
||||
let error = false;
|
||||
|
||||
log.info("init: compile styles");
|
||||
|
||||
sass = await h.compileSassAll(worker);
|
||||
let output = await h.concatSass(sass);
|
||||
await fs.writeFile("./resources/public/css/main.css", output);
|
||||
try {
|
||||
sass = await h.compileSassAll(worker);
|
||||
let output = await h.concatSass(sass);
|
||||
await fs.writeFile("./resources/public/css/main.css", output);
|
||||
|
||||
if (isDebug) {
|
||||
let debugCSS = await h.compileSassDebug(worker);
|
||||
await fs.writeFile("./resources/public/css/debug.css", debugCSS);
|
||||
if (isDebug) {
|
||||
let debugCSS = await h.compileSassDebug(worker);
|
||||
await fs.writeFile("./resources/public/css/debug.css", debugCSS);
|
||||
}
|
||||
} catch (cause) {
|
||||
error = cause;
|
||||
}
|
||||
|
||||
const end = process.hrtime(start);
|
||||
log.info("done: compile styles", `(${ppt(end)})`);
|
||||
|
||||
if (error) {
|
||||
log.error("error: compile styles", `(${ppt(end)})`);
|
||||
console.error(error);
|
||||
} else {
|
||||
log.info("done: compile styles", `(${ppt(end)})`);
|
||||
}
|
||||
}
|
||||
|
||||
async function compileSass(path) {
|
||||
@@ -48,7 +60,7 @@ async function compileSass(path) {
|
||||
}
|
||||
}
|
||||
|
||||
await fs.mkdir("./resources/public/css/", { recursive: true });
|
||||
await h.ensureDirectories();
|
||||
await compileSassAll();
|
||||
await h.copyAssets();
|
||||
await h.copyWasmPlayground();
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
{:fn-invoke-direct true
|
||||
:optimizations #shadow/env ["PENPOT_BUILD_OPTIMIZATIONS" :as :keyword :default :advanced]
|
||||
:source-map true
|
||||
:pseudo-names true
|
||||
:elide-asserts true
|
||||
:anon-fn-naming-policy :off
|
||||
:cross-chunk-method-motion false
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
(def browser (parse-browser))
|
||||
(def platform (parse-platform))
|
||||
|
||||
(def version-tag (obj/get global "penpotVersionTag"))
|
||||
(def terms-of-service-uri (obj/get global "penpotTermsOfServiceURI"))
|
||||
(def privacy-policy-uri (obj/get global "penpotPrivacyPolicyURI"))
|
||||
(def flex-help-uri (obj/get global "penpotGridHelpURI" "https://help.penpot.app/user-guide/flexible-layouts/"))
|
||||
@@ -190,9 +191,8 @@
|
||||
|
||||
(defn resolve-href
|
||||
[resource]
|
||||
(let [version (get version :full)
|
||||
href (-> public-uri
|
||||
(u/ensure-path-slash)
|
||||
(u/join resource)
|
||||
(get :path))]
|
||||
(str href "?version=" version)))
|
||||
(let [href (-> public-uri
|
||||
(u/ensure-path-slash)
|
||||
(u/join resource)
|
||||
(get :path))]
|
||||
(str href "?version=" version-tag)))
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
If the `value` is not parseable and/or has missing references returns a map with `:errors`.
|
||||
If the `value` is parseable but is out of range returns a map with `warnings`."
|
||||
[value]
|
||||
(let [missing-references? (seq (seq (cto/find-token-value-references value)))
|
||||
(let [missing-references? (seq (cto/find-token-value-references value))
|
||||
parsed-value (cft/parse-token-value value)
|
||||
out-of-scope (not (<= 0 (:value parsed-value) 1))
|
||||
references (seq (cto/find-token-value-references value))]
|
||||
@@ -152,15 +152,14 @@
|
||||
[value]
|
||||
(let [missing-references? (seq (cto/find-token-value-references value))
|
||||
parsed-value (cft/parse-token-value value)
|
||||
out-of-scope (< (:value parsed-value) 0)
|
||||
references (seq (cto/find-token-value-references value))]
|
||||
out-of-scope (< (:value parsed-value) 0)]
|
||||
(cond
|
||||
(and parsed-value (not out-of-scope))
|
||||
parsed-value
|
||||
|
||||
references
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference references)]
|
||||
:references references}
|
||||
missing-references?
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/missing-reference missing-references?)]
|
||||
:references missing-references?}
|
||||
|
||||
(and (not missing-references?) out-of-scope)
|
||||
{:errors [(wte/error-with-value :error.style-dictionary/invalid-token-value-stroke-width value)]}
|
||||
|
||||
@@ -480,6 +480,9 @@
|
||||
(def workspace-token-sets-tree
|
||||
(l/derived (d/nilf ctob/get-set-tree) tokens-lib))
|
||||
|
||||
(def workspace-all-tokens-map
|
||||
(l/derived (d/nilf ctob/get-all-tokens) tokens-lib))
|
||||
|
||||
(def workspace-active-theme-paths
|
||||
(l/derived (d/nilf ctob/get-active-theme-paths) tokens-lib))
|
||||
|
||||
|
||||
@@ -193,16 +193,15 @@
|
||||
restore-fn
|
||||
(fn [_]
|
||||
(st/emit! (dd/restore-files-immediately
|
||||
(with-meta {:team-id (:id current-team)
|
||||
(with-meta {:team-id current-team-id
|
||||
: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)))
|
||||
(dd/fetch-projects current-team-id)
|
||||
(dd/fetch-deleted-files current-team-id))
|
||||
:on-error #(st/emit! (ntf/error (tr "dashboard.errors.error-on-restore-file" (:name file))))}))))
|
||||
|
||||
on-restore-immediately
|
||||
(fn []
|
||||
(prn files)
|
||||
(st/emit!
|
||||
(modal/show {:type :confirm
|
||||
:title (tr "dashboard-restore-file-confirmation.title")
|
||||
@@ -214,7 +213,7 @@
|
||||
on-delete-immediately
|
||||
(fn []
|
||||
(let [accept-fn #(st/emit! (dd/delete-files-immediately
|
||||
{:team-id (:id current-team)
|
||||
{:team-id current-team-id
|
||||
:ids (into #{} d/xf:map-id files)}))]
|
||||
(st/emit!
|
||||
(modal/show {:type :confirm
|
||||
@@ -244,8 +243,7 @@
|
||||
(for [project current-projects]
|
||||
{:name (get-project-name project)
|
||||
:id (get-project-id project)
|
||||
:handler (on-move (:id current-team)
|
||||
(:id project))})
|
||||
:handler (on-move current-team-id (:id project))})
|
||||
(when (seq other-teams)
|
||||
[{:name (tr "dashboard.move-to-other-team")
|
||||
:id "move-to-other-team"
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
touched? (and (contains? (:data @form) input-name)
|
||||
(get-in @form [:touched input-name]))
|
||||
|
||||
error (get-in @form [:errors input-name])
|
||||
|
||||
value (get-in @form [:data input-name] "")
|
||||
@@ -52,7 +53,8 @@
|
||||
(let [form (mf/use-ctx context)
|
||||
disabled? (or (and (some? form)
|
||||
(or (not (:valid @form))
|
||||
(seq (:external-errors @form))))
|
||||
(seq (:async-errors @form))
|
||||
(seq (:extra-errors @form))))
|
||||
(true? disabled))
|
||||
handle-key-down-save
|
||||
(mf/use-fn
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
[app.main.ui.releases.v2-10]
|
||||
[app.main.ui.releases.v2-11]
|
||||
[app.main.ui.releases.v2-12]
|
||||
[app.main.ui.releases.v2-13]
|
||||
[app.main.ui.releases.v2-2]
|
||||
[app.main.ui.releases.v2-3]
|
||||
[app.main.ui.releases.v2-4]
|
||||
@@ -103,4 +104,4 @@
|
||||
|
||||
(defmethod rc/render-release-notes "0.0"
|
||||
[params]
|
||||
(rc/render-release-notes (assoc params :version "2.12")))
|
||||
(rc/render-release-notes (assoc params :version "2.13")))
|
||||
|
||||
118
frontend/src/app/main/ui/releases/v2_13.cljs
Normal file
@@ -0,0 +1,118 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.releases.v2-13
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.ui.releases.common :as c]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defmethod c/render-release-notes "2.13"
|
||||
[{:keys [slide klass next finish navigate version]}]
|
||||
(mf/html
|
||||
(case slide
|
||||
:start
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.13-slide-0.jpg"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Penpot 2.13 is here!"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"What’s new in Penpot?"]
|
||||
|
||||
[:div {:class (stl/css :version-tag)}
|
||||
(dm/str "Version " version)]]
|
||||
|
||||
[:div {:class (stl/css :features-block)}
|
||||
[:span {:class (stl/css :feature-title)}
|
||||
"The first release of the year, and we’re just getting started 🚀"]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"This is our first release of the year, and it sets the tone for what’s coming next. We’re kicking off an exciting year where we’ll take Penpot to a whole new level, with improved performance, stronger design system foundations, long-requested features, and new capabilities that unlock better workflows for teams."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"This release brings two highlights the community has been asking for, along with solid improvements under the hood to keep everything fast and smooth."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Let’s dive in!"]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:button {:class (stl/css :next-btn)
|
||||
:on-click next} "Continue"]]]]]]
|
||||
|
||||
0
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.13-trash.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "The Trash"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"The Trash"]]
|
||||
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Deleting a file no longer means it’s gone forever. Introducing The Trash, a dedicated space in the dashboard where deleted files and projects live before being permanently removed."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"From here, you can recover content deleted by mistake or clean things up for good when you’re sure you don’t need them anymore. The Trash works for both files and projects, and items are automatically removed after a period of time depending on your Penpot plan."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Highly requested, long overdue, and now officially here."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 3}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
1
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.13-shadow-tokens.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Shadow tokens: Reusable shadows, at last!"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"Shadow tokens: Reusable shadows, at last!"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"With Shadow tokens, we’re introducing our second composite token, right after Typography tokens. This is a big step forward for design systems in Penpot."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Until now, shadows couldn’t be defined as reusable styles the way colors could before color tokens existed. Shadow tokens change that. You can now create reusable, consistent shadows, made of one or multiple layers, fully tokenized and ready to scale across your designs."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Each shadow can reference existing tokens or use custom values, supports both Drop Shadow and Inner Shadow, and even allows shadow tokens to reference other shadow tokens. A brand-new capability, unlocked."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 2}]
|
||||
|
||||
[:button {:on-click finish
|
||||
:class (stl/css :next-btn)} "Let's go"]]]]]])))
|
||||
|
||||
102
frontend/src/app/main/ui/releases/v2_13.scss
Normal file
@@ -0,0 +1,102 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@use "refactor/common-refactor.scss" as deprecated;
|
||||
|
||||
.modal-overlay {
|
||||
@extend .modal-overlay-base;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
display: grid;
|
||||
grid-template-columns: deprecated.$s-324 1fr;
|
||||
height: deprecated.$s-500;
|
||||
width: deprecated.$s-888;
|
||||
border-radius: deprecated.$br-8;
|
||||
background-color: var(--modal-background-color);
|
||||
border: deprecated.$s-2 solid var(--modal-border-color);
|
||||
}
|
||||
|
||||
.start-image {
|
||||
width: deprecated.$s-324;
|
||||
border-radius: deprecated.$br-8 0 0 deprecated.$br-8;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: deprecated.$s-40;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr deprecated.$s-32;
|
||||
gap: deprecated.$s-24;
|
||||
|
||||
a {
|
||||
color: var(--button-primary-background-color-rest);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: grid;
|
||||
gap: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.version-tag {
|
||||
@include deprecated.flexCenter;
|
||||
@include deprecated.headlineSmallTypography;
|
||||
height: deprecated.$s-32;
|
||||
width: deprecated.$s-96;
|
||||
background-color: var(--communication-tag-background-color);
|
||||
color: var(--communication-tag-foreground-color);
|
||||
border-radius: deprecated.$br-8;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
@include deprecated.headlineLargeTypography;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
.features-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: deprecated.$s-16;
|
||||
width: deprecated.$s-440;
|
||||
}
|
||||
|
||||
.feature {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
@include deprecated.bodyLargeTypography;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
.feature-content {
|
||||
@include deprecated.bodyMediumTypography;
|
||||
margin: 0;
|
||||
color: var(--modal-text-foreground-color);
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
@include deprecated.bodyMediumTypography;
|
||||
color: var(--modal-text-foreground-color);
|
||||
list-style: disc;
|
||||
display: grid;
|
||||
gap: deprecated.$s-8;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-areas: "bullets button";
|
||||
}
|
||||
|
||||
.next-btn {
|
||||
@extend .button-primary;
|
||||
width: deprecated.$s-100;
|
||||
justify-self: flex-end;
|
||||
grid-area: button;
|
||||
}
|
||||
@@ -273,4 +273,4 @@
|
||||
{:label (tr "workspace.tokens.import-menu-folder-option") :value :folder}]
|
||||
:on-click handle-import-action
|
||||
:text-render render-button-text
|
||||
:default :zip}]]]))
|
||||
:default :file}]]]))
|
||||
|
||||
@@ -53,10 +53,12 @@
|
||||
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token value]
|
||||
[tokens prev-token token-name value]
|
||||
(let [token
|
||||
{:value value
|
||||
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
|
||||
:name (if (str/blank? token-name)
|
||||
"__PENPOT__TOKEN__NAME__PLACEHOLDER__"
|
||||
token-name)}
|
||||
|
||||
tokens
|
||||
(-> tokens
|
||||
@@ -131,6 +133,7 @@
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
token-name (get-in @form [:data :name] nil)
|
||||
|
||||
|
||||
touched?
|
||||
@@ -140,6 +143,9 @@
|
||||
error
|
||||
(get-in @form [:errors input-name])
|
||||
|
||||
extra-error
|
||||
(get-in @form [:extra-errors input-name])
|
||||
|
||||
value
|
||||
(get-in @form [:data input-name] "")
|
||||
|
||||
@@ -247,15 +253,20 @@
|
||||
:hint-type (:type hint)})
|
||||
|
||||
props
|
||||
(if (and error touched?)
|
||||
(cond
|
||||
(and error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
(and extra-error touched?)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message extra-error)})
|
||||
:else
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
(mf/with-effect [resolve-stream tokens token input-name token-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/mapcat (partial resolve-value tokens token token-name))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
@@ -301,7 +312,7 @@
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
token-name (get-in @form [:data :name] nil)
|
||||
error
|
||||
(get-in @form [:errors :value value-subfield index input-name])
|
||||
|
||||
@@ -414,10 +425,10 @@
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name index value-subfield]
|
||||
(mf/with-effect [resolve-stream tokens token input-name index value-subfield token-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/mapcat (partial resolve-value tokens token token-name))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
|
||||
@@ -49,10 +49,12 @@
|
||||
;; validate data within the form state.
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token value]
|
||||
[tokens prev-token token-name value]
|
||||
(let [token
|
||||
{:value (cto/split-font-family value)
|
||||
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
|
||||
:name (if (str/blank? token-name)
|
||||
"__PENPOT__TOKEN__NAME__PLACEHOLDER__"
|
||||
token-name)}
|
||||
|
||||
tokens
|
||||
(-> tokens
|
||||
@@ -73,6 +75,7 @@
|
||||
[{:keys [token tokens name] :rest props}]
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
token-name (get-in @form [:data :name] nil)
|
||||
|
||||
touched?
|
||||
(and (contains? (:data @form) input-name)
|
||||
@@ -152,10 +155,10 @@
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name touched?]
|
||||
(mf/with-effect [resolve-stream tokens token input-name touched? token-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/mapcat (partial resolve-value tokens token token-name))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
@@ -200,7 +203,7 @@
|
||||
[{:keys [token tokens name] :rest props}]
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
token-name (get-in @form [:data :name] nil)
|
||||
error
|
||||
(get-in @form [:errors :value input-name])
|
||||
|
||||
@@ -276,10 +279,10 @@
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
(mf/with-effect [resolve-stream tokens token input-name token-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/mapcat (partial resolve-value tokens token token-name))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
|
||||
@@ -139,10 +139,12 @@
|
||||
|
||||
|
||||
(defn- resolve-value
|
||||
[tokens prev-token value]
|
||||
[tokens prev-token token-name value]
|
||||
(let [token
|
||||
{:value value
|
||||
:name "__PENPOT__TOKEN__NAME__PLACEHOLDER__"}
|
||||
:name (if (str/blank? token-name)
|
||||
"__PENPOT__TOKEN__NAME__PLACEHOLDER__"
|
||||
token-name)}
|
||||
tokens
|
||||
(-> tokens
|
||||
;; Remove previous token when renaming a token
|
||||
@@ -163,6 +165,7 @@
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
token-name (get-in @form [:data :name] nil)
|
||||
|
||||
touched?
|
||||
(and (contains? (:data @form) input-name)
|
||||
@@ -206,11 +209,11 @@
|
||||
:hint-message (:message error)})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name]
|
||||
(mf/with-effect [resolve-stream tokens token input-name token-name]
|
||||
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/mapcat (partial resolve-value tokens token token-name))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
@@ -236,12 +239,14 @@
|
||||
(on-composite-input-change form field value false))
|
||||
([form field value trim?]
|
||||
(letfn [(clean-errors [errors]
|
||||
(-> errors
|
||||
(dissoc field)
|
||||
(not-empty)))]
|
||||
(some-> errors
|
||||
(update :value #(when (map? %) (dissoc % field)))
|
||||
(update :value #(when (seq %) %))
|
||||
(not-empty)))]
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:data :value field] (if trim? (str/trim value) value))
|
||||
(assoc-in [:touched :value field] true)
|
||||
(update :errors clean-errors)
|
||||
(update :extra-errors clean-errors)))))))
|
||||
|
||||
@@ -250,6 +255,7 @@
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
token-name (get-in @form [:data :name] nil)
|
||||
|
||||
error
|
||||
(get-in @form [:errors :value input-name])
|
||||
@@ -257,6 +263,9 @@
|
||||
value
|
||||
(get-in @form [:data :value input-name] "")
|
||||
|
||||
touched?
|
||||
(get-in @form [:touched :value input-name])
|
||||
|
||||
resolve-stream
|
||||
(mf/with-memo [token]
|
||||
(if-let [value (get-in token [:value input-name])]
|
||||
@@ -284,7 +293,7 @@
|
||||
:hint-message (:message hint)
|
||||
:hint-type (:type hint)})
|
||||
props
|
||||
(if error
|
||||
(if (and touched? error)
|
||||
(mf/spread-props props {:hint-type "error"
|
||||
:hint-message (:message error)})
|
||||
props)
|
||||
@@ -293,10 +302,10 @@
|
||||
(mf/spread-props props {:hint-formated true})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name name]
|
||||
(mf/with-effect [resolve-stream tokens token input-name name token-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/mapcat (partial resolve-value tokens token token-name))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
@@ -332,6 +341,7 @@
|
||||
message (tr "workspace.tokens.resolved-value" (or resolved-value value))]
|
||||
(swap! form update :errors dissoc :value)
|
||||
(swap! form update :extra-errors dissoc :value)
|
||||
(swap! form update :async-errors dissoc :reference)
|
||||
(if (= input-value (str resolved-value))
|
||||
(reset! hint* {})
|
||||
(reset! hint* {:message message :type "hint"})))))))]
|
||||
@@ -359,7 +369,7 @@
|
||||
|
||||
(let [form (mf/use-ctx fc/context)
|
||||
input-name name
|
||||
|
||||
token-name (get-in @form [:data :name] nil)
|
||||
|
||||
error
|
||||
(get-in @form [:errors :value value-subfield index input-name])
|
||||
@@ -404,10 +414,10 @@
|
||||
(mf/spread-props props {:hint-formated true})
|
||||
props)]
|
||||
|
||||
(mf/with-effect [resolve-stream tokens token input-name index value-subfield]
|
||||
(mf/with-effect [resolve-stream tokens token input-name index value-subfield token-name]
|
||||
(let [subs (->> resolve-stream
|
||||
(rx/debounce 300)
|
||||
(rx/mapcat (partial resolve-value tokens token))
|
||||
(rx/mapcat (partial resolve-value tokens token token-name))
|
||||
(rx/map (fn [result]
|
||||
(d/update-when result :error
|
||||
(fn [error]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
[app.main.constants :refer [max-input-length]]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.data.workspace.tokens.propagation :as dwtp]
|
||||
[app.main.refs :as refs]
|
||||
@@ -101,13 +102,6 @@
|
||||
active-tab* (mf/use-state #(if (cft/is-reference? token) :reference :composite))
|
||||
active-tab (deref active-tab*)
|
||||
|
||||
on-toggle-tab
|
||||
(mf/use-fn
|
||||
(mf/deps)
|
||||
(fn [new-tab]
|
||||
(let [new-tab (keyword new-tab)]
|
||||
(reset! active-tab* new-tab))))
|
||||
|
||||
token
|
||||
(mf/with-memo [token]
|
||||
(or token {:type token-type}))
|
||||
@@ -117,8 +111,7 @@
|
||||
|
||||
token-title (str/lower (:title token-properties))
|
||||
|
||||
tokens
|
||||
(mf/deref refs/workspace-active-theme-sets-tokens)
|
||||
tokens (mf/deref refs/workspace-all-tokens-map)
|
||||
|
||||
tokens
|
||||
(mf/with-memo [tokens token]
|
||||
@@ -144,6 +137,17 @@
|
||||
(fm/use-form :schema schema
|
||||
:initial initial)
|
||||
|
||||
on-toggle-tab
|
||||
(mf/use-fn
|
||||
(mf/deps form)
|
||||
(fn [new-tab]
|
||||
(let [new-tab (keyword new-tab)]
|
||||
(if (= new-tab :reference)
|
||||
(swap! form assoc-in [:async-errors :reference]
|
||||
{:message "Need valid reference"})
|
||||
(swap! form update :async-errors dissoc :reference))
|
||||
(reset! active-tab* new-tab))))
|
||||
|
||||
warning-name-change?
|
||||
(not= (get-in @form [:data :name])
|
||||
(:name initial))
|
||||
@@ -203,7 +207,11 @@
|
||||
:value (:value valid-token)
|
||||
:description description}))
|
||||
(dwtp/propagate-workspace-tokens)
|
||||
(modal/hide))))))))]
|
||||
(modal/hide)))
|
||||
(fn [{:keys [errors]}]
|
||||
(let [error-messages (wte/humanize-errors errors)
|
||||
error-message (first error-messages)]
|
||||
(swap! form assoc-in [:extra-errors :value] {:message error-message}))))))))]
|
||||
|
||||
[:> fc/form* {:class (stl/css :form-wrapper)
|
||||
:form form
|
||||
|
||||
@@ -291,6 +291,7 @@
|
||||
[:color {:optional true} [:maybe :string]]
|
||||
[:color-result {:optional true} ::sm/any]
|
||||
[:inset {:optional true} [:maybe :boolean]]]]]
|
||||
|
||||
(if (= active-tab :reference)
|
||||
[:reference {:optional false} ::sm/text]
|
||||
[:reference {:optional true} [:maybe :string]])]]
|
||||
|
||||
@@ -1185,7 +1185,6 @@
|
||||
{:cmd :export-shapes
|
||||
:profile-id (:profile-id @st/state)
|
||||
:wait true
|
||||
:skip-children (:skip-children value false)
|
||||
:exports [{:file-id file-id
|
||||
:page-id page-id
|
||||
:object-id id
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
|
||||
(defn- load
|
||||
[locale]
|
||||
(let [path (str "./translation." locale ".js?version=" (:full cf/version))]
|
||||
(let [path (str "./translation." locale ".js?version=" cf/version-tag)]
|
||||
(->> (mod/import path)
|
||||
(p/fmap (fn [result] (unchecked-get result "default")))
|
||||
(p/fnly (fn [data cause]
|
||||
|
||||
@@ -7551,6 +7551,10 @@ msgstr "Remap Token References"
|
||||
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"
|
||||
|
||||
@@ -4428,6 +4428,10 @@ msgstr "Actualizar referencias de token"
|
||||
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"
|
||||
@@ -7753,7 +7757,7 @@ msgstr "Line height (multiplicador, px o %) o {alias}"
|
||||
|
||||
#: src/app/main/data/workspace/tokens/errors.cljs:57
|
||||
msgid "workspace.tokens.missing-references"
|
||||
msgstr "Referéncias de tokens no encontradas:"
|
||||
msgstr "Referencias de tokens no encontradas: "
|
||||
|
||||
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
|
||||
msgid "workspace.tokens.more-options"
|
||||
|
||||
@@ -1191,21 +1191,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: soft
|
||||
|
||||
"@penpot/plugin-types@npm:^1.3.2":
|
||||
version: 1.3.2
|
||||
resolution: "@penpot/plugin-types@npm:1.3.2"
|
||||
checksum: 10c0/3f624472c260721ad89bf8d944e75acf6a9c9577271a757acb77574102213914051d1a32d5ab16e6ba16ae077fff78cf7a0f6d11d18351dfc214426677a67468
|
||||
"@penpot/plugin-types@npm:^1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@penpot/plugin-types@npm:1.4.2"
|
||||
checksum: 10c0/b0972fe75c97e697eb1044c89db660393886b3c30676f8436ff4ab56c5bf0397b2c675697ae1b9c5fe40bc95a803aecf6d7ac356dbf6d3535bf8baec5d05eab1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@penpot/plugins-runtime@npm:1.3.2":
|
||||
version: 1.3.2
|
||||
resolution: "@penpot/plugins-runtime@npm:1.3.2"
|
||||
"@penpot/plugins-runtime@npm:1.4.2":
|
||||
version: 1.4.2
|
||||
resolution: "@penpot/plugins-runtime@npm:1.4.2"
|
||||
dependencies:
|
||||
"@penpot/plugin-types": "npm:^1.3.2"
|
||||
"@penpot/plugin-types": "npm:^1.4.2"
|
||||
ses: "npm:^1.1.0"
|
||||
zod: "npm:^3.22.4"
|
||||
checksum: 10c0/b6d2cb3a57bcbe58232db52b8224d1817495e96b34997bfa72421629b5f34a8c9cc71357c315dcab9d52ea036ed632a5efe0ac50f52e730901c02d498dfa1313
|
||||
checksum: 10c0/af084d906cce9a6dea956fe5420111d7ea37c7620737a1e3d4f12958cb302a8f697c1229c237107c28fbb3b9f37eee308e6d16262b04ad56ae6f76c7a12f12e5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4176,7 +4176,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@penpot/draft-js": "portal:./packages/draft-js"
|
||||
"@penpot/mousetrap": "portal:./packages/mousetrap"
|
||||
"@penpot/plugins-runtime": "npm:1.3.2"
|
||||
"@penpot/plugins-runtime": "npm:1.4.2"
|
||||
"@penpot/svgo": "penpot/svgo#v3.2"
|
||||
"@penpot/text-editor": "portal:./text-editor"
|
||||
"@playwright/test": "npm:1.57.0"
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
## 1.4.2 (2026-01-21)
|
||||
|
||||
- **plugin-types:** fix atob/btoa functions
|
||||
|
||||
## 1.4.0 (2026-01-21)
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- switch component ([7d68450](https://github.com/penpot/penpot-plugins/commit/7d68450))
|
||||
- Add variants to plugins API ([04f3c26](https://github.com/penpot/penpot-plugins/commit/04f3c26))
|
||||
- format ci job ([17b5834](https://github.com/penpot/penpot-plugins/commit/17b5834))
|
||||
- fix problem with ci ([4b3c50f](https://github.com/penpot/penpot-plugins/commit/4b3c50f))
|
||||
- change in workflow ([3a69f51](https://github.com/penpot/penpot-plugins/commit/3a69f51))
|
||||
- **plugin-types:** add methods to modify the index for shapes ([4ad50af](https://github.com/penpot/penpot-plugins/commit/4ad50af))
|
||||
- **plugin-types:** change content type and added new attributes ([dbb68a5](https://github.com/penpot/penpot-plugins/commit/dbb68a5))
|
||||
- **plugins-runtime:** add data method to image data ([f077481](https://github.com/penpot/penpot-plugins/commit/f077481))
|
||||
- **plugins-runtime:** fix problem with linter ([30f4984](https://github.com/penpot/penpot-plugins/commit/30f4984))
|
||||
- **plugins-runtime:** allow openPage() to toggle opening on a new window or not ([da8288b](https://github.com/penpot/penpot-plugins/commit/da8288b))
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
- package-lock.json ([d1d940a](https://github.com/penpot/penpot-plugins/commit/d1d940a))
|
||||
- fonts gdpr & switch provider ([d63231e](https://github.com/penpot/penpot-plugins/commit/d63231e))
|
||||
- missing changes ([b8fc936](https://github.com/penpot/penpot-plugins/commit/b8fc936))
|
||||
- format ci ([e0fab2e](https://github.com/penpot/penpot-plugins/commit/e0fab2e))
|
||||
- fetch main only in pr ([e48c5d4](https://github.com/penpot/penpot-plugins/commit/e48c5d4))
|
||||
|
||||
### ❤️ Thank You
|
||||
|
||||
- alonso.torres
|
||||
- Juanfran @juanfran
|
||||
- Michał Korczak
|
||||
- Miguel de Benito Delgado
|
||||
- Pablo Alba
|
||||
|
||||
## 1.3.2 (2025-07-04)
|
||||
|
||||
### 🩹 Fixes
|
||||
|
||||
@@ -7,6 +7,29 @@ This guide details the process of publishing `plugin-types`,
|
||||
for plugin development. Below is a walkthrough for publishing these
|
||||
packages and managing releases.
|
||||
|
||||
**Warning**
|
||||
Before generating the release, please, check the update the changelog with
|
||||
the changes that will be released.
|
||||
|
||||
## Problem with pnpm
|
||||
|
||||
There is an issue with dependencies and release with pnpm. For it to work
|
||||
you need to add the following into your `.npmrc`
|
||||
|
||||
```
|
||||
link-workspace-packages=true
|
||||
```
|
||||
|
||||
## NPM Authentication
|
||||
|
||||
You need to generate a temporary access token in the NPM website.
|
||||
|
||||
Once you have the token add the following to the `.npmrc`
|
||||
|
||||
```
|
||||
//registry.npmjs.org/:_authToken=<TOKEN>
|
||||
```
|
||||
|
||||
## Publishing Libraries
|
||||
|
||||
Publishing packages enables the distribution of types and styles
|
||||
@@ -35,28 +58,16 @@ pnpm run release -- --dry-run false
|
||||
|
||||
This command will:
|
||||
|
||||
- Update the `CHANGELOG.md`
|
||||
- Update the library's `package.json` version
|
||||
- Generate a commit
|
||||
- Create a new git tag
|
||||
- Publish to NPM with the `latest` tag
|
||||
|
||||
Ensure everything is correct before proceeding with the git push. Once
|
||||
verified, execute the following commands:
|
||||
|
||||
```shell
|
||||
git commit -m ":arrow_up: Updated plugins release to X.X.X"
|
||||
git push
|
||||
git push origin vX.X.X
|
||||
```
|
||||
|
||||
Replace `vX.X.X` with the new version number.
|
||||
|
||||
> 📘 To update the documentation site, you must also update the `stable` branch:
|
||||
|
||||
```shell
|
||||
git checkout stable
|
||||
git merge main
|
||||
git push origin stable
|
||||
```
|
||||
|
||||
For detailed information, refer to the [Nx Release
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@penpot/plugin-types",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.2",
|
||||
"typings": "./index.d.ts",
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@penpot/plugins-runtime",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.2",
|
||||
"dependencies": {
|
||||
"@penpot/plugin-types": "^1.3.2",
|
||||
"@penpot/plugin-types": "^1.4.2",
|
||||
"ses": "^1.1.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
|
||||
@@ -118,8 +118,8 @@ export function createSandbox(
|
||||
// Window properties
|
||||
console: ses.harden(window.console),
|
||||
devicePixelRatio: ses.harden(window.devicePixelRatio),
|
||||
atob: ses.harden(window.atob),
|
||||
btoa: ses.harden(window.btoa),
|
||||
atob: ses.harden(window.atob.bind(null)),
|
||||
btoa: ses.harden(window.btoa.bind(null)),
|
||||
structuredClone: ses.harden(window.structuredClone),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@penpot/plugin-styles",
|
||||
"version": "1.3.2",
|
||||
"version": "1.4.2",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
14
plugins/pnpm-lock.yaml
generated
@@ -230,8 +230,8 @@ importers:
|
||||
libs/plugins-runtime:
|
||||
dependencies:
|
||||
'@penpot/plugin-types':
|
||||
specifier: ^1.3.2
|
||||
version: 1.3.2
|
||||
specifier: ^1.4.2
|
||||
version: link:../plugin-types
|
||||
ses:
|
||||
specifier: ^1.1.0
|
||||
version: 1.14.0
|
||||
@@ -4200,12 +4200,6 @@ packages:
|
||||
}
|
||||
engines: { node: '>= 10.0.0' }
|
||||
|
||||
'@penpot/plugin-types@1.3.2':
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-f0kmmZaFNs9sGtSmqmSJQYCs5Qt+KYgTD8RneUjL+Dv+zfNQnd5e4L+iHSYFJ4HWvcDvTiK7F/gya7PwMTu7WA==,
|
||||
}
|
||||
|
||||
'@phenomnomnominal/tsquery@5.0.1':
|
||||
resolution:
|
||||
{
|
||||
@@ -13194,6 +13188,7 @@ packages:
|
||||
integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==,
|
||||
}
|
||||
engines: { node: '>=10' }
|
||||
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
|
||||
|
||||
tar@7.5.2:
|
||||
resolution:
|
||||
@@ -13201,6 +13196,7 @@ packages:
|
||||
integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==,
|
||||
}
|
||||
engines: { node: '>=18' }
|
||||
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
|
||||
|
||||
terser-webpack-plugin@5.3.16:
|
||||
resolution:
|
||||
@@ -18203,8 +18199,6 @@ snapshots:
|
||||
'@parcel/watcher-win32-x64': 2.5.1
|
||||
optional: true
|
||||
|
||||
'@penpot/plugin-types@1.3.2': {}
|
||||
|
||||
'@phenomnomnominal/tsquery@5.0.1(typescript@5.6.3)':
|
||||
dependencies:
|
||||
esquery: 1.6.0
|
||||
|
||||
@@ -69,17 +69,6 @@ const determineArgs = async () => {
|
||||
},
|
||||
);
|
||||
|
||||
await releaseChangelog({
|
||||
dryRun: args.dryRun,
|
||||
versionData: result.projectsVersionData,
|
||||
version: result.workspaceVersion,
|
||||
gitCommitMessage: `chore(release): publish ${result.workspaceVersion} [skip ci]`,
|
||||
gitCommit: true,
|
||||
gitTag: true,
|
||||
verbose: args.verbose,
|
||||
firstRelease: args.firstRelease,
|
||||
});
|
||||
|
||||
if (!args.skipPublish) {
|
||||
await releasePublish({
|
||||
dryRun: args.dryRun,
|
||||
|
||||
4
plugins/wrangle-penpot-plugins-api-doc.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
name = "penpot-plugins-api-doc"
|
||||
compatibility_date = "2025-01-01"
|
||||
|
||||
assets = { directory = "dist/doc" }
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export CURRENT_VERSION=${CURRENT_VERSION:-develop};
|
||||
export VERSION_TAG=${VERSION:-develop};
|
||||
|
||||
if [ "$NODE_ENV" = "production" ]; then
|
||||
export BUILD_MODE="release";
|
||||
@@ -81,7 +81,7 @@ function copy_artifacts {
|
||||
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js $DEST/$BUILD_NAME.js;
|
||||
cp target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.wasm $DEST/$BUILD_NAME.wasm;
|
||||
|
||||
sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$CURRENT_VERSION/g" $DEST/$BUILD_NAME.js;
|
||||
sed -i "s/render_wasm.wasm/$BUILD_NAME.wasm?version=$VERSION_TAG/g" $DEST/$BUILD_NAME.js;
|
||||
|
||||
yarn esbuild target/wasm32-unknown-emscripten/$BUILD_MODE/render_wasm.js \
|
||||
--log-level=error \
|
||||
|
||||