mirror of
https://github.com/penpot/penpot.git
synced 2026-01-06 13:28:57 -05:00
Compare commits
207 Commits
eva-bugfix
...
palba-nitr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eca75ccecc | ||
|
|
66c8e1e1d6 | ||
|
|
69bbdad570 | ||
|
|
df4279bdee | ||
|
|
c8c901ee4c | ||
|
|
8f0e5e36e9 | ||
|
|
a5e9f7229b | ||
|
|
5f22220a8b | ||
|
|
6c7661b04d | ||
|
|
b867f276f2 | ||
|
|
da8d7a78cf | ||
|
|
ec4936f5fe | ||
|
|
dd9ec54bd1 | ||
|
|
3ad4b0a453 | ||
|
|
481fa44f18 | ||
|
|
42c9f2123d | ||
|
|
d18a018236 | ||
|
|
4ab6ecec21 | ||
|
|
b39c00fbf6 | ||
|
|
8a0fddf1e4 | ||
|
|
95fdd75030 | ||
|
|
54489c4285 | ||
|
|
6815806669 | ||
|
|
febe87aa7b | ||
|
|
99e8b22672 | ||
|
|
65adbfaadb | ||
|
|
0581c60800 | ||
|
|
7e92408807 | ||
|
|
03eeeda44f | ||
|
|
2f33009e69 | ||
|
|
1d5c407456 | ||
|
|
aa15232cc7 | ||
|
|
f53935f5df | ||
|
|
de04026dc8 | ||
|
|
f3b914534f | ||
|
|
fcc9282304 | ||
|
|
122619b197 | ||
|
|
dbf9bdceb5 | ||
|
|
f6eb492329 | ||
|
|
c66a8f5dc5 | ||
|
|
ed4df73e42 | ||
|
|
59e745e9ab | ||
|
|
d4b4d943c6 | ||
|
|
e4b4f1bd08 | ||
|
|
e58b2453b1 | ||
|
|
e9230b8b54 | ||
|
|
9d7cac5e73 | ||
|
|
17fefcf0bc | ||
|
|
4367bd2dc6 | ||
|
|
6e2b2e8924 | ||
|
|
f3805e3b70 | ||
|
|
262937c421 | ||
|
|
15ee75a692 | ||
|
|
942e3300dd | ||
|
|
eaa3904a3a | ||
|
|
0c66b5db73 | ||
|
|
cc40448cb5 | ||
|
|
6a2029ca3b | ||
|
|
f32913adcf | ||
|
|
d906f05a6f | ||
|
|
2402334fb2 | ||
|
|
c3e2621ed5 | ||
|
|
d37695d7a5 | ||
|
|
fadbe24aaa | ||
|
|
9d29d5e8cc | ||
|
|
e681f95a70 | ||
|
|
5c8b401037 | ||
|
|
9dfb0ebe84 | ||
|
|
08162c825d | ||
|
|
bc700334ca | ||
|
|
133590f19c | ||
|
|
66c5a0570e | ||
|
|
94cbf9d8f2 | ||
|
|
70143f8ae3 | ||
|
|
6c824651df | ||
|
|
1b81ddebb4 | ||
|
|
6076df5c80 | ||
|
|
6d2d66a079 | ||
|
|
239af4fb82 | ||
|
|
0ad4a9ca7e | ||
|
|
034463e63a | ||
|
|
aadc1aac1c | ||
|
|
2cdc76f1af | ||
|
|
23f49237f8 | ||
|
|
93fb54c116 | ||
|
|
7565bb8d24 | ||
|
|
0d394ee962 | ||
|
|
c4bebc1b0a | ||
|
|
6edc29dce2 | ||
|
|
d773e3a966 | ||
|
|
e18aef1d39 | ||
|
|
b033690239 | ||
|
|
9f732eb45a | ||
|
|
474453a503 | ||
|
|
c3d40659a9 | ||
|
|
15e2b35afc | ||
|
|
ad15887d57 | ||
|
|
d01f921344 | ||
|
|
9e035ec4fe | ||
|
|
fbacdf0351 | ||
|
|
3f4d699395 | ||
|
|
1626371337 | ||
|
|
4d8a70f1fa | ||
|
|
14d5de29da | ||
|
|
df718c940f | ||
|
|
80c78d9cd4 | ||
|
|
e2ce226814 | ||
|
|
28c4c1a286 | ||
|
|
f64105ad08 | ||
|
|
a346d29d76 | ||
|
|
2c37c5c8ed | ||
|
|
ed767d9a5b | ||
|
|
57bfca4062 | ||
|
|
e9dcd64463 | ||
|
|
b498056c01 | ||
|
|
81f851cad4 | ||
|
|
479ce99b32 | ||
|
|
6290b88d2e | ||
|
|
dba718b850 | ||
|
|
7c1205018b | ||
|
|
89763d7c5a | ||
|
|
7f6af6179b | ||
|
|
ceb184782f | ||
|
|
247c5c3700 | ||
|
|
0882c448f6 | ||
|
|
f8cebb9d63 | ||
|
|
1e248c7177 | ||
|
|
351a35dad6 | ||
|
|
eb088c31c1 | ||
|
|
45af469a11 | ||
|
|
232f2271d3 | ||
|
|
a30315c91c | ||
|
|
04542e1e66 | ||
|
|
36c986d8e8 | ||
|
|
38c3b2eaba | ||
|
|
98e91ecda5 | ||
|
|
54ac64db4b | ||
|
|
30ca6bf6ff | ||
|
|
81a364dfc4 | ||
|
|
c6b9954af8 | ||
|
|
f120cf82d3 | ||
|
|
7ec335ae96 | ||
|
|
8dcc46aba8 | ||
|
|
058a555594 | ||
|
|
e073b89604 | ||
|
|
140290cd60 | ||
|
|
5e6af5aea9 | ||
|
|
5df2a740b9 | ||
|
|
fd596a1371 | ||
|
|
87221eb7db | ||
|
|
69f2e131d7 | ||
|
|
69da63e01c | ||
|
|
dc689f9756 | ||
|
|
82e1a5003c | ||
|
|
024697ff87 | ||
|
|
fc4b717287 | ||
|
|
9e8cdc8a3f | ||
|
|
a51fd009bc | ||
|
|
f795f20ef8 | ||
|
|
ca21e7e8b4 | ||
|
|
93e7f2950b | ||
|
|
d0e5d0d952 | ||
|
|
e4c07e0ec0 | ||
|
|
068caf2784 | ||
|
|
436bc23da4 | ||
|
|
579de6558a | ||
|
|
2d45cba36c | ||
|
|
cf21ffb30f | ||
|
|
7a2fe232d5 | ||
|
|
9e17a0e65d | ||
|
|
220c27c354 | ||
|
|
b0e4257e56 | ||
|
|
b3cb7df33c | ||
|
|
fec420b6e9 | ||
|
|
35af5455a0 | ||
|
|
216b2d3072 | ||
|
|
bbc6709943 | ||
|
|
14f6e22610 | ||
|
|
2f27a78bc0 | ||
|
|
f5761066a9 | ||
|
|
3665bccaed | ||
|
|
fbbee98c3d | ||
|
|
854ad5bb4d | ||
|
|
a32f44a62c | ||
|
|
95f58ffda5 | ||
|
|
e8e27c25c0 | ||
|
|
42c416e3cb | ||
|
|
5ad04e0f4c | ||
|
|
9f4db4479c | ||
|
|
66997d2bc9 | ||
|
|
7350329658 | ||
|
|
544b118925 | ||
|
|
8ceb909cda | ||
|
|
af54e6ccc2 | ||
|
|
6ef0b8fd16 | ||
|
|
4a6d143a15 | ||
|
|
07dedbd3bb | ||
|
|
7ca8bf32b2 | ||
|
|
2e6fb1b9c5 | ||
|
|
8e8d46b314 | ||
|
|
e964f9820e | ||
|
|
d933e91c6c | ||
|
|
9266ace537 | ||
|
|
b057ed1b9a | ||
|
|
2c5abb0cbf | ||
|
|
7f6bffdbfc | ||
|
|
b4cd955484 |
4
.github/workflows/build-bundle.yml
vendored
4
.github/workflows/build-bundle.yml
vendored
@@ -84,8 +84,10 @@ jobs:
|
||||
uses: mattermost/action-mattermost-notify@master
|
||||
with:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
||||
TEXT: |
|
||||
❌ *[PENPOT] Error during the execution of the job*
|
||||
❌ 📦 *[PENPOT] Error building penpot bundles.*
|
||||
📄 Triggered from ref: `${{ steps.vars.outputs.gh_ref }}`
|
||||
Bundle version: `${{ steps.vars.outputs.bundle_version }}`
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@infra
|
||||
|
||||
51
.github/workflows/build-docker.yml
vendored
51
.github/workflows/build-docker.yml
vendored
@@ -34,18 +34,26 @@ jobs:
|
||||
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download Penpot Bundles
|
||||
id: bundles
|
||||
env:
|
||||
FILE_NAME: penpot-${{ steps.vars.outputs.gh_ref }}.zip
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION }}
|
||||
run: |
|
||||
tmp=$(aws s3api head-object \
|
||||
--bucket ${{ secrets.S3_BUCKET }} \
|
||||
--key "$FILE_NAME" \
|
||||
--query 'Metadata."bundle-version"' \
|
||||
--output text)
|
||||
echo "bundle_version=$tmp" >> $GITHUB_OUTPUT
|
||||
pushd docker/images
|
||||
aws s3 cp s3://${{ secrets.S3_BUCKET }}/$FILE_NAME .
|
||||
unzip $FILE_NAME > /dev/null
|
||||
mv penpot/backend bundle-backend
|
||||
mv penpot/frontend bundle-frontend
|
||||
mv penpot/exporter bundle-exporter
|
||||
mv penpot/storybook bundle-storybook
|
||||
popd
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
@@ -58,6 +66,18 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Extract metadata (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images:
|
||||
frontend
|
||||
backend
|
||||
exporter
|
||||
storybook
|
||||
labels: |
|
||||
bundle_version=${{ steps.bundles.outputs.bundle_version }}
|
||||
|
||||
- name: Build and push Backend Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
env:
|
||||
@@ -69,6 +89,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
|
||||
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
|
||||
|
||||
@@ -83,6 +104,7 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
|
||||
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
|
||||
|
||||
@@ -97,5 +119,34 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
|
||||
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
|
||||
|
||||
- name: Build and push Storybook Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
env:
|
||||
DOCKER_IMAGE: 'storybook'
|
||||
BUNDLE_PATH: './bundle-storybook'
|
||||
with:
|
||||
context: ./docker/images/
|
||||
file: ./docker/images/Dockerfile.storybook
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ steps.vars.outputs.gh_ref }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache
|
||||
cache-to: type=registry,ref=${{ secrets.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:buildcache,mode=max
|
||||
|
||||
- name: Notify Mattermost
|
||||
if: failure()
|
||||
uses: mattermost/action-mattermost-notify@master
|
||||
with:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
||||
TEXT: |
|
||||
❌ 🐳 *[PENPOT] Error building penpot docker images.*
|
||||
📄 Triggered from ref: `${{ steps.vars.outputs.gh_ref }}`
|
||||
📦 Bundle: `${{ steps.bundles.outputs.bundle_version }}`
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@infra
|
||||
|
||||
2
.github/workflows/commit-checker.yml
vendored
2
.github/workflows/commit-checker.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Check Commit Type
|
||||
uses: gsactions/commit-message-checker@v2
|
||||
with:
|
||||
pattern: '^(Merge|Revert|:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s["A-Z].*[^.]$'
|
||||
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert).+[^.])$'
|
||||
flags: 'gm'
|
||||
error: 'Commit should match CONTRIBUTING.md guideline'
|
||||
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request
|
||||
|
||||
18
.github/workflows/release.yml
vendored
18
.github/workflows/release.yml
vendored
@@ -60,12 +60,12 @@ jobs:
|
||||
EXTRA_TAGS=("main" "latest")
|
||||
|
||||
for image in "${IMAGES[@]}"; do
|
||||
docker pull "$REGISTRY/penpotapp/$image:$TAG"
|
||||
docker tag "$REGISTRY/penpotapp/$image:$TAG" "penpotapp/$image:$TAG"
|
||||
docker pull "$REGISTRY/$image:$TAG"
|
||||
docker tag "$REGISTRY/$image:$TAG" "penpotapp/$image:$TAG"
|
||||
docker push "penpotapp/$image:$TAG"
|
||||
|
||||
for tag in "${EXTRA_TAGS[@]}"; do
|
||||
docker tag "$REGISTRY/penpotapp/$image:$TAG" "penpotapp/$image:$tag"
|
||||
docker tag "$REGISTRY/$image:$TAG" "penpotapp/$image:$tag"
|
||||
docker push "penpotapp/$image:$tag"
|
||||
done
|
||||
done
|
||||
@@ -93,3 +93,15 @@ jobs:
|
||||
tag_name: ${{ steps.vars.outputs.gh_ref }}
|
||||
name: ${{ steps.vars.outputs.gh_ref }}
|
||||
body: ${{ steps.extract_release_notes.outputs.release_notes }}
|
||||
|
||||
- name: Notify Mattermost
|
||||
if: failure()
|
||||
uses: mattermost/action-mattermost-notify@master
|
||||
with:
|
||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
||||
TEXT: |
|
||||
❌ 🚀 *[PENPOT] Error releasing penpot.*
|
||||
📄 Triggered from ref: `${{ steps.vars.outputs.gh_ref }}`
|
||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@infra
|
||||
|
||||
35
CHANGES.md
35
CHANGES.md
@@ -9,11 +9,20 @@
|
||||
### :heart: Community contributions (Thank you!)
|
||||
|
||||
### :sparkles: New features & Enhancements
|
||||
- Select boards to export as PDF [Taiga #12320](https://tree.taiga.io/project/penpot/issue/12320)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix text line-height values are wrong [Taiga #12252](https://tree.taiga.io/project/penpot/issue/12252)
|
||||
- Fix an error translation [Taiga #12402](https://tree.taiga.io/project/penpot/issue/12402)
|
||||
- Fix pan cursor not disabling viewport guides [Github #6985](https://github.com/penpot/penpot/issues/6985)
|
||||
- Fix viewport resize on locked shapes [Taiga #11974](https://tree.taiga.io/project/penpot/issue/11974)
|
||||
- Fix nested variant in a component doesn't keep inherited overrides [Taiga #12299](https://tree.taiga.io/project/penpot/issue/12299)
|
||||
- Fix on copy instance inside a components chain touched are missing [Taiga #12371](https://tree.taiga.io/project/penpot/issue/12371)
|
||||
- Fix problem with multiple selection and shadows [Github #7437](https://github.com/penpot/penpot/issues/7437)
|
||||
- Fix search shortcut [Taiga #10265](https://tree.taiga.io/project/penpot/issue/10265)
|
||||
- Fix shortcut conflict in text editor (increase/decrease font size vs word selection)
|
||||
- Fix problem with plugins generating code for pages different than current one [Taiga #12312](https://tree.taiga.io/project/penpot/issue/12312)
|
||||
|
||||
## 2.11.0 (Unreleased)
|
||||
|
||||
@@ -56,6 +65,7 @@
|
||||
- Alternative ways of creating variants - Button Viewport [Taiga #11931](https://tree.taiga.io/project/penpot/us/11931)
|
||||
- Reorder properties for a component [Taiga #10225](https://tree.taiga.io/project/penpot/us/10225)
|
||||
- File Data storage layout refactor [Github #7345](https://github.com/penpot/penpot/pull/7345)
|
||||
- Make several queries optimization on comment threads [Github #7506](https://github.com/penpot/penpot/pull/7506)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
@@ -70,7 +80,25 @@
|
||||
- Fix auto-width changes to fixed when switching variants [Taiga #12172](https://tree.taiga.io/project/penpot/issue/12172)
|
||||
- Fix component number has no singular translation string [Taiga #12106](https://tree.taiga.io/project/penpot/issue/12106)
|
||||
- Fix adding/removing identical text fills [Taiga #12287](https://tree.taiga.io/project/penpot/issue/12287)
|
||||
|
||||
- Fix scroll on the inspect tab [Taiga #12293](https://tree.taiga.io/project/penpot/issue/12293)
|
||||
- Fix lock proportion tooltip [Taiga #12326](https://tree.taiga.io/project/penpot/issue/12326)
|
||||
- Fix internal Error when selecting a set by name in the token theme editor [Taiga #12310](https://tree.taiga.io/project/penpot/issue/12310)
|
||||
- Fix drag & drop functionality is swapping instead or reordering [Taiga #12254](https://tree.taiga.io/project/penpot/issue/12254)
|
||||
- Fix variants not syncronizing tokens on switch [Taiga #12290](https://tree.taiga.io/project/penpot/issue/12290)
|
||||
- Fix incorrect behavior of Alt + Drag for variants [Taiga #12309](https://tree.taiga.io/project/penpot/issue/12309)
|
||||
- Fix text override is lost after switch [Taiga #12269](https://tree.taiga.io/project/penpot/issue/12269)
|
||||
- Fix exporting a board crashing the app [Taiga #12384](https://tree.taiga.io/project/penpot/issue/12384)
|
||||
- Fix nested variant in a component doesn't keep inherited overrides [Taiga #12299](https://tree.taiga.io/project/penpot/issue/12299)
|
||||
- Fix selected colors not showing colors from children shapes in multiple selection [Taiga #12384](https://tree.taiga.io/project/penpot/issue/12385)
|
||||
- Fix scrollbar issue in design tab [Taiga #12367](https://tree.taiga.io/project/penpot/issue/12367)
|
||||
- Fix library update notificacions showing when they should not [Taiga #12397](https://tree.taiga.io/project/penpot/issue/12397)
|
||||
- Fix remove flex button doesn’t work within variant [Taiga #12314](https://tree.taiga.io/project/penpot/issue/12314)
|
||||
- Fix an error translation [Taiga #12402](https://tree.taiga.io/project/penpot/issue/12402)
|
||||
- Fix problem with certain text input in some editable labels (pages, components, tokens...) being in conflict with the drag/drop functionality [Taiga #12316](https://tree.taiga.io/project/penpot/issue/12316)
|
||||
- Fix not controlled theme renaming [Taiga #12411](https://tree.taiga.io/project/penpot/issue/12411)
|
||||
- Fix paste without selection sends the new element in the back [Taiga #12382](https://tree.taiga.io/project/penpot/issue/12382)
|
||||
- Fix options button does not work for comments created in the lower part of the screen [Taiga #12422](https://tree.taiga.io/project/penpot/issue/12422)
|
||||
- Fix problem when checking usage with removed teams [Taiga #12442](https://tree.taiga.io/project/penpot/issue/12442)
|
||||
|
||||
## 2.10.1
|
||||
|
||||
@@ -78,12 +106,10 @@
|
||||
|
||||
- Improve workpace file loading [Github 7366](https://github.com/penpot/penpot/pull/7366)
|
||||
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix regression with text shapes creation with Plugins API [Taiga #12244](https://tree.taiga.io/project/penpot/issue/12244)
|
||||
|
||||
|
||||
## 2.10.0
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
@@ -99,7 +125,7 @@
|
||||
- Add efficiency enhancements to right sidebar [Github #7182](https://github.com/penpot/penpot/pull/7182)
|
||||
- Add defaults for artboard drawing [Taiga #494](https://tree.taiga.io/project/penpot/us/494?milestone=465047)
|
||||
- Continuous display of distances between elements when moving a layer with the keyboard [Taiga #1780](https://tree.taiga.io/project/penpot/us/1780)
|
||||
- New Number token - unitless values [Taiga #10936](https://tree.taiga.io/project/penpot/us/10936)
|
||||
- New Number token - unitless values [Taiga #10936](https://tree.taiga.io/project/penpot/us/10936)
|
||||
- New font-family token [Taiga #10937](https://tree.taiga.io/project/penpot/us/10937)
|
||||
- New text case token [Taiga #10942](https://tree.taiga.io/project/penpot/us/10942)
|
||||
- New text-decoration token [Taiga #10941](https://tree.taiga.io/project/penpot/us/10941)
|
||||
@@ -180,7 +206,6 @@
|
||||
- Add info to apply-token event [Taiga #11710](https://tree.taiga.io/project/penpot/task/11710)
|
||||
- Fix double click on set name input [Taiga #11747](https://tree.taiga.io/project/penpot/issue/11747)
|
||||
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Copying font size does not copy the unit [Taiga #11143](https://tree.taiga.io/project/penpot/issue/11143)
|
||||
|
||||
@@ -8,38 +8,41 @@
|
||||
<body>
|
||||
<p>
|
||||
<strong>Feedback from:</strong><br />
|
||||
{% if profile %}
|
||||
<span>
|
||||
<span>Name: </span>
|
||||
<span><code>{{profile.fullname|abbreviate:25}}</code></span>
|
||||
</span>
|
||||
<br />
|
||||
|
||||
<span>
|
||||
<span>Email: </span>
|
||||
<span>{{profile.email}}</span>
|
||||
</span>
|
||||
<br />
|
||||
|
||||
<span>
|
||||
<span>ID: </span>
|
||||
<span><code>{{profile.id}}</code></span>
|
||||
</span>
|
||||
{% else %}
|
||||
<span>
|
||||
<span>Email: </span>
|
||||
<span>{{profile.email}}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span>
|
||||
<span>Name: </span>
|
||||
<span><code>{{profile.fullname|abbreviate:25}}</code></span>
|
||||
</span>
|
||||
<br />
|
||||
<span>
|
||||
<span>Email: </span>
|
||||
<span>{{profile.email}}</span>
|
||||
</span>
|
||||
<br />
|
||||
<span>
|
||||
<span>ID: </span>
|
||||
<span><code>{{profile.id}}</code></span>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Subject:</strong><br />
|
||||
<span>{{subject|abbreviate:300}}</span>
|
||||
<span>{{feedback-subject|abbreviate:300}}</span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Type:</strong><br />
|
||||
<span>{{feedback-type|abbreviate:300}}</span>
|
||||
</p>
|
||||
|
||||
{% if feedback-error-href %}
|
||||
<p>
|
||||
<strong>Error HREF:</strong><br />
|
||||
<span>{{feedback-error-href|abbreviate:500}}</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<strong>Message:</strong><br />
|
||||
{{content|linebreaks-br|safe}}
|
||||
{{feedback-content|linebreaks-br}}
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1 +1 @@
|
||||
[PENPOT FEEDBACK]: {{subject}}
|
||||
[PENPOT FEEDBACK]: {{feedback-subject}}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{% if profile %}
|
||||
Feedback profile: {{profile.fullname}} <{{profile.email}}> / {{profile.id}}
|
||||
{% else %}
|
||||
Feedback from: {{email}}
|
||||
{% endif %}
|
||||
From: {{profile.fullname}} <{{profile.email}}> / {{profile.id}}
|
||||
Subject: {{feedback-subject}}
|
||||
Type: {{feedback-type}}
|
||||
{%- if feedback-error-href %}
|
||||
HREF: {{feedback-error-href}}
|
||||
{% endif -%}
|
||||
|
||||
Subject: {{subject}}
|
||||
Message:
|
||||
|
||||
{{content}}
|
||||
{{feedback-content}}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
export PENPOT_MANAGEMENT_API_SHARED_KEY=super-secret-management-api-key
|
||||
export PENPOT_NITRATE_API_SHARED_KEY=super-secret-nitrate-api-key
|
||||
export PENPOT_SECRET_KEY=super-secret-devenv-key
|
||||
export PENPOT_HOST=devenv
|
||||
|
||||
@@ -20,6 +21,7 @@ export PENPOT_FLAGS="\
|
||||
enable-audit-log \
|
||||
enable-transit-readable-response \
|
||||
enable-demo-users \
|
||||
enable-user-feedback \
|
||||
disable-secure-session-cookies \
|
||||
enable-smtp \
|
||||
enable-prepl-server \
|
||||
@@ -46,6 +48,8 @@ export PENPOT_MEDIA_MAX_FILE_SIZE=104857600
|
||||
# Setup default multipart upload size to 300MiB
|
||||
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=314572800
|
||||
|
||||
export PENPOT_USER_FEEDBACK_DESTINATION="support@example.com"
|
||||
|
||||
export AWS_ACCESS_KEY_ID=penpot-devenv
|
||||
export AWS_SECRET_ACCESS_KEY=penpot-devenv
|
||||
export PENPOT_OBJECTS_STORAGE_BACKEND=s3
|
||||
|
||||
@@ -749,7 +749,7 @@
|
||||
l.version
|
||||
FROM libs AS l
|
||||
INNER JOIN project AS p ON (p.id = l.project_id)
|
||||
WHERE l.deleted_at IS NULL OR l.deleted_at > now();")
|
||||
WHERE l.deleted_at IS NULL;")
|
||||
|
||||
(defn get-file-libraries
|
||||
[conn file-id]
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(ns app.email
|
||||
"Main api for send emails."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
@@ -93,36 +94,44 @@
|
||||
headers)))
|
||||
|
||||
(defn- assign-body
|
||||
[^MimeMessage mmsg {:keys [body charset] :or {charset "utf-8"}}]
|
||||
(let [mpart (MimeMultipart. "mixed")]
|
||||
[^MimeMessage mmsg {:keys [body charset attachments] :or {charset "utf-8"}}]
|
||||
(let [mixed-mpart (MimeMultipart. "mixed")]
|
||||
(cond
|
||||
(string? body)
|
||||
(let [bpart (MimeBodyPart.)]
|
||||
(.setContent bpart ^String body (str "text/plain; charset=" charset))
|
||||
(.addBodyPart mpart bpart))
|
||||
|
||||
(vector? body)
|
||||
(let [mmp (MimeMultipart. "alternative")
|
||||
mbp (MimeBodyPart.)]
|
||||
(.addBodyPart mpart mbp)
|
||||
(.setContent mbp mmp)
|
||||
(doseq [item body]
|
||||
(let [mbp (MimeBodyPart.)]
|
||||
(.setContent mbp
|
||||
^String (:content item)
|
||||
^String (str (:type item "text/plain") "; charset=" charset))
|
||||
(.addBodyPart mmp mbp))))
|
||||
(let [text-part (MimeBodyPart.)]
|
||||
(.setText text-part ^String body ^String charset)
|
||||
(.addBodyPart mixed-mpart text-part))
|
||||
|
||||
(map? body)
|
||||
(let [bpart (MimeBodyPart.)]
|
||||
(.setContent bpart
|
||||
^String (:content body)
|
||||
^String (str (:type body "text/plain") "; charset=" charset))
|
||||
(.addBodyPart mpart bpart))
|
||||
(let [content-part (MimeBodyPart.)
|
||||
alternative-mpart (MimeMultipart. "alternative")]
|
||||
|
||||
(when-let [content (get body "text/html")]
|
||||
(let [html-part (MimeBodyPart.)]
|
||||
(.setContent html-part ^String content
|
||||
(str "text/html; charset=" charset))
|
||||
(.addBodyPart alternative-mpart html-part)))
|
||||
|
||||
(when-let [content (get body "text/plain")]
|
||||
(let [text-part (MimeBodyPart.)]
|
||||
(.setText text-part ^String content ^String charset)
|
||||
(.addBodyPart alternative-mpart text-part)))
|
||||
|
||||
(.setContent content-part alternative-mpart)
|
||||
(.addBodyPart mixed-mpart content-part))
|
||||
|
||||
:else
|
||||
(throw (ex-info "Unsupported type" {:body body})))
|
||||
(.setContent mmsg mpart)
|
||||
(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))
|
||||
(.addBodyPart mixed-mpart attachment-part)))
|
||||
|
||||
(.setContent mmsg mixed-mpart)
|
||||
mmsg))
|
||||
|
||||
(defn- opts->props
|
||||
@@ -210,24 +219,26 @@
|
||||
(ex/raise :type :internal
|
||||
:code :missing-email-templates))
|
||||
{:subject subj
|
||||
:body (into
|
||||
[{:type "text/plain"
|
||||
:content text}]
|
||||
(when html
|
||||
[{:type "text/html"
|
||||
:content html}]))}))
|
||||
:body (d/without-nils
|
||||
{"text/plain" text
|
||||
"text/html" html})}))
|
||||
|
||||
(def ^:private schema:context
|
||||
[:map
|
||||
(def ^:private schema:params
|
||||
[:map {:title "Email Params"}
|
||||
[:to [:or ::sm/email [::sm/vec ::sm/email]]]
|
||||
[:reply-to {:optional true} ::sm/email]
|
||||
[:from {:optional true} ::sm/email]
|
||||
[:lang {:optional true} ::sm/text]
|
||||
[:subject {:optional true} ::sm/text]
|
||||
[:priority {:optional true} [:enum :high :low]]
|
||||
[:extra-data {:optional true} ::sm/text]])
|
||||
[:extra-data {:optional true} ::sm/text]
|
||||
[:body {:optional true}
|
||||
[:or :string [:map-of :string :string]]]
|
||||
[:attachments {:optional true}
|
||||
[:map-of :string :string]]])
|
||||
|
||||
(def ^:private check-context
|
||||
(sm/check-fn schema:context))
|
||||
(def ^:private check-params
|
||||
(sm/check-fn schema:params))
|
||||
|
||||
(defn template-factory
|
||||
[& {:keys [id schema]}]
|
||||
@@ -235,9 +246,9 @@
|
||||
(let [check-fn (if schema
|
||||
(sm/check-fn schema)
|
||||
(constantly nil))]
|
||||
(fn [context]
|
||||
(let [context (-> context check-context check-fn)
|
||||
email (build-email-template id context)]
|
||||
(fn [params]
|
||||
(let [params (-> params check-params check-fn)
|
||||
email (build-email-template id params)]
|
||||
(when-not email
|
||||
(ex/raise :type :internal
|
||||
:code :email-template-does-not-exists
|
||||
@@ -245,35 +256,40 @@
|
||||
:template-id id))
|
||||
|
||||
(cond-> (assoc email :id (name id))
|
||||
(:extra-data context)
|
||||
(assoc :extra-data (:extra-data context))
|
||||
(:extra-data params)
|
||||
(assoc :extra-data (:extra-data params))
|
||||
|
||||
(:from context)
|
||||
(assoc :from (:from context))
|
||||
(seq (:attachments params))
|
||||
(assoc :attachments (:attachments params))
|
||||
|
||||
(:reply-to context)
|
||||
(assoc :reply-to (:reply-to context))
|
||||
(:from params)
|
||||
(assoc :from (:from params))
|
||||
|
||||
(:to context)
|
||||
(assoc :to (:to context)))))))
|
||||
(:reply-to params)
|
||||
(assoc :reply-to (:reply-to params))
|
||||
|
||||
(:to params)
|
||||
(assoc :to (:to params)))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; PUBLIC HIGH-LEVEL API
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn render
|
||||
[email-factory context]
|
||||
(email-factory context))
|
||||
[email-factory params]
|
||||
(email-factory params))
|
||||
|
||||
(defn send!
|
||||
"Schedule an already defined email to be sent using asynchronously
|
||||
using worker task."
|
||||
[{:keys [::conn ::factory] :as context}]
|
||||
[{:keys [::conn ::factory] :as params}]
|
||||
(assert (db/connectable? conn) "expected a valid database connection or pool")
|
||||
|
||||
(let [email (if factory
|
||||
(factory context)
|
||||
(dissoc context ::conn))]
|
||||
(factory params)
|
||||
(-> params
|
||||
(dissoc params)
|
||||
(check-params)))]
|
||||
(wrk/submit! {::wrk/task :sendmail
|
||||
::wrk/delay 0
|
||||
::wrk/max-retries 4
|
||||
@@ -343,8 +359,10 @@
|
||||
|
||||
(def ^:private schema:feedback
|
||||
[:map
|
||||
[:subject ::sm/text]
|
||||
[:content ::sm/text]])
|
||||
[:feedback-subject ::sm/text]
|
||||
[:feedback-type ::sm/text]
|
||||
[:feedback-content ::sm/text]
|
||||
[:profile :map]])
|
||||
|
||||
(def user-feedback
|
||||
"A profile feedback email."
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
[app.http.errors :as errors]
|
||||
[app.http.management :as mgmt]
|
||||
[app.http.middleware :as mw]
|
||||
[app.http.nitrate :as nitrate]
|
||||
[app.http.security :as sec]
|
||||
[app.http.session :as session]
|
||||
[app.http.websocket :as-alias ws]
|
||||
@@ -156,6 +157,7 @@
|
||||
[::mtx/routes schema:routes]
|
||||
[::awsns/routes schema:routes]
|
||||
[::mgmt/routes schema:routes]
|
||||
[::nitrate/routes schema:routes]
|
||||
::session/manager
|
||||
::setup/props
|
||||
::db/pool])
|
||||
@@ -187,6 +189,9 @@
|
||||
["/management"
|
||||
(::mgmt/routes cfg)]
|
||||
|
||||
["/nitrate"
|
||||
(::nitrate/routes cfg)]
|
||||
|
||||
(::ws/routes cfg)
|
||||
|
||||
["/api" {:middleware [[mw/cors]
|
||||
|
||||
410
backend/src/app/http/nitrate.clj
Normal file
410
backend/src/app/http/nitrate.clj
Normal file
@@ -0,0 +1,410 @@
|
||||
;; 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.http.nitrate
|
||||
"Internal Nitrate HTTP API.
|
||||
Provides authenticated access to organization management and token validation endpoints.
|
||||
|
||||
All requests must include a valid shared key token in the Authorization header."
|
||||
(:require
|
||||
[app.common.logging :as l]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.http.access-token :refer [get-token]]
|
||||
[app.main :as-alias main]
|
||||
[app.msgbus :as mbus]
|
||||
[app.setup :as-alias setup]
|
||||
[app.tokens :as tokens]
|
||||
[app.worker :as-alias wrk]
|
||||
[clojure.walk :as walk]
|
||||
[integrant.core :as ig]
|
||||
[yetti.response :as-alias yres]))
|
||||
|
||||
;; ---- ROUTES
|
||||
|
||||
(declare ^:private authenticate)
|
||||
(declare ^:private get-organization)
|
||||
(declare ^:private create-organization)
|
||||
(declare ^:private update-organization)
|
||||
(declare ^:private list-organizations)
|
||||
(declare ^:private list-teams)
|
||||
(declare ^:private set-team-org)
|
||||
|
||||
|
||||
(defmethod ig/assert-key ::routes
|
||||
[_ params]
|
||||
(assert (db/pool? (::db/pool params)) "expect valid database pool"))
|
||||
|
||||
(def ^:private auth
|
||||
{:name ::auth
|
||||
:compile
|
||||
(fn [_ _]
|
||||
(fn [handler shared-key]
|
||||
(if shared-key
|
||||
(fn [request]
|
||||
(let [token (get-token request)]
|
||||
(if (= token shared-key)
|
||||
(handler request)
|
||||
{::yres/status 401})))
|
||||
(fn [_ _]
|
||||
{::yres/status 401}))))})
|
||||
|
||||
(def ^:private default-system
|
||||
{:name ::default-system
|
||||
:compile
|
||||
(fn [_ _]
|
||||
(fn [handler cfg]
|
||||
(fn [request]
|
||||
(handler cfg request))))})
|
||||
|
||||
(def ^:private transaction
|
||||
{:name ::transaction
|
||||
:compile
|
||||
(fn [data _]
|
||||
(when (:transaction data)
|
||||
(fn [handler]
|
||||
(fn [cfg request]
|
||||
(db/tx-run! cfg handler request)))))})
|
||||
|
||||
(defmethod ig/init-key ::routes
|
||||
[_ cfg]
|
||||
["" {:middleware [[auth (cf/get :nitrate-api-shared-key)]
|
||||
[default-system cfg]
|
||||
[transaction]]}
|
||||
["/authenticate"
|
||||
{:handler authenticate
|
||||
:allowed-methods #{:post}}]
|
||||
|
||||
["/list-organizations"
|
||||
{:handler list-organizations
|
||||
:allowed-methods #{:post}
|
||||
:transaction true}]
|
||||
|
||||
["/list-teams"
|
||||
{:handler list-teams
|
||||
:allowed-methods #{:post}
|
||||
:transaction true}]
|
||||
|
||||
["/set-team-org"
|
||||
{:handler set-team-org
|
||||
:allowed-methods #{:post}
|
||||
:transaction true}]
|
||||
|
||||
["/get-organization"
|
||||
{:handler get-organization
|
||||
:allowed-methods #{:post}
|
||||
:transaction true}]
|
||||
|
||||
["/update-organization"
|
||||
{:handler update-organization
|
||||
:allowed-methods #{:post}
|
||||
:transaction true}]
|
||||
|
||||
["/create-organization"
|
||||
{:handler create-organization
|
||||
:allowed-methods #{:post}
|
||||
:transaction true}]])
|
||||
|
||||
;; ---- HELPERS
|
||||
|
||||
(defn- coercer
|
||||
"Returns a parameter coercion function that:
|
||||
- Decodes JSON params according to the given `schema`
|
||||
- Validates them using `sm/check-fn`
|
||||
- Throws validation errors if data is invalid
|
||||
|
||||
@param schema Schema definition for input validation
|
||||
@return function that validates request params"
|
||||
[schema & {:as opts}]
|
||||
(let [decode-fn (sm/decoder schema sm/json-transformer)
|
||||
check-fn (sm/check-fn schema opts)]
|
||||
(fn [data]
|
||||
(-> data decode-fn check-fn))))
|
||||
|
||||
|
||||
(defn with-current-user
|
||||
"Wraps a handler to inject the current user ID if the provided token is valid.
|
||||
Returns 401 if no valid user is found or token verification fails.
|
||||
|
||||
@param handler - function of [cfg request current-user-id]
|
||||
@return handler of [cfg request]"
|
||||
[handler]
|
||||
(fn [cfg request]
|
||||
(let [token (-> request :params :user-token)
|
||||
auth (when token (tokens/verify cfg {:token token :iss "authentication"}))
|
||||
uid (:uid auth)]
|
||||
(if uid
|
||||
(handler cfg request uid)
|
||||
{::yres/status 401}))))
|
||||
|
||||
(def ^:private sql:get-role
|
||||
"SELECT role
|
||||
FROM organization_profile_rel
|
||||
WHERE organization_id = ?
|
||||
and profile_id = ?;")
|
||||
|
||||
(defn- is-org-owner?
|
||||
[cfg organization-id current-user-id]
|
||||
(let [result (db/exec-one! cfg [sql:get-role organization-id current-user-id])]
|
||||
(= "owner" (:role result))))
|
||||
|
||||
(def ^:private sql:is-team-owner
|
||||
"SELECT COUNT(*) AS n
|
||||
FROM team_profile_rel
|
||||
WHERE team_id = ?
|
||||
AND profile_id = ?
|
||||
AND is_owner = true;")
|
||||
|
||||
(defn- is-team-owner?
|
||||
[cfg team-id current-user-id]
|
||||
(-> (db/exec-one! cfg [sql:is-team-owner team-id current-user-id])
|
||||
:n
|
||||
pos?))
|
||||
|
||||
;; ---- API: AUTHENTICATE
|
||||
|
||||
(defn- authenticate
|
||||
"Authenticate a service token.
|
||||
|
||||
@api POST /authenticate
|
||||
@auth SharedKey
|
||||
@params
|
||||
token (string): The access token to validate.
|
||||
@returns
|
||||
200 OK: Returns decoded token claims if valid.
|
||||
401 Unauthorized: If the shared key or token is invalid."
|
||||
[cfg request]
|
||||
(let [token (-> request :params :token)
|
||||
result (tokens/verify cfg {:token token :iss "authentication"})]
|
||||
{::yres/status 200
|
||||
::yres/body result}))
|
||||
|
||||
;; ---- API: GET-ORGANIZATION
|
||||
|
||||
(def ^:private schema:get-organization
|
||||
[:map [:id ::sm/uuid]])
|
||||
|
||||
(def ^:private coerce-get-organization-params
|
||||
(coercer schema:get-organization
|
||||
:type :validation
|
||||
:hint "invalid data provided for `get-organization` rpc call"))
|
||||
|
||||
(def get-organization
|
||||
"Retrieve an organization by ID.
|
||||
|
||||
@api POST /get-organization
|
||||
@auth SharedKey
|
||||
@params
|
||||
id (uuid): Organization identifier.
|
||||
@returns
|
||||
200 OK: Returns the organization record.
|
||||
400 Bad Request: Invalid input data.
|
||||
401 Unauthorized: If the shared key or user token is invalid.
|
||||
404 Not Found: Organization not found."
|
||||
(with-current-user
|
||||
(fn [cfg request _]
|
||||
(let [organization-id (-> request :params coerce-get-organization-params :id)
|
||||
result (-> (db/get-by-id cfg :organization organization-id)
|
||||
(walk/stringify-keys))]
|
||||
{::yres/status 200
|
||||
::yres/body result}))))
|
||||
|
||||
;; ---- API: LIST-ORGANIZATIONS
|
||||
|
||||
(def ^:private sql:list-organizations
|
||||
"SELECT o.*
|
||||
FROM organization AS o
|
||||
JOIN organization_profile_rel AS opr ON o.id = opr.organization_id
|
||||
WHERE opr.profile_id = ?
|
||||
AND opr.role = 'owner';")
|
||||
|
||||
|
||||
(def list-organizations
|
||||
"List organizations for which current user is owner.
|
||||
|
||||
@api POST /list-organizations
|
||||
@auth SharedKey
|
||||
@returns
|
||||
200 OK: Returns the list of organizations for the user.
|
||||
401 Unauthorized: If the shared key or user token is invalid."
|
||||
(with-current-user
|
||||
(fn [cfg _request current-user-id]
|
||||
(let [result (->> (db/exec! cfg [sql:list-organizations current-user-id])
|
||||
(map #(update % :id str))
|
||||
vec
|
||||
walk/stringify-keys)]
|
||||
{::yres/status 200
|
||||
::yres/body result}))))
|
||||
|
||||
|
||||
|
||||
;; ---- API: CREATE-ORGANIZATION
|
||||
|
||||
(def ^:private schema:create-organization
|
||||
[:map
|
||||
[:name [::sm/word-string {:max 250}]]])
|
||||
|
||||
(def ^:private coerce-create-organization-params
|
||||
(coercer schema:create-organization
|
||||
:type :validation
|
||||
:hint "invalid data provided for `create-organization` rpc call"))
|
||||
|
||||
(def create-organization
|
||||
"Create a new organization.
|
||||
|
||||
@api POST /create-organization
|
||||
@auth SharedKey
|
||||
@params
|
||||
name (string, max 250): Name of the organization.
|
||||
@returns
|
||||
201 Created: Returns the newly created organization.
|
||||
400 Bad Request: Invalid data.
|
||||
401 Unauthorized: If the shared key or user token is invalid."
|
||||
(with-current-user
|
||||
(fn [cfg request current-user-id]
|
||||
(let [{:keys [name]}
|
||||
(-> request :params coerce-create-organization-params)]
|
||||
|
||||
(l/dbg :hint "create organization"
|
||||
:name name)
|
||||
|
||||
(let [organization (db/insert! cfg :organization {:id (uuid/next) :name name})
|
||||
_ (db/insert! cfg :organization_profile_rel {:organization_id (:id organization) :profile_id current-user-id :role :owner})]
|
||||
{::yres/status 201
|
||||
::yres/body organization})))))
|
||||
|
||||
;; ---- API: UPDATE-ORGANIZATION
|
||||
|
||||
(def ^:private schema:update-organization
|
||||
[:map
|
||||
[:id ::sm/uuid]
|
||||
[:name [::sm/word-string {:max 250}]]])
|
||||
|
||||
(def ^:private coerce-update-organization-params
|
||||
(coercer schema:update-organization
|
||||
:type :validation
|
||||
:hint "invalid data provided for `update-organization` rpc call"))
|
||||
|
||||
(def update-organization
|
||||
"Update an existing organization’s name.
|
||||
|
||||
@api POST /update-organization
|
||||
@auth SharedKey
|
||||
@params
|
||||
id (uuid): Organization identifier.
|
||||
name (string, max 250): New organization name.
|
||||
@returns
|
||||
204 Updated: Operation successful.
|
||||
400 Bad Request: Invalid input data.
|
||||
401 Unauthorized: If the shared key or user token is invalid.
|
||||
403 Forbidden: The user doesn't have permissions to execute this operation."
|
||||
(with-current-user
|
||||
(fn [cfg request current-user-id]
|
||||
(let [{:keys [id name]}
|
||||
(-> request :params coerce-update-organization-params)
|
||||
org-owner? (is-org-owner? cfg id current-user-id)]
|
||||
|
||||
(if org-owner?
|
||||
(do
|
||||
(db/update! cfg :organization
|
||||
{:name name}
|
||||
{:id id}
|
||||
{::db/return-keys false})
|
||||
|
||||
{::yres/status 204
|
||||
::yres/body nil})
|
||||
{::yres/status 403
|
||||
::yres/body nil})))))
|
||||
|
||||
;; ---- API: LIST-TEAMS
|
||||
|
||||
(def ^:private sql:list-teams
|
||||
"SELECT t.*
|
||||
FROM team AS t
|
||||
JOIN team_profile_rel AS tpr ON t.id = tpr.team_id
|
||||
WHERE tpr.profile_id = ?
|
||||
AND tpr.is_owner = 't'
|
||||
AND t.is_default = 'f';")
|
||||
|
||||
|
||||
(def list-teams
|
||||
"List teams for which current user is owner.
|
||||
|
||||
@api POST /list-teams
|
||||
@auth SharedKey
|
||||
@returns
|
||||
200 OK: Returns the list of teams for the user.
|
||||
401 Unauthorized: If the shared key or user token is invalid."
|
||||
(with-current-user
|
||||
(fn [cfg _request current-user-id]
|
||||
(let [result (->> (db/exec! cfg [sql:list-teams current-user-id])
|
||||
(map #(dissoc % :features :subscription :is-default))
|
||||
(map #(update % :organization-id str))
|
||||
(map #(update % :id str))
|
||||
vec
|
||||
walk/stringify-keys)]
|
||||
{::yres/status 200
|
||||
::yres/body result}))))
|
||||
|
||||
|
||||
;; ---- API: SET-TEAM-ORG
|
||||
|
||||
(def ^:private schema:set-team-org
|
||||
[:map
|
||||
[:team-id ::sm/uuid]
|
||||
[:organization-id {:optional true} [:maybe ::sm/uuid]]])
|
||||
|
||||
(def ^:private coerce-set-team-org-params
|
||||
(coercer schema:set-team-org
|
||||
:type :validation
|
||||
:hint "invalid data provided for `set-team-org` rpc call"))
|
||||
|
||||
(def ^:private sql:set-team-org
|
||||
"UPDATE team
|
||||
SET organization_id = ?
|
||||
WHERE team.id = ?;")
|
||||
|
||||
|
||||
(def set-team-org
|
||||
"Set the organization of a team.
|
||||
|
||||
@api POST /set-team-org
|
||||
@auth SharedKey
|
||||
@params
|
||||
team-id (uuid): Team identifier.
|
||||
organization-id (uuid | null): Organization identifier, or null to remove association.
|
||||
@returns
|
||||
204 Updated: Operation successful.
|
||||
401 Unauthorized: If the shared key or user token is invalid.
|
||||
403 Forbidden: The user doesn't have permissions to execute this operation."
|
||||
(with-current-user
|
||||
(fn [cfg request current-user-id]
|
||||
(let [{:keys [organization-id team-id]}
|
||||
(-> request :params coerce-set-team-org-params)
|
||||
org-owner? (if (nil? organization-id)
|
||||
true
|
||||
(is-org-owner? cfg organization-id current-user-id))
|
||||
|
||||
team-owner? (is-team-owner? cfg team-id current-user-id)
|
||||
organization (when (and team-owner? org-owner?) (db/get-by-id cfg :organization organization-id))
|
||||
msgbus (::mbus/msgbus cfg)]
|
||||
|
||||
|
||||
(if (or
|
||||
(not team-owner?)
|
||||
(not org-owner?))
|
||||
{::yres/status 403}
|
||||
(do
|
||||
(db/exec! cfg [sql:set-team-org organization-id team-id])
|
||||
(mbus/pub! msgbus
|
||||
:topic uuid/zero #_team-id
|
||||
:message {:type :team-org-change
|
||||
:team-id team-id
|
||||
:organization-id organization-id
|
||||
:organization-name (:name organization)})
|
||||
{::yres/status 204}))))))
|
||||
@@ -21,6 +21,7 @@
|
||||
[app.http.client :as-alias http.client]
|
||||
[app.http.debug :as-alias http.debug]
|
||||
[app.http.management :as mgmt]
|
||||
[app.http.nitrate :as nitrate]
|
||||
[app.http.session :as-alias session]
|
||||
[app.http.session.tasks :as-alias session.tasks]
|
||||
[app.http.websocket :as http.ws]
|
||||
@@ -276,6 +277,11 @@
|
||||
{::db/pool (ig/ref ::db/pool)
|
||||
::setup/props (ig/ref ::setup/props)}
|
||||
|
||||
::nitrate/routes
|
||||
{::db/pool (ig/ref ::db/pool)
|
||||
::setup/props (ig/ref ::setup/props)
|
||||
::mbus/msgbus (ig/ref ::mbus/msgbus)}
|
||||
|
||||
:app.http/router
|
||||
{::session/manager (ig/ref ::session/manager)
|
||||
::db/pool (ig/ref ::db/pool)
|
||||
@@ -285,6 +291,7 @@
|
||||
::mtx/routes (ig/ref ::mtx/routes)
|
||||
::oidc/routes (ig/ref ::oidc/routes)
|
||||
::mgmt/routes (ig/ref ::mgmt/routes)
|
||||
::nitrate/routes (ig/ref ::nitrate/routes)
|
||||
::http.debug/routes (ig/ref ::http.debug/routes)
|
||||
::http.assets/routes (ig/ref ::http.assets/routes)
|
||||
::http.ws/routes (ig/ref ::http.ws/routes)
|
||||
|
||||
@@ -450,7 +450,10 @@
|
||||
:fn (mg/resource "app/migrations/sql/0141-add-idx-to-file-library-rel.sql")}
|
||||
|
||||
{:name "0141-add-file-data-table.sql"
|
||||
:fn (mg/resource "app/migrations/sql/0141-add-file-data-table.sql")}])
|
||||
:fn (mg/resource "app/migrations/sql/0141-add-file-data-table.sql")}
|
||||
|
||||
{:name "0142-add-organization-tables.sql"
|
||||
:fn (mg/resource "app/migrations/sql/0142-add-organization-tables.sql")}])
|
||||
|
||||
(defn apply-migrations!
|
||||
[pool name migrations]
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
CREATE TABLE organization (
|
||||
id uuid NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
name text NOT NULL,
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
ALTER TABLE team
|
||||
ADD COLUMN organization_id uuid NULL REFERENCES organization(id) ON DELETE SET NULL;
|
||||
|
||||
|
||||
|
||||
CREATE TABLE organization_profile_rel (
|
||||
organization_id uuid NOT NULL REFERENCES organization(id) ON DELETE CASCADE,
|
||||
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
role text NOT NULL DEFAULT 'user',
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
|
||||
PRIMARY KEY (organization_id, profile_id)
|
||||
);
|
||||
|
||||
CREATE INDEX team__organization_id__idx
|
||||
ON team(organization_id);
|
||||
|
||||
CREATE INDEX organization_profile_rel__organization_id__idx
|
||||
ON organization_profile_rel(organization_id);
|
||||
|
||||
CREATE INDEX organization_profile_rel__profile_id__idx
|
||||
ON organization_profile_rel(profile_id);
|
||||
|
||||
|
||||
@@ -234,36 +234,39 @@
|
||||
(files/check-comment-permissions! conn profile-id file-id share-id)
|
||||
(get-comment-threads conn profile-id file-id))))
|
||||
|
||||
(def ^:private sql:comment-threads
|
||||
"SELECT DISTINCT ON (ct.id)
|
||||
ct.*,
|
||||
pf.fullname AS owner_fullname,
|
||||
pf.email AS owner_email,
|
||||
pf.photo_id AS owner_photo_id,
|
||||
p.team_id AS team_id,
|
||||
f.name AS file_name,
|
||||
f.project_id AS project_id,
|
||||
first_value(c.content) OVER w AS content,
|
||||
(SELECT count(1)
|
||||
FROM comment AS c
|
||||
WHERE c.thread_id = ct.id) AS count_comments,
|
||||
(SELECT count(1)
|
||||
FROM comment AS c
|
||||
WHERE c.thread_id = ct.id
|
||||
AND c.created_at >= coalesce(cts.modified_at, ct.created_at)) AS count_unread_comments
|
||||
FROM comment_thread AS ct
|
||||
INNER JOIN comment AS c ON (c.thread_id = ct.id)
|
||||
INNER JOIN file AS f ON (f.id = ct.file_id)
|
||||
INNER JOIN project AS p ON (p.id = f.project_id)
|
||||
LEFT JOIN comment_thread_status AS cts ON (cts.thread_id = ct.id AND cts.profile_id = ?)
|
||||
LEFT JOIN profile AS pf ON (ct.owner_id = pf.id)
|
||||
WHERE f.deleted_at IS NULL
|
||||
AND p.deleted_at IS NULL
|
||||
WINDOW w AS (PARTITION BY c.thread_id ORDER BY c.created_at ASC)")
|
||||
(defn- get-comment-threads-sql
|
||||
[where]
|
||||
(str/ffmt
|
||||
"SELECT DISTINCT ON (ct.id)
|
||||
ct.*,
|
||||
pf.fullname AS owner_fullname,
|
||||
pf.email AS owner_email,
|
||||
pf.photo_id AS owner_photo_id,
|
||||
p.team_id AS team_id,
|
||||
f.name AS file_name,
|
||||
f.project_id AS project_id,
|
||||
first_value(c.content) OVER w AS content,
|
||||
(SELECT count(1)
|
||||
FROM comment AS c
|
||||
WHERE c.thread_id = ct.id) AS count_comments,
|
||||
(SELECT count(1)
|
||||
FROM comment AS c
|
||||
WHERE c.thread_id = ct.id
|
||||
AND c.created_at >= coalesce(cts.modified_at, ct.created_at)) AS count_unread_comments
|
||||
FROM comment_thread AS ct
|
||||
INNER JOIN comment AS c ON (c.thread_id = ct.id)
|
||||
INNER JOIN file AS f ON (f.id = ct.file_id)
|
||||
INNER JOIN project AS p ON (p.id = f.project_id)
|
||||
LEFT JOIN comment_thread_status AS cts ON (cts.thread_id = ct.id AND cts.profile_id = ?)
|
||||
LEFT JOIN profile AS pf ON (ct.owner_id = pf.id)
|
||||
WHERE f.deleted_at IS NULL
|
||||
AND p.deleted_at IS NULL
|
||||
%1
|
||||
WINDOW w AS (PARTITION BY c.thread_id ORDER BY c.created_at ASC)"
|
||||
where))
|
||||
|
||||
(def ^:private sql:comment-threads-by-file-id
|
||||
(str "WITH threads AS (" sql:comment-threads ")"
|
||||
"SELECT * FROM threads WHERE file_id = ?"))
|
||||
(get-comment-threads-sql "AND ct.file_id = ?"))
|
||||
|
||||
(defn- get-comment-threads
|
||||
[conn profile-id file-id]
|
||||
@@ -273,34 +276,29 @@
|
||||
;; --- COMMAND: Get Unread Comment Threads
|
||||
|
||||
(def ^:private sql:unread-all-comment-threads-by-team
|
||||
(str "WITH threads AS (" sql:comment-threads ")"
|
||||
"SELECT * FROM threads WHERE count_unread_comments > 0 AND team_id = ?"))
|
||||
(str "WITH threads AS ("
|
||||
(get-comment-threads-sql "AND p.team_id = ?")
|
||||
")"
|
||||
"SELECT t.* FROM threads AS t
|
||||
WHERE t.count_unread_comments > 0"))
|
||||
|
||||
;; The partial configuration will retrieve only comments created by the user and
|
||||
;; threads that have a mention to the user.
|
||||
(def ^:private sql:unread-partial-comment-threads-by-team
|
||||
(str "WITH threads AS (" sql:comment-threads ")"
|
||||
"SELECT * FROM threads
|
||||
WHERE count_unread_comments > 0
|
||||
AND team_id = ?
|
||||
AND (owner_id = ? OR ? = ANY(mentions))"))
|
||||
(str "WITH threads AS ("
|
||||
(get-comment-threads-sql "AND p.team_id = ? AND (ct.owner_id = ? OR ? = ANY(ct.mentions))")
|
||||
")"
|
||||
"SELECT t.* FROM threads AS t
|
||||
WHERE t.count_unread_comments > 0"))
|
||||
|
||||
(defn- get-unread-comment-threads
|
||||
[cfg profile-id team-id]
|
||||
(let [profile (-> (db/get cfg :profile {:id profile-id})
|
||||
(let [profile (-> (db/get cfg :profile {:id profile-id} ::db/remove-deleted false)
|
||||
(profile/decode-row))
|
||||
notify (or (-> profile :props :notifications :dashboard-comments) :all)]
|
||||
|
||||
(case notify
|
||||
:all
|
||||
(->> (db/exec! cfg [sql:unread-all-comment-threads-by-team profile-id team-id])
|
||||
(into [] xf-decode-row))
|
||||
|
||||
:partial
|
||||
(->> (db/exec! cfg [sql:unread-partial-comment-threads-by-team profile-id team-id profile-id profile-id])
|
||||
(into [] xf-decode-row))
|
||||
|
||||
[])))
|
||||
notify (or (-> profile :props :notifications :dashboard-comments) :all)
|
||||
result (case notify
|
||||
:all (db/exec! cfg [sql:unread-all-comment-threads-by-team profile-id team-id])
|
||||
:partial (db/exec! cfg [sql:unread-partial-comment-threads-by-team profile-id team-id profile-id profile-id])
|
||||
[])]
|
||||
(into [] xf-decode-row result)))
|
||||
|
||||
(def ^:private
|
||||
schema:get-unread-comment-threads
|
||||
@@ -323,16 +321,17 @@
|
||||
[:id ::sm/uuid]
|
||||
[:share-id {:optional true} [:maybe ::sm/uuid]]])
|
||||
|
||||
(def ^:private sql:get-comment-thread
|
||||
(get-comment-threads-sql "AND ct.file_id = ? AND ct.id = ?"))
|
||||
|
||||
(sv/defmethod ::get-comment-thread
|
||||
{::doc/added "1.15"
|
||||
::sm/params schema:get-comment-thread}
|
||||
[cfg {:keys [::rpc/profile-id file-id id share-id] :as params}]
|
||||
(db/run! cfg (fn [{:keys [::db/conn]}]
|
||||
(files/check-comment-permissions! conn profile-id file-id share-id)
|
||||
(let [sql (str "WITH threads AS (" sql:comment-threads ")"
|
||||
"SELECT * FROM threads WHERE id = ? AND file_id = ?")]
|
||||
(-> (db/exec-one! conn [sql profile-id id file-id])
|
||||
(decode-row))))))
|
||||
(some-> (db/exec-one! conn [sql:get-comment-thread profile-id file-id id])
|
||||
(decode-row)))))
|
||||
|
||||
;; --- COMMAND: Retrieve Comments
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
params {:email email
|
||||
:fullname fullname
|
||||
:is-active true
|
||||
:is-demo true
|
||||
:deleted-at (ct/in-future (cf/get-deletion-delay))
|
||||
:password (derive-password password)
|
||||
:props {}}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
(ns app.rpc.commands.feedback
|
||||
"A general purpose feedback module."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.schema :as sm]
|
||||
[app.config :as cf]
|
||||
@@ -21,8 +22,11 @@
|
||||
|
||||
(def ^:private schema:send-user-feedback
|
||||
[:map {:title "send-user-feedback"}
|
||||
[:subject [:string {:max 400}]]
|
||||
[:content [:string {:max 2500}]]])
|
||||
[:subject [:string {:max 500}]]
|
||||
[:content [:string {:max 2500}]]
|
||||
[:type {:optional true} :string]
|
||||
[:error-href {:optional true} [:string {:max 2500}]]
|
||||
[:error-report {:optional true} :string]])
|
||||
|
||||
(sv/defmethod ::send-user-feedback
|
||||
{::doc/added "1.18"
|
||||
@@ -39,16 +43,26 @@
|
||||
|
||||
(defn- send-user-feedback!
|
||||
[pool profile params]
|
||||
(let [dest (or (cf/get :user-feedback-destination)
|
||||
;; LEGACY
|
||||
(cf/get :feedback-destination))]
|
||||
(let [destination
|
||||
(or (cf/get :user-feedback-destination)
|
||||
;; LEGACY
|
||||
(cf/get :feedback-destination))
|
||||
|
||||
attachments
|
||||
(d/without-nils
|
||||
{"error-report.txt" (:error-report params)})]
|
||||
|
||||
(eml/send! {::eml/conn pool
|
||||
::eml/factory eml/user-feedback
|
||||
:from dest
|
||||
:to dest
|
||||
:profile profile
|
||||
:from (cf/get :smtp-default-from)
|
||||
:to destination
|
||||
:reply-to (:email profile)
|
||||
:email (:email profile)
|
||||
:subject (:subject params)
|
||||
:content (:content params)})
|
||||
:attachments attachments
|
||||
|
||||
:feedback-subject (:subject params)
|
||||
:feedback-type (:type params "not-specified")
|
||||
:feedback-content (:content params)
|
||||
:feedback-error-href (:error-href params)
|
||||
:profile profile})
|
||||
nil))
|
||||
|
||||
@@ -107,7 +107,9 @@
|
||||
(defn get-profile
|
||||
"Get profile by id. Throws not-found exception if no profile found."
|
||||
[conn id & {:as opts}]
|
||||
(-> (db/get-by-id conn :profile id opts)
|
||||
;; NOTE: We need to set ::db/remove-deleted to false because demo profiles
|
||||
;; are created with a set deleted-at value
|
||||
(-> (db/get-by-id conn :profile id (assoc opts ::db/remove-deleted false))
|
||||
(decode-row)))
|
||||
|
||||
;; --- MUTATION: Update Profile (own)
|
||||
@@ -473,13 +475,17 @@
|
||||
p.fullname AS name,
|
||||
p.email AS email
|
||||
FROM team_profile_rel AS tpr1
|
||||
JOIN team as t
|
||||
ON tpr1.team_id = t.id
|
||||
JOIN team_profile_rel AS tpr2
|
||||
ON (tpr1.team_id = tpr2.team_id)
|
||||
JOIN profile AS p
|
||||
ON (tpr2.profile_id = p.id)
|
||||
WHERE tpr1.profile_id = ?
|
||||
AND tpr1.is_owner IS true
|
||||
AND tpr2.can_edit IS true")
|
||||
AND tpr2.can_edit IS true
|
||||
AND NOT t.is_default
|
||||
AND t.deleted_at IS NULL")
|
||||
|
||||
(sv/defmethod ::get-subscription-usage
|
||||
{::doc/added "2.9"}
|
||||
|
||||
@@ -37,14 +37,14 @@
|
||||
;; --- Helpers & Specs
|
||||
|
||||
(def ^:private sql:team-permissions
|
||||
"select tpr.is_owner,
|
||||
"SELECT tpr.is_owner,
|
||||
tpr.is_admin,
|
||||
tpr.can_edit
|
||||
from team_profile_rel as tpr
|
||||
join team as t on (t.id = tpr.team_id)
|
||||
where tpr.profile_id = ?
|
||||
and tpr.team_id = ?
|
||||
and t.deleted_at is null")
|
||||
FROM team_profile_rel AS tpr
|
||||
JOIN team AS t ON (t.id = tpr.team_id)
|
||||
WHERE tpr.profile_id = ?
|
||||
AND tpr.team_id = ?
|
||||
AND t.deleted_at IS NULL")
|
||||
|
||||
(defn get-permissions
|
||||
[conn profile-id team-id]
|
||||
@@ -121,9 +121,11 @@
|
||||
tp.is_owner,
|
||||
tp.is_admin,
|
||||
tp.can_edit,
|
||||
(t.id = ?) AS is_default
|
||||
(t.id = ?) AS is_default,
|
||||
o.name AS organization_name
|
||||
FROM team_profile_rel AS tp
|
||||
JOIN team AS t ON (t.id = tp.team_id)
|
||||
LEFT JOIN organization AS o ON o.id = t.organization_id
|
||||
WHERE t.deleted_at IS null
|
||||
AND tp.profile_id = ?
|
||||
ORDER BY tp.created_at ASC")
|
||||
@@ -134,6 +136,7 @@
|
||||
tp.is_admin,
|
||||
tp.can_edit,
|
||||
(t.id = ?) AS is_default,
|
||||
o.name AS organization_name,
|
||||
|
||||
jsonb_build_object(
|
||||
'~:type', COALESCE(p.props->'~:subscription'->>'~:type', 'professional'),
|
||||
@@ -149,6 +152,7 @@
|
||||
ON (tpr.team_id = t.id AND tpr.is_owner IS true)
|
||||
JOIN profile AS p
|
||||
ON (tpr.profile_id = p.id)
|
||||
LEFT JOIN organization AS o ON o.id = t.organization_id
|
||||
WHERE t.deleted_at IS null
|
||||
AND tp.profile_id = ?
|
||||
ORDER BY tp.created_at ASC")
|
||||
|
||||
@@ -102,8 +102,7 @@
|
||||
::wrk/label "quotes-notification"
|
||||
::wrk/params {:to (vec admins)
|
||||
:subject subject
|
||||
:body [{:type "text/plain"
|
||||
:content content}]}}))))
|
||||
:body content}}))))
|
||||
|
||||
(defn- generic-check!
|
||||
[{:keys [::db/conn ::incr ::quote-sql ::count-sql ::default ::target] :or {incr 1} :as params}]
|
||||
|
||||
@@ -22,4 +22,4 @@
|
||||
(t/is (contains? result :body))
|
||||
(t/is (contains? result :to))
|
||||
#_(t/is (contains? result :reply-to))
|
||||
(t/is (vector? (:body result)))))
|
||||
(t/is (map? (:body result)))))
|
||||
|
||||
@@ -1024,6 +1024,29 @@
|
||||
:clj
|
||||
(sort comp-fn items))))
|
||||
|
||||
(defn reorder
|
||||
"Reorder a vector by moving one of their items from some position to some space between positions.
|
||||
It clamps the position numbers to a valid range."
|
||||
[v from-pos to-space-between-pos]
|
||||
(let [max-space-pos (count v)
|
||||
max-prop-pos (dec max-space-pos)
|
||||
|
||||
from-pos (max 0 (min max-prop-pos from-pos))
|
||||
to-space-between-pos (max 0 (min max-space-pos to-space-between-pos))]
|
||||
|
||||
(if (= from-pos to-space-between-pos)
|
||||
v
|
||||
(let [elem (nth v from-pos)
|
||||
without-elem (-> []
|
||||
(into (subvec v 0 from-pos))
|
||||
(into (subvec v (inc from-pos))))
|
||||
insert-pos (if (< from-pos to-space-between-pos)
|
||||
(dec to-space-between-pos)
|
||||
to-space-between-pos)]
|
||||
(-> []
|
||||
(into (subvec without-elem 0 insert-pos))
|
||||
(into [elem])
|
||||
(into (subvec without-elem insert-pos)))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; String Functions
|
||||
|
||||
@@ -517,8 +517,7 @@
|
||||
(when verify?
|
||||
(check-changes items))
|
||||
|
||||
(binding [*touched-changes* (volatile! #{})
|
||||
cts/*wasm-sync* true]
|
||||
(binding [*touched-changes* (volatile! #{})]
|
||||
(let [result (reduce #(or (process-change %1 %2) %1) data items)
|
||||
result (reduce process-touched-change result @*touched-changes*)]
|
||||
;; Validate result shapes (only on the backend)
|
||||
|
||||
@@ -638,6 +638,7 @@
|
||||
(reduce add-undo-change-shape $ ids)))
|
||||
(apply-changes-local)))))
|
||||
|
||||
;; FIXME: PERFORMANCE
|
||||
(defn resize-parents
|
||||
[changes ids]
|
||||
(assert-page-id! changes)
|
||||
|
||||
@@ -1413,17 +1413,27 @@
|
||||
(defmethod migrate-data "0006-fix-old-texts-fills"
|
||||
[data _]
|
||||
(letfn [(fix-fills [node]
|
||||
(let [fills (if (and (not (seq (:fills node)))
|
||||
(or (some? (:fill-color node))
|
||||
(some? (:fill-opacity node))
|
||||
(some? (:fill-color-gradient node))))
|
||||
[(d/without-nils (select-keys node [:fill-color :fill-opacity :fill-color-gradient
|
||||
:fill-color-ref-id :fill-color-ref-file]))]
|
||||
(:fills node))]
|
||||
(-> node
|
||||
(assoc :fills fills)
|
||||
(dissoc :fill-color :fill-opacity :fill-color-gradient
|
||||
:fill-color-ref-id :fill-color-ref-file))))
|
||||
(let [;; In the old format refs were strings
|
||||
sanitize-uuid
|
||||
(fn [o]
|
||||
(if (uuid? o)
|
||||
o
|
||||
(uuid/parse* o)))
|
||||
|
||||
fills
|
||||
(if (and (not (seq (:fills node)))
|
||||
(or (some? (:fill-color node))
|
||||
(some? (:fill-opacity node))
|
||||
(some? (:fill-color-gradient node))))
|
||||
[(-> (select-keys node types.fills/fill-attrs)
|
||||
(update :fill-color-ref-file sanitize-uuid)
|
||||
(update :fill-color-ref-id sanitize-uuid)
|
||||
(d/without-nils))]
|
||||
(:fills node))]
|
||||
|
||||
(reduce dissoc
|
||||
(assoc node :fills fills)
|
||||
types.fills/fill-attrs)))
|
||||
|
||||
(update-object [object]
|
||||
(if (cfh/text-shape? object)
|
||||
@@ -1630,6 +1640,125 @@
|
||||
;; as value; this migration fixes it.
|
||||
(d/update-when data :components d/update-vals d/without-nils))
|
||||
|
||||
(defmethod migrate-data "0015-fix-text-attrs-blank-strings"
|
||||
[data _]
|
||||
;; After making text validation more restrictive (using ::sm/text
|
||||
;; instead of :string), we need to fix text attributes that contain
|
||||
;; empty or blank strings. These should be replaced with default
|
||||
;; values from default-text-attrs.
|
||||
(letfn [(blank-or-empty? [v]
|
||||
(or (nil? v)
|
||||
(and (string? v)
|
||||
(or (str/empty? v)
|
||||
(str/blank? v)))))
|
||||
|
||||
(get-default-value [attr]
|
||||
(let [defaults types.text/default-text-attrs]
|
||||
(case attr
|
||||
;; direction in content maps to text-direction in defaults
|
||||
:direction (:text-direction defaults)
|
||||
;; For other attrs, get directly from defaults
|
||||
(get defaults attr))))
|
||||
|
||||
(fix-text-attrs [node]
|
||||
;; These are the attributes that were changed to ::sm/text in the schema
|
||||
(let [text-attrs [:font-family :font-size :font-style :font-weight
|
||||
:direction :text-decoration :text-transform]]
|
||||
(reduce
|
||||
(fn [node attr]
|
||||
(if (and (contains? node attr)
|
||||
(blank-or-empty? (get node attr)))
|
||||
;; Replace blank/empty value with default
|
||||
(if-let [default-val (get-default-value attr)]
|
||||
(assoc node attr default-val)
|
||||
;; If no default, remove the attribute
|
||||
(dissoc node attr))
|
||||
node))
|
||||
node
|
||||
text-attrs)))
|
||||
|
||||
(fix-position-data [position-data]
|
||||
(mapv fix-text-attrs position-data))
|
||||
|
||||
(fix-text-content [content]
|
||||
(types.text/transform-nodes types.text/is-content-node? fix-text-attrs content))
|
||||
|
||||
(update-shape [object]
|
||||
(if (cfh/text-shape? object)
|
||||
(-> object
|
||||
(d/update-when :content fix-text-content)
|
||||
(d/update-when :position-data fix-position-data))
|
||||
object))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects d/update-vals update-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
;; Copy fills from position-data to text nodes when all text nodes lack fills,
|
||||
;; all position-data have fills, and the counts match
|
||||
(defmethod migrate-data "0016-copy-fills-from-position-data-to-text-node"
|
||||
[data _]
|
||||
(letfn [(get-text-nodes [content]
|
||||
;; Get all leaf text nodes from the content tree
|
||||
(when content
|
||||
(->> (types.text/node-seq types.text/is-text-node? content)
|
||||
(seq))))
|
||||
|
||||
(update-content [content fills-map]
|
||||
;; Transform the content tree to update text nodes with their corresponding fills
|
||||
;; fills-map is a map from text node to its fills
|
||||
(types.text/transform-nodes
|
||||
types.text/is-text-node?
|
||||
(fn [text-node]
|
||||
(if-let [fills (get fills-map text-node)]
|
||||
(assoc text-node :fills fills)
|
||||
text-node))
|
||||
content))
|
||||
|
||||
(update-object [object]
|
||||
(if (cfh/text-shape? object)
|
||||
(let [content (:content object)
|
||||
position-data (:position-data object)
|
||||
text-nodes (get-text-nodes content)]
|
||||
|
||||
;; Check if conditions are met:
|
||||
;; 1. Has at least one text node
|
||||
;; 2. All text nodes have no fills or empty fills
|
||||
;; 3. Has at least one position-data entry
|
||||
;; 4. All position-data have fills
|
||||
;; 5. The number of text nodes matches the number of position-data
|
||||
(if (and (seq text-nodes)
|
||||
(seq position-data)
|
||||
(= (count text-nodes) (count position-data))
|
||||
(every? (fn [text-node]
|
||||
(let [fills (:fills text-node)]
|
||||
(or (nil? fills) (empty? fills))))
|
||||
text-nodes)
|
||||
(every? (fn [pd]
|
||||
(let [fills (:fills pd)]
|
||||
(and (some? fills) (seq fills))))
|
||||
position-data))
|
||||
|
||||
;; Apply the migration: create a map from each text node to its corresponding fills
|
||||
(let [fills-map (zipmap text-nodes (map :fills position-data))]
|
||||
(update object :content #(update-content % fills-map)))
|
||||
|
||||
;; Don't modify if conditions aren't met
|
||||
object))
|
||||
|
||||
;; Not a text shape, return as-is
|
||||
object))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(def available-migrations
|
||||
(into (d/ordered-set)
|
||||
["legacy-2"
|
||||
@@ -1701,4 +1830,6 @@
|
||||
"0013-fix-component-path"
|
||||
"0013-clear-invalid-strokes-and-fills"
|
||||
"0014-fix-tokens-lib-duplicate-ids"
|
||||
"0014-clear-components-nil-objects"]))
|
||||
"0014-clear-components-nil-objects"
|
||||
"0015-fix-text-attrs-blank-strings"
|
||||
"0016-copy-fills-from-position-data-to-text-node"]))
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
(let [attr? (set attributes)]
|
||||
(->> (remove (fn [[k v]]
|
||||
(and (attr? k)
|
||||
(= v (token-identifier token))))
|
||||
(= v (or (token-identifier token) token))))
|
||||
applied-tokens)
|
||||
(into {}))))
|
||||
|
||||
|
||||
@@ -10,16 +10,23 @@
|
||||
[app.common.types.components-list :as ctcl]
|
||||
[app.common.types.variant :as ctv]))
|
||||
|
||||
|
||||
(defn find-variant-components
|
||||
"Find a list of the components thet belongs to this variant-id"
|
||||
[data objects variant-id]
|
||||
;; We can't simply filter components, because we need to maintain the order
|
||||
(->> (dm/get-in objects [variant-id :shapes])
|
||||
(map #(dm/get-in objects [% :component-id]))
|
||||
(map #(ctcl/get-component data % true))
|
||||
reverse))
|
||||
|
||||
([data variant-id]
|
||||
(let [page-id (->> data
|
||||
:components
|
||||
vals
|
||||
(filter #(= (:variant-id %) variant-id))
|
||||
first
|
||||
:main-instance-page)
|
||||
objects (dm/get-in data [:pages-index page-id :objects])]
|
||||
(find-variant-components data objects variant-id)))
|
||||
([data objects variant-id]
|
||||
;; We can't simply filter components, because we need to maintain the order
|
||||
(->> (dm/get-in objects [variant-id :shapes])
|
||||
(map #(dm/get-in objects [% :component-id]))
|
||||
(map #(ctcl/get-component data % true))
|
||||
reverse)))
|
||||
|
||||
(defn extract-properties-names
|
||||
[shape data]
|
||||
@@ -28,7 +35,6 @@
|
||||
:variant-properties
|
||||
(map :name)))
|
||||
|
||||
|
||||
(defn extract-properties-values
|
||||
"Get a map of properties associated to their possible values"
|
||||
[data objects variant-id]
|
||||
@@ -50,7 +56,6 @@
|
||||
(get :objects))]
|
||||
(dm/get-in objects [variant-id :shapes]))))
|
||||
|
||||
|
||||
(defn is-secondary-variant?
|
||||
[component data]
|
||||
(let [shapes (get-variant-mains component data)]
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
:token-color
|
||||
:token-typography-types
|
||||
:token-typography-composite
|
||||
:token-shadow
|
||||
:transit-readable-response
|
||||
:user-feedback
|
||||
;; TODO: remove this flag.
|
||||
|
||||
@@ -1642,7 +1642,8 @@
|
||||
(pcb/apply-changes-local)))))
|
||||
|
||||
(defn- generate-update-tokens
|
||||
[changes container dest-shape origin-shape touched omit-touched?]
|
||||
[changes container dest-shape origin-shape touched omit-touched? valid-attrs]
|
||||
;; valid-attrs is a set of attrs to consider on the update. If it is nil, it will consider all the attrs
|
||||
(let [attrs (->> (seq (keys ctk/sync-attrs))
|
||||
;; We don't update the flex-child attrs
|
||||
(remove #(= :layout-grid-cells %)))
|
||||
@@ -1650,8 +1651,8 @@
|
||||
applied-tokens (reduce (fn [applied-tokens attr]
|
||||
(let [attr-group (get ctk/sync-attrs attr)
|
||||
token-attrs (cto/shape-attr->token-attrs attr)]
|
||||
(if (not (and (touched attr-group)
|
||||
omit-touched?))
|
||||
(if (and (or (not omit-touched?) (not (touched attr-group)))
|
||||
(or (empty? valid-attrs) (contains? valid-attrs attr)))
|
||||
(into applied-tokens token-attrs)
|
||||
applied-tokens)))
|
||||
#{}
|
||||
@@ -1808,7 +1809,7 @@
|
||||
:always
|
||||
(check-detached-main dest-shape origin-shape)
|
||||
:always
|
||||
(generate-update-tokens container dest-shape origin-shape touched omit-touched?))
|
||||
(generate-update-tokens container dest-shape origin-shape touched omit-touched? nil))
|
||||
|
||||
(let [attr-group (get ctk/sync-attrs attr)
|
||||
;; position-data is a special case because can be affected by
|
||||
@@ -1991,6 +1992,12 @@
|
||||
;; If the values are already equal, don't copy them
|
||||
(= (get previous-shape attr) (get current-shape attr))
|
||||
|
||||
;; If the value is the same as the origin, don't copy it
|
||||
(= (get previous-shape attr) (get origin-ref-shape attr))
|
||||
|
||||
;; If the attr is not touched, don't copy it
|
||||
(not (touched attr-group))
|
||||
|
||||
;; If both variants (origin and destiny) don't have the same value
|
||||
;; for that attribute, don't copy it.
|
||||
;; Exceptions: :points :selrect and :content can be different
|
||||
@@ -2006,10 +2013,7 @@
|
||||
(not= (get origin-ref-shape attr) (get current-shape attr)))
|
||||
|
||||
;; The :content attr cant't be copied to elements of different type
|
||||
(and (= attr :content) (not= (:type previous-shape) (:type current-shape)))
|
||||
|
||||
;; If the attr is not touched, don't copy it
|
||||
(not (touched attr-group)))
|
||||
(and (= attr :content) (not= (:type previous-shape) (:type current-shape))))
|
||||
|
||||
;; On texts, both text (the actual letters)
|
||||
;; and attrs (bold, font, etc) are in the same attr :content.
|
||||
@@ -2082,12 +2086,14 @@
|
||||
(recur (next attrs)
|
||||
roperations'
|
||||
uoperations'))
|
||||
(cond-> changes
|
||||
(> (count roperations) 1)
|
||||
(add-update-attr-changes current-shape container roperations uoperations)
|
||||
|
||||
:always
|
||||
(generate-update-tokens container current-shape previous-shape touched false))))))
|
||||
(let [updated-attrs (into #{} (comp (filter #(= :set (:type %)))
|
||||
(map :attr))
|
||||
roperations)]
|
||||
(cond-> changes
|
||||
(> (count roperations) 1)
|
||||
(-> (add-update-attr-changes current-shape container roperations uoperations)
|
||||
(generate-update-tokens container current-shape previous-shape touched false updated-attrs))))))))
|
||||
|
||||
(defn- propagate-attrs
|
||||
"Helper that puts the origin attributes (attrs) into dest but only if
|
||||
@@ -2798,7 +2804,7 @@
|
||||
(defn generate-duplicate-changes
|
||||
"Prepare objects to duplicate: generate new id, give them unique names,
|
||||
move to the desired position, and recalculate parents and frames as needed."
|
||||
[changes all-objects page ids delta libraries library-data file-id & {:keys [variant-props]}]
|
||||
[changes all-objects page ids delta libraries library-data file-id & {:keys [variant-props alt-duplication?]}]
|
||||
(let [shapes (map (d/getf all-objects) ids)
|
||||
unames (volatile! (cfh/get-used-names (:objects page)))
|
||||
update-unames! (fn [new-name] (vswap! unames conj new-name))
|
||||
@@ -2808,10 +2814,22 @@
|
||||
;; we calculate a new one because the components will have created new shapes.
|
||||
ids-map (into {} (map #(vector % (uuid/next))) all-ids)
|
||||
|
||||
|
||||
;; If there is an alt-duplication of a variant, change its parent to root
|
||||
;; so the copy is made as a child of root
|
||||
;; This is because inside a variant-container can't be a copy
|
||||
shapes (map (fn [shape]
|
||||
(if (and alt-duplication? (ctk/is-variant? shape))
|
||||
(assoc shape :parent-id uuid/zero :frame-id nil)
|
||||
shape))
|
||||
shapes)
|
||||
|
||||
|
||||
changes (-> changes
|
||||
(pcb/with-page page)
|
||||
(pcb/with-objects all-objects)
|
||||
(pcb/with-library-data library-data))
|
||||
|
||||
changes
|
||||
(->> shapes
|
||||
(reduce #(generate-duplicate-shape-change %1
|
||||
|
||||
@@ -28,11 +28,7 @@
|
||||
(pcb/update-component
|
||||
changes (:id component)
|
||||
(fn [component]
|
||||
(d/update-in-when component [:variant-properties pos]
|
||||
(fn [property]
|
||||
(-> property
|
||||
(assoc :name new-name)
|
||||
(with-meta nil)))))
|
||||
(d/update-in-when component [:variant-properties pos] #(assoc % :name new-name)))
|
||||
{:apply-changes-local-library? true}))
|
||||
changes
|
||||
related-components)))
|
||||
@@ -42,18 +38,21 @@
|
||||
[changes variant-id pos]
|
||||
(let [data (pcb/get-library-data changes)
|
||||
objects (pcb/get-objects changes)
|
||||
related-components (cfv/find-variant-components data objects variant-id)]
|
||||
(reduce (fn [changes component]
|
||||
(let [props (:variant-properties component)
|
||||
props (d/remove-at-index props pos)
|
||||
main-id (:main-instance-id component)
|
||||
name (ctv/properties-to-name props)]
|
||||
(-> changes
|
||||
(pcb/update-component (:id component) #(assoc % :variant-properties props)
|
||||
{:apply-changes-local-library? true})
|
||||
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
|
||||
changes
|
||||
related-components)))
|
||||
related-components (cfv/find-variant-components data objects variant-id)
|
||||
props (-> related-components first :variant-properties)]
|
||||
(if (and (seq props) (<= 0 pos) (< pos (count props)))
|
||||
(reduce (fn [changes component]
|
||||
(let [props (:variant-properties component)
|
||||
props (d/remove-at-index props pos)
|
||||
main-id (:main-instance-id component)
|
||||
name (ctv/properties-to-name props)]
|
||||
(-> changes
|
||||
(pcb/update-component (:id component) #(assoc % :variant-properties props)
|
||||
{:apply-changes-local-library? true})
|
||||
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
|
||||
changes
|
||||
related-components)
|
||||
changes)))
|
||||
|
||||
|
||||
(defn generate-update-property-value
|
||||
@@ -88,7 +87,7 @@
|
||||
related-components (cfv/find-variant-components data objects variant-id)]
|
||||
(reduce (fn [changes component]
|
||||
(let [props (:variant-properties component)
|
||||
props (ctv/reorder-by-moving-to-position props from-pos to-space-between-pos)
|
||||
props (d/reorder props from-pos to-space-between-pos)
|
||||
main-id (:main-instance-id component)
|
||||
name (ctv/properties-to-name props)]
|
||||
(-> changes
|
||||
|
||||
@@ -67,7 +67,6 @@
|
||||
[[] {}]
|
||||
shapes))))
|
||||
|
||||
|
||||
(defn- keep-swapped-item
|
||||
"As part of the keep-touched process on a switch, given a child on the original
|
||||
copy that was swapped (orig-swapped-child), and its related shape on the new copy
|
||||
@@ -88,7 +87,6 @@
|
||||
current-parent (get objects (:parent-id related-shape-in-new))
|
||||
pos (d/index-of (:shapes current-parent) (:id related-shape-in-new))]
|
||||
|
||||
|
||||
(-> (pcb/concat-changes before-changes changes)
|
||||
|
||||
;; Move the previous shape to the new parent
|
||||
@@ -122,6 +120,29 @@
|
||||
(subvec (vec ancestors) 1 (dec num-ancestors)))]
|
||||
(some ctk/get-swap-slot ancestors)))
|
||||
|
||||
(defn- find-shape-ref-child-of
|
||||
"Get the shape referenced by the shape-ref of the near main of the shape,
|
||||
recursively repeated until find a shape-ref with parent-id as ancestor.
|
||||
It will return the shape or nil if it doesn't found any"
|
||||
[container libraries shape parent-id]
|
||||
(let [ref-shape (ctf/find-ref-shape nil container libraries shape
|
||||
:with-context? true)
|
||||
|
||||
ref-shape-container (when ref-shape (:container (meta ref-shape)))
|
||||
ref-shape-parents-set (when ref-shape
|
||||
(->> (cfh/get-parents-with-self (:objects ref-shape-container) (:id ref-shape))
|
||||
(into #{} d/xf:map-id)))]
|
||||
|
||||
(if (or (nil? ref-shape) (contains? ref-shape-parents-set parent-id))
|
||||
ref-shape
|
||||
(find-shape-ref-child-of ref-shape-container libraries ref-shape parent-id))))
|
||||
|
||||
(defn- add-touched-from-ref-chain
|
||||
"Adds to the :touched attr of a shape the content of
|
||||
the :touched of all its chain of ref shapes"
|
||||
[container libraries shape]
|
||||
(let [new-touched (ctf/get-touched-from-ref-chain-until-target-ref container libraries shape nil)]
|
||||
(assoc shape :touched new-touched)))
|
||||
|
||||
(defn generate-keep-touched
|
||||
"This is used as part of the switch process, when you switch from
|
||||
@@ -141,7 +162,10 @@
|
||||
;; Ignore children of swapped items, because
|
||||
;; they will be moved without change when
|
||||
;; managing their swapped ancestor
|
||||
orig-touched (->> (filter (comp seq :touched) original-shapes)
|
||||
orig-touched (->> original-shapes
|
||||
;; Add to each shape also the touched of its ref chain
|
||||
(map #(add-touched-from-ref-chain container libraries %))
|
||||
(filter (comp seq :touched))
|
||||
(remove
|
||||
#(child-of-swapped? %
|
||||
page-objects
|
||||
@@ -158,20 +182,19 @@
|
||||
|
||||
;; The original-shape is in a copy. For the relation rules, we need the referenced
|
||||
;; shape on the main component
|
||||
orig-ref-shape (ctf/find-ref-shape nil container libraries original-shape {:with-context? true})
|
||||
orig-ref-objects (:objects (:container (meta orig-ref-shape)))
|
||||
orig-base-ref-shape (ctf/find-remote-shape container libraries original-shape {:with-context? true})
|
||||
orig-ref-objects (:objects (:container (meta orig-base-ref-shape)))
|
||||
|
||||
;; Adds a :shape-path attribute to the children of the orig-ref-shape,
|
||||
;; that contains the type of its ancestors and its name
|
||||
o-ref-shapes-wp (add-unique-path
|
||||
(reverse (cfh/get-children-with-self orig-ref-objects (:id orig-ref-shape)))
|
||||
(reverse (cfh/get-children-with-self orig-ref-objects (:id orig-base-ref-shape)))
|
||||
orig-ref-objects
|
||||
(:id orig-ref-shape))
|
||||
(:id orig-base-ref-shape))
|
||||
|
||||
;; Creates a map to quickly find a child of the orig-ref-shape by its shape-path
|
||||
o-ref-shapes-p-map (into {} (map (juxt :id :shape-path)) o-ref-shapes-wp)
|
||||
|
||||
|
||||
;; Process each touched children of the original-shape
|
||||
[changes parents-of-swapped]
|
||||
(reduce
|
||||
@@ -182,8 +205,7 @@
|
||||
;; orig-child-touched is in a copy. Get the referenced shape on the main component
|
||||
;; If there is a swap slot, we will get the referenced shape in another way
|
||||
orig-ref-shape (when-not swap-slot
|
||||
;; TODO Maybe just get it from o-ref-shapes-wp
|
||||
(ctf/find-ref-shape nil container libraries orig-child-touched))
|
||||
(find-shape-ref-child-of container libraries orig-child-touched (:id orig-base-ref-shape)))
|
||||
|
||||
orig-ref-id (if swap-slot
|
||||
;; If there is a swap slot, find the referenced shape id
|
||||
@@ -193,9 +215,11 @@
|
||||
|
||||
;; Get the shape path of the referenced main
|
||||
shape-path (get o-ref-shapes-p-map orig-ref-id)
|
||||
|
||||
;; Get its related shape in the children of new-shape: the one that
|
||||
;; has the same shape-path
|
||||
related-shape-in-new (get new-shapes-map shape-path)
|
||||
|
||||
parents-of-swapped (if related-shape-in-new
|
||||
(conj parent-of-swapped (:parent-id related-shape-in-new))
|
||||
parent-of-swapped)
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
|
||||
(defn add-variant
|
||||
[file variant-label component1-label root1-label component2-label root2-label
|
||||
& {:keys []}]
|
||||
& {:keys [variant1-params variant2-params]
|
||||
:or {variant1-params {} variant2-params {}}}]
|
||||
(let [file (ths/add-sample-shape file variant-label :type :frame :is-variant-container true)
|
||||
variant-id (thi/id variant-label)]
|
||||
|
||||
(-> file
|
||||
(ths/add-sample-shape root2-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value2")
|
||||
(ths/add-sample-shape root1-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value1")
|
||||
(ths/add-sample-shape root2-label (assoc variant2-params :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value2"))
|
||||
(ths/add-sample-shape root1-label (assoc variant1-params :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value1"))
|
||||
(thc/make-component component1-label root1-label)
|
||||
(thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property 1" :value "Value1"}]})
|
||||
(thc/make-component component2-label root2-label)
|
||||
@@ -42,7 +43,8 @@
|
||||
|
||||
(defn add-variant-with-child
|
||||
[file variant-label component1-label root1-label component2-label root2-label child1-label child2-label
|
||||
& {:keys [child1-params child2-params]}]
|
||||
& {:keys [child1-params child2-params]
|
||||
:or {child1-params {} child2-params {}}}]
|
||||
(let [file (ths/add-sample-shape file variant-label :type :frame :is-variant-container true)
|
||||
variant-id (thi/id variant-label)]
|
||||
(-> file
|
||||
|
||||
@@ -286,7 +286,7 @@
|
||||
(fn [touched]
|
||||
(into #{} (remove #(str/starts-with? (name %) "swap-slot-") touched)))))
|
||||
|
||||
(defn get-component-root
|
||||
(defn get-deleted-component-root
|
||||
[component]
|
||||
(if (some? (:main-instance-id component))
|
||||
(get-in component [:objects (:main-instance-id component)])
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
[app.common.types.typographies-list :as ctyl]
|
||||
[app.common.types.typography :as cty]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -276,7 +277,7 @@
|
||||
(-> file-data
|
||||
(get-component-page component)
|
||||
(ctn/get-shape (:main-instance-id component)))
|
||||
(ctk/get-component-root component)))
|
||||
(ctk/get-deleted-component-root component)))
|
||||
|
||||
(defn get-component-shape
|
||||
"Retrieve one shape in the component by id. If with-context? is true, add the
|
||||
@@ -355,7 +356,7 @@
|
||||
|
||||
(defn find-remote-shape
|
||||
"Recursively go back by the :shape-ref of the shape until find the correct shape of the original component"
|
||||
[container libraries shape]
|
||||
[container libraries shape & {:keys [with-context?] :or {with-context? false}}]
|
||||
(let [top-instance (ctn/get-component-shape (:objects container) shape)
|
||||
component-file (get-in libraries [(:component-file top-instance) :data])
|
||||
component (ctkl/get-component component-file (:component-id top-instance) true)
|
||||
@@ -375,8 +376,12 @@
|
||||
(if (nil? remote-shape)
|
||||
nil
|
||||
(if (nil? (:shape-ref remote-shape))
|
||||
remote-shape
|
||||
(find-remote-shape component-container libraries remote-shape)))))
|
||||
(cond-> remote-shape
|
||||
(and remote-shape with-context?)
|
||||
(with-meta {:file {:id (:id file-data)
|
||||
:data file-data}
|
||||
:container component-container}))
|
||||
(find-remote-shape component-container libraries remote-shape :with-context? with-context?)))))
|
||||
|
||||
(defn direct-copy?
|
||||
"Check if the shape is in a direct copy of the component (i.e. the shape-ref points to shapes inside
|
||||
@@ -901,7 +906,7 @@
|
||||
(println))
|
||||
|
||||
(when (seq (:objects component))
|
||||
(let [root (ctk/get-component-root component)]
|
||||
(let [root (ctk/get-deleted-component-root component)]
|
||||
(dump-shape (:id root)
|
||||
1
|
||||
(:objects component)
|
||||
@@ -1115,3 +1120,29 @@
|
||||
(defn set-base-font-size
|
||||
[file-data base-font-size]
|
||||
(assoc-in file-data [:options :base-font-size] base-font-size))
|
||||
|
||||
|
||||
;; Ref Chains
|
||||
(defn get-ref-chain-until-target-ref
|
||||
"Returns a vector with the shape ref chain until target-ref, including itself"
|
||||
[container libraries shape target-ref]
|
||||
(loop [chain [shape]
|
||||
current shape]
|
||||
(if (= current target-ref)
|
||||
chain
|
||||
(if-let [ref (find-ref-shape nil container libraries current :with-context? true)]
|
||||
(recur (conj chain ref) ref)
|
||||
chain))))
|
||||
|
||||
(defn get-touched-from-ref-chain-until-target-ref
|
||||
"Returns a set with the :touched of all the items on the shape
|
||||
ref chain until target-ref, including itself"
|
||||
[container libraries shape target-ref]
|
||||
(let [chain (get-ref-chain-until-target-ref container libraries shape target-ref)
|
||||
more-touched (->> chain
|
||||
(map :touched)
|
||||
(remove nil?)
|
||||
(apply set/union)
|
||||
(remove ctk/swap-slot?)
|
||||
set)]
|
||||
(set/union (or (:touched shape) #{}) more-touched)))
|
||||
|
||||
@@ -301,11 +301,17 @@
|
||||
|
||||
IHeapWritable
|
||||
(-get-byte-size [_]
|
||||
(- (.-byteLength dbuffer) 4))
|
||||
;; Include the 4-byte header with the fill count
|
||||
(+ 4 (* size FILL-U8-SIZE)))
|
||||
|
||||
(-write-to [_ heap offset]
|
||||
(let [buffer' (.-buffer ^js/DataView dbuffer)]
|
||||
(.set heap (js/Uint32Array. buffer' 4) offset)))
|
||||
(let [buffer' (.-buffer ^js/DataView dbuffer)
|
||||
;; Calculate byte size: 4 bytes header + (size * FILL-U8-SIZE)
|
||||
byte-size (+ 4 (* size FILL-U8-SIZE))
|
||||
;; Create Uint32Array with exact size needed (convert bytes to u32 elements)
|
||||
u32-array (js/Uint32Array. buffer' 0 (/ byte-size 4))]
|
||||
;; Copy from offset 0 to include the header with fill count
|
||||
(.set heap u32-array offset)))
|
||||
|
||||
IBinaryFills
|
||||
(-get-image-ids [_]
|
||||
|
||||
@@ -36,8 +36,7 @@
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.set :as set]))
|
||||
|
||||
(defonce ^:dynamic *wasm-sync* false)
|
||||
|
||||
(defonce ^:dynamic *shape-changes* nil)
|
||||
(defonce wasm-enabled? false)
|
||||
(defonce wasm-create-shape (constantly nil))
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
[:fills {:optional true}
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} schema:fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:direction {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]
|
||||
[:font-family {:optional true} ::sm/text]
|
||||
[:font-size {:optional true} ::sm/text]
|
||||
[:font-style {:optional true} ::sm/text]
|
||||
[:font-weight {:optional true} ::sm/text]
|
||||
[:direction {:optional true} ::sm/text]
|
||||
[:text-decoration {:optional true} ::sm/text]
|
||||
[:text-transform {:optional true} ::sm/text]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]
|
||||
[:children
|
||||
@@ -51,13 +51,13 @@
|
||||
[:fills {:optional true}
|
||||
[:maybe
|
||||
[:vector {:gen/max 2} schema:fill]]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:direction {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]
|
||||
[:font-family {:optional true} ::sm/text]
|
||||
[:font-size {:optional true} ::sm/text]
|
||||
[:font-style {:optional true} ::sm/text]
|
||||
[:font-weight {:optional true} ::sm/text]
|
||||
[:direction {:optional true} ::sm/text]
|
||||
[:text-decoration {:optional true} ::sm/text]
|
||||
[:text-transform {:optional true} ::sm/text]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:typography-ref-file {:optional true} [:maybe ::sm/uuid]]]]]]]]]]]]])
|
||||
|
||||
@@ -72,11 +72,11 @@
|
||||
[:width ::sm/safe-number]
|
||||
[:height ::sm/safe-number]
|
||||
[:fills [:vector {:gen/max 2} schema:fill]]
|
||||
[:font-family {:optional true} :string]
|
||||
[:font-size {:optional true} :string]
|
||||
[:font-style {:optional true} :string]
|
||||
[:font-weight {:optional true} :string]
|
||||
[:font-family {:optional true} ::sm/text]
|
||||
[:font-size {:optional true} ::sm/text]
|
||||
[:font-style {:optional true} ::sm/text]
|
||||
[:font-weight {:optional true} ::sm/text]
|
||||
[:rtl {:optional true} :boolean]
|
||||
[:text {:optional true} :string]
|
||||
[:text-decoration {:optional true} :string]
|
||||
[:text-transform {:optional true} :string]]])
|
||||
[:text-decoration {:optional true} ::sm/text]
|
||||
[:text-transform {:optional true} ::sm/text]]])
|
||||
|
||||
@@ -249,12 +249,16 @@
|
||||
(defn equal-attrs?
|
||||
"Given a text structure, and a map of attrs, check that all the internal attrs in
|
||||
paragraphs and sentences have the same attrs"
|
||||
[item attrs]
|
||||
(let [item-attrs (dissoc item :text :type :key :children)]
|
||||
(and
|
||||
(or (empty? item-attrs)
|
||||
(= attrs (dissoc item :text :type :key :children)))
|
||||
(every? #(equal-attrs? % attrs) (:children item)))))
|
||||
([item attrs]
|
||||
;; Ignore the root attrs of the content. We only want to check paragraphs and sentences
|
||||
(equal-attrs? item attrs true))
|
||||
([item attrs ignore?]
|
||||
(let [item-attrs (dissoc item :text :type :key :children)]
|
||||
(and
|
||||
(or ignore?
|
||||
(empty? item-attrs)
|
||||
(= attrs (dissoc item :text :type :key :children)))
|
||||
(every? #(equal-attrs? % attrs false) (:children item))))))
|
||||
|
||||
(defn get-first-paragraph-text-attrs
|
||||
"Given a content text structure, extract it's first paragraph
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
(def token-type->dtcg-token-type
|
||||
{:boolean "boolean"
|
||||
:border-radius "borderRadius"
|
||||
:shadow "shadow"
|
||||
:color "color"
|
||||
:dimensions "dimension"
|
||||
:font-family "fontFamilies"
|
||||
@@ -77,7 +78,8 @@
|
||||
;; Allow these properties to be imported with singular key names for backwards compability
|
||||
(assoc "fontWeight" :font-weight
|
||||
"fontSize" :font-size
|
||||
"fontFamily" :font-family)))
|
||||
"fontFamily" :font-family
|
||||
"boxShadow" :shadow)))
|
||||
|
||||
(def composite-token-type->dtcg-token-type
|
||||
"Custom set of conversion keys for composite typography token with `:line-height` available.
|
||||
@@ -115,6 +117,12 @@
|
||||
|
||||
(def border-radius-keys (schema-keys schema:border-radius))
|
||||
|
||||
(def ^:private schema:shadow
|
||||
[:map {:title "ShadowTokenAttrs"}
|
||||
[:shadow {:optional true} token-name-ref]])
|
||||
|
||||
(def shadow-keys (schema-keys schema:shadow))
|
||||
|
||||
(def ^:private schema:stroke-width
|
||||
[:map
|
||||
[:stroke-width {:optional true} token-name-ref]])
|
||||
@@ -271,6 +279,7 @@
|
||||
|
||||
(def all-keys (set/union color-keys
|
||||
border-radius-keys
|
||||
shadow-keys
|
||||
stroke-width-keys
|
||||
sizing-keys
|
||||
opacity-keys
|
||||
@@ -289,6 +298,7 @@
|
||||
[:merge {:title "AppliedTokens"}
|
||||
schema:tokens
|
||||
schema:border-radius
|
||||
schema:shadow
|
||||
schema:sizing
|
||||
schema:spacing
|
||||
schema:rotation
|
||||
@@ -334,6 +344,7 @@
|
||||
(font-weight-keys shape-attr) #{shape-attr :typography}
|
||||
|
||||
(border-radius-keys shape-attr) #{shape-attr}
|
||||
(shadow-keys shape-attr) #{shape-attr}
|
||||
(sizing-keys shape-attr) #{shape-attr}
|
||||
(opacity-keys shape-attr) #{shape-attr}
|
||||
(spacing-keys shape-attr) #{shape-attr}
|
||||
@@ -361,6 +372,7 @@
|
||||
rotation-keys
|
||||
sizing-keys
|
||||
opacity-keys
|
||||
shadow-keys
|
||||
position-attributes))
|
||||
|
||||
(def rect-attributes
|
||||
@@ -444,6 +456,30 @@
|
||||
spacing-margin-keys)]
|
||||
(unapply-token-id shape layout-item-attrs)))
|
||||
|
||||
(def tokens-by-input
|
||||
"A map from input name to applicable token for that input."
|
||||
{:width #{:sizing :dimensions}
|
||||
:height #{:sizing :dimensions}
|
||||
:max-width #{:sizing :dimensions}
|
||||
:max-height #{:sizing :dimensions}
|
||||
:x #{:spacing :dimensions}
|
||||
:y #{:spacing :dimensions}
|
||||
:rotation #{:number :rotation}
|
||||
:border-radius #{:border-radius :dimensions}
|
||||
:row-gap #{:spacing :dimensions}
|
||||
:column-gap #{:spacing :dimensions}
|
||||
:horizontal-padding #{:spacing :dimensions}
|
||||
:vertical-padding #{:spacing :dimensions}
|
||||
:sided-paddings #{:spacing :dimensions}
|
||||
:horizontal-margin #{:spacing :dimensions}
|
||||
:vertical-margin #{:spacing :dimensions}
|
||||
:sided-margins #{:spacing :dimensions}
|
||||
:line-height #{:line-height :number}
|
||||
:font-size #{:font-size}
|
||||
:letter-spacing #{:letter-spacing}
|
||||
:fill #{:color}
|
||||
:stroke-color #{:color}})
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TYPOGRAPHY
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
@@ -514,26 +550,11 @@
|
||||
[token-value]
|
||||
(string? token-value))
|
||||
|
||||
(def tokens-by-input
|
||||
"A map from input name to applicable token for that input."
|
||||
{:width #{:sizing :dimensions}
|
||||
:height #{:sizing :dimensions}
|
||||
:max-width #{:sizing :dimensions}
|
||||
:max-height #{:sizing :dimensions}
|
||||
:x #{:spacing :dimensions}
|
||||
:y #{:spacing :dimensions}
|
||||
:rotation #{:number :rotation}
|
||||
:border-radius #{:border-radius :dimensions}
|
||||
:row-gap #{:spacing :dimensions}
|
||||
:column-gap #{:spacing :dimensions}
|
||||
:horizontal-padding #{:spacing :dimensions}
|
||||
:vertical-padding #{:spacing :dimensions}
|
||||
:sided-paddings #{:spacing :dimensions}
|
||||
:horizontal-margin #{:spacing :dimensions}
|
||||
:vertical-margin #{:spacing :dimensions}
|
||||
:sided-margins #{:spacing :dimensions}
|
||||
:line-height #{:line-height :number}
|
||||
:font-size #{:font-size}
|
||||
:letter-spacing #{:letter-spacing}
|
||||
:fill #{:color}
|
||||
:stroke-color #{:color}})
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SHADOW
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn shadow-composite-token-reference?
|
||||
"Predicate if a shadow composite token is a reference value - a string pointing to another reference token."
|
||||
[token-value]
|
||||
(string? token-value))
|
||||
|
||||
@@ -1552,6 +1552,46 @@ Will return a value that matches this schema:
|
||||
;; Reference value
|
||||
value))
|
||||
|
||||
(defn- convert-dtcg-shadow-composite
|
||||
"Convert shadow token value from DTCG format to internal format."
|
||||
[value]
|
||||
(let [process-shadow (fn [shadow]
|
||||
(if (map? shadow)
|
||||
(let [legacy-shadow-type (get "type" shadow)]
|
||||
(-> shadow
|
||||
(set/rename-keys {"x" :offsetX
|
||||
"offsetX" :offsetX
|
||||
"y" :offsetY
|
||||
"offsetY" :offsetY
|
||||
"blur" :blur
|
||||
"spread" :spread
|
||||
"color" :color
|
||||
"inset" :inset})
|
||||
(update :inset #(cond
|
||||
(boolean? %) %
|
||||
(= "true" %) true
|
||||
(= "false" %) false
|
||||
(= legacy-shadow-type "innerShadow") true
|
||||
:else false))
|
||||
(select-keys [:offsetX :offsetY :blur :spread :color :inset])))
|
||||
shadow))]
|
||||
(cond
|
||||
;; Reference value - keep as string
|
||||
(string? value)
|
||||
value
|
||||
|
||||
;; Array of shadows - process each
|
||||
(sequential? value)
|
||||
(mapv process-shadow value)
|
||||
|
||||
;; Single shadow object - wrap in vector
|
||||
(map? value)
|
||||
[(process-shadow value)]
|
||||
|
||||
;; Fallback - keep as is
|
||||
:else
|
||||
value)))
|
||||
|
||||
(defn- flatten-nested-tokens-json
|
||||
"Convert a tokens tree in the decoded json fragment into a flat map,
|
||||
being the keys the token paths after joining the keys with '.'."
|
||||
@@ -1574,6 +1614,7 @@ Will return a value that matches this schema:
|
||||
(case token-type
|
||||
:font-family (convert-dtcg-font-family token-value)
|
||||
:typography (convert-dtcg-typography-composite token-value)
|
||||
:shadow (convert-dtcg-shadow-composite token-value)
|
||||
token-value))
|
||||
:description (get v "$description")))
|
||||
;; Discard unknown type tokens
|
||||
@@ -1739,11 +1780,32 @@ Will return a value that matches this schema:
|
||||
{} value)
|
||||
value))
|
||||
|
||||
(defn- shadow-token->dtcg-token
|
||||
"Convert shadow token value from internal format to DTCG format."
|
||||
[value]
|
||||
(if (sequential? value)
|
||||
(mapv (fn [shadow]
|
||||
(if (map? shadow)
|
||||
(-> shadow
|
||||
(set/rename-keys {:offsetX "offsetX"
|
||||
:offsetY "offsetY"
|
||||
:blur "blur"
|
||||
:spread "spread"
|
||||
:color "color"
|
||||
:inset "inset"})
|
||||
(select-keys ["offsetX" "offsetY" "blur" "spread" "color" "inset"]))
|
||||
shadow))
|
||||
value)
|
||||
value))
|
||||
|
||||
(defn- token->dtcg-token [token]
|
||||
(cond-> {"$value" (cond-> (:value token)
|
||||
;; Transform typography token values
|
||||
(= :typography (:type token))
|
||||
typography-token->dtcg-token)
|
||||
typography-token->dtcg-token
|
||||
;; Transform shadow token values
|
||||
(= :shadow (:type token))
|
||||
shadow-token->dtcg-token)
|
||||
"$type" (cto/token-type->dtcg-token-type (:type token))}
|
||||
(:description token) (assoc "$description" (:description token))))
|
||||
|
||||
|
||||
@@ -310,27 +310,3 @@
|
||||
the real name of the shape joined by the properties values separated by '/'"
|
||||
[variant]
|
||||
(cpn/merge-path-item (:name variant) (str/replace (:variant-name variant) #", " " / ")))
|
||||
|
||||
(defn reorder-by-moving-to-position
|
||||
"Reorder a vector by moving one of their items from some position to some space between positions.
|
||||
It clamps the position numbers to a valid range."
|
||||
[props from-pos to-space-between-pos]
|
||||
(let [max-space-pos (count props)
|
||||
max-prop-pos (dec max-space-pos)
|
||||
|
||||
from-pos (max 0 (min max-prop-pos from-pos))
|
||||
to-space-between-pos (max 0 (min max-space-pos to-space-between-pos))]
|
||||
|
||||
(if (= from-pos to-space-between-pos)
|
||||
props
|
||||
(let [elem (nth props from-pos)
|
||||
without-elem (-> []
|
||||
(into (subvec props 0 from-pos))
|
||||
(into (subvec props (inc from-pos))))
|
||||
insert-pos (if (< from-pos to-space-between-pos)
|
||||
(dec to-space-between-pos)
|
||||
to-space-between-pos)]
|
||||
(-> []
|
||||
(into (subvec without-elem 0 insert-pos))
|
||||
(into [elem])
|
||||
(into (subvec without-elem insert-pos)))))))
|
||||
|
||||
@@ -102,3 +102,14 @@
|
||||
(t/is (= (d/insert-at-index [:a :b :c :d] 1 [:a])
|
||||
[:a :b :c :d])))
|
||||
|
||||
(t/deftest reorder
|
||||
(let [v ["a" "b" "c" "d"]]
|
||||
(t/is (= (d/reorder v 0 2) ["b" "a" "c" "d"]))
|
||||
(t/is (= (d/reorder v 0 3) ["b" "c" "a" "d"]))
|
||||
(t/is (= (d/reorder v 0 4) ["b" "c" "d" "a"]))
|
||||
(t/is (= (d/reorder v 3 0) ["d" "a" "b" "c"]))
|
||||
(t/is (= (d/reorder v 3 2) ["a" "b" "d" "c"]))
|
||||
(t/is (= (d/reorder v 0 5) ["b" "c" "d" "a"]))
|
||||
(t/is (= (d/reorder v 3 -1) ["d" "a" "b" "c"]))
|
||||
(t/is (= (d/reorder v 5 -1) ["d" "a" "b" "c"]))
|
||||
(t/is (= (d/reorder v -1 5) ["b" "c" "d" "a"]))))
|
||||
|
||||
@@ -18,6 +18,29 @@
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
|
||||
(t/deftest test-basic-switch
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant
|
||||
:v01 :c01 :m01 :c02 :m02
|
||||
{:variant1-params {:width 5}
|
||||
:variant2-params {:width 15}})
|
||||
|
||||
(thc/instantiate-component :c01
|
||||
:copy01))
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
copy01' (ths/get-shape file' :copy02)]
|
||||
(thf/dump-file file :keys [:width])
|
||||
;; The copy had width 5 before the switch
|
||||
(t/is (= (:width copy01) 5))
|
||||
;; The rect has width 15 after the switch
|
||||
(t/is (= (:width copy01') 15))))
|
||||
|
||||
(t/deftest test-simple-switch
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
@@ -46,6 +69,40 @@
|
||||
(t/is (= (:width rect02') 15))))
|
||||
|
||||
|
||||
(t/deftest test-basic-switch-override
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant
|
||||
:v01 :c01 :m01 :c02 :m02
|
||||
{:variant1-params {:width 5}
|
||||
:variant2-params {:width 5}})
|
||||
|
||||
(thc/instantiate-component :c01
|
||||
:copy01))
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
|
||||
;; Change width of copy
|
||||
page (thf/current-page file)
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id copy01)}
|
||||
(fn [shape]
|
||||
(assoc shape :width 25))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
copy01' (ths/get-shape file' :copy02)]
|
||||
(thf/dump-file file :keys [:width])
|
||||
;; The copy had width 25 before the switch
|
||||
(t/is (= (:width copy01) 25))
|
||||
;; The override is keept: The copy still has width 25 after the switch
|
||||
(t/is (= (:width copy01') 25))))
|
||||
|
||||
(t/deftest test-switch-with-override
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
@@ -125,12 +182,10 @@
|
||||
;; The rect has width 15 after the switch
|
||||
(t/is (= (:width rect02') 15))))
|
||||
|
||||
|
||||
(def font-size-path-paragraph [:content :children 0 :children 0 :font-size])
|
||||
(def font-size-path-0 [:content :children 0 :children 0 :children 0 :font-size])
|
||||
(def font-size-path-1 [:content :children 0 :children 0 :children 1 :font-size])
|
||||
|
||||
|
||||
(def text-path-0 [:content :children 0 :children 0 :children 0 :text])
|
||||
(def text-path-1 [:content :children 0 :children 0 :children 1 :text])
|
||||
(def text-lines-path [:content :children 0 :children 0 :children])
|
||||
@@ -188,6 +243,8 @@
|
||||
|
||||
|
||||
;; The copy clean has no overrides
|
||||
|
||||
|
||||
copy-clean (ths/get-shape file :copy-clean)
|
||||
copy-clean-t (ths/get-shape file :copy-clean-t)
|
||||
|
||||
@@ -209,6 +266,8 @@
|
||||
|
||||
|
||||
;; ==== Action: Switch all the copies
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
@@ -234,6 +293,8 @@
|
||||
;; Before the switch:
|
||||
;; * font size 14
|
||||
;; * text "hello world"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-clean-t font-size-path-0) "14"))
|
||||
(t/is (= (get-in copy-clean-t text-path-0) "hello world"))
|
||||
|
||||
@@ -248,6 +309,8 @@
|
||||
;; Before the switch:
|
||||
;; * font size 25
|
||||
;; * text "hello world"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
|
||||
(t/is (= (get-in copy-font-size-t text-path-0) "hello world"))
|
||||
|
||||
@@ -306,6 +369,8 @@
|
||||
|
||||
|
||||
;; The copy clean has no overrides
|
||||
|
||||
|
||||
copy-clean (ths/get-shape file :copy-clean)
|
||||
copy-clean-t (ths/get-shape file :copy-clean-t)
|
||||
|
||||
@@ -327,6 +392,8 @@
|
||||
|
||||
|
||||
;; ==== Action: Switch all the copies
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
@@ -352,6 +419,8 @@
|
||||
;; Before the switch:
|
||||
;; * font size 14
|
||||
;; * text "hello world"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-clean-t font-size-path-0) "14"))
|
||||
(t/is (= (get-in copy-clean-t text-path-0) "hello world"))
|
||||
|
||||
@@ -366,6 +435,8 @@
|
||||
;; Before the switch:
|
||||
;; * font size 25
|
||||
;; * text "hello world"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
|
||||
(t/is (= (get-in copy-font-size-t text-path-0) "hello world"))
|
||||
|
||||
@@ -401,7 +472,6 @@
|
||||
(t/is (= (get-in copy-both-t' font-size-path-0) "50"))
|
||||
(t/is (= (get-in copy-both-t' text-path-0) "text overriden"))))
|
||||
|
||||
|
||||
(t/deftest test-switch-with-different-text-text-override
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
@@ -423,6 +493,8 @@
|
||||
|
||||
|
||||
;; The copy clean has no overrides
|
||||
|
||||
|
||||
copy-clean (ths/get-shape file :copy-clean)
|
||||
copy-clean-t (ths/get-shape file :copy-clean-t)
|
||||
|
||||
@@ -444,6 +516,8 @@
|
||||
|
||||
|
||||
;; ==== Action: Switch all the copies
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
@@ -469,6 +543,8 @@
|
||||
;; Before the switch:
|
||||
;; * font size 14
|
||||
;; * text "hello world"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-clean-t font-size-path-0) "14"))
|
||||
(t/is (= (get-in copy-clean-t text-path-0) "hello world"))
|
||||
|
||||
@@ -483,6 +559,8 @@
|
||||
;; Before the switch:
|
||||
;; * font size 25
|
||||
;; * text "hello world"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
|
||||
(t/is (= (get-in copy-font-size-t text-path-0) "hello world"))
|
||||
|
||||
@@ -518,7 +596,6 @@
|
||||
(t/is (= (get-in copy-both-t' font-size-path-0) "25"))
|
||||
(t/is (= (get-in copy-both-t' text-path-0) "bye"))))
|
||||
|
||||
|
||||
(t/deftest test-switch-with-different-text-and-prop-text-override
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
@@ -542,6 +619,8 @@
|
||||
|
||||
|
||||
;; The copy clean has no overrides
|
||||
|
||||
|
||||
copy-clean (ths/get-shape file :copy-clean)
|
||||
copy-clean-t (ths/get-shape file :copy-clean-t)
|
||||
|
||||
@@ -563,6 +642,8 @@
|
||||
|
||||
|
||||
;; ==== Action: Switch all the copies
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
@@ -588,6 +669,8 @@
|
||||
;; Before the switch:
|
||||
;; * font size 14
|
||||
;; * text "hello world"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-clean-t font-size-path-0) "14"))
|
||||
(t/is (= (get-in copy-clean-t text-path-0) "hello world"))
|
||||
|
||||
@@ -602,6 +685,8 @@
|
||||
;; Before the switch:
|
||||
;; * font size 25
|
||||
;; * text "hello world"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
|
||||
(t/is (= (get-in copy-font-size-t text-path-0) "hello world"))
|
||||
|
||||
@@ -637,7 +722,6 @@
|
||||
(t/is (= (get-in copy-both-t' font-size-path-0) "50"))
|
||||
(t/is (= (get-in copy-both-t' text-path-0) "bye"))))
|
||||
|
||||
|
||||
(t/deftest test-switch-with-identical-structure-text-override
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
@@ -657,6 +741,8 @@
|
||||
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean
|
||||
|
||||
|
||||
file (change-structure file :copy-structure-clean-t)
|
||||
copy-structure-clean (ths/get-shape file :copy-structure-clean)
|
||||
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
|
||||
@@ -678,6 +764,8 @@
|
||||
|
||||
|
||||
;; ==== Action: Switch all the copies
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
@@ -763,7 +851,6 @@
|
||||
(t/is (= (get-in copy-structure-mixed-t' font-size-path-1) "40"))
|
||||
(t/is (= (get-in copy-structure-mixed-t' text-path-1) "new line 2"))))
|
||||
|
||||
|
||||
(t/deftest test-switch-with-different-prop-structure-text-override
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
@@ -784,6 +871,8 @@
|
||||
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean
|
||||
|
||||
|
||||
file (change-structure file :copy-structure-clean-t)
|
||||
copy-structure-clean (ths/get-shape file :copy-structure-clean)
|
||||
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
|
||||
@@ -805,6 +894,8 @@
|
||||
|
||||
|
||||
;; ==== Action: Switch all the copies
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
@@ -906,6 +997,8 @@
|
||||
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean
|
||||
|
||||
|
||||
file (change-structure file :copy-structure-clean-t)
|
||||
copy-structure-clean (ths/get-shape file :copy-structure-clean)
|
||||
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
|
||||
@@ -927,6 +1020,8 @@
|
||||
|
||||
|
||||
;; ==== Action: Switch all the copies
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
@@ -971,6 +1066,8 @@
|
||||
;; Second line:
|
||||
;; * font size 25
|
||||
;; * text "new line 2"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-structure-unif-t font-size-path-0) "25"))
|
||||
(t/is (= (get-in copy-structure-unif-t text-path-0) "new line 1"))
|
||||
(t/is (= (get-in copy-structure-unif-t font-size-path-1) "25"))
|
||||
@@ -992,6 +1089,8 @@
|
||||
;; Before the switch, second line:
|
||||
;; * font size 40
|
||||
;; * text "new line 2"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-structure-mixed-t font-size-path-0) "35"))
|
||||
(t/is (= (get-in copy-structure-mixed-t text-path-0) "new line 1"))
|
||||
(t/is (= (get-in copy-structure-mixed-t font-size-path-1) "40"))
|
||||
@@ -1025,6 +1124,8 @@
|
||||
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean
|
||||
|
||||
|
||||
file (change-structure file :copy-structure-clean-t)
|
||||
copy-structure-clean (ths/get-shape file :copy-structure-clean)
|
||||
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
|
||||
@@ -1046,6 +1147,8 @@
|
||||
|
||||
|
||||
;; ==== Action: Switch all the copies
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
@@ -1090,6 +1193,8 @@
|
||||
;; Second line:
|
||||
;; * font size 25
|
||||
;; * text "new line 2"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-structure-unif-t font-size-path-0) "25"))
|
||||
(t/is (= (get-in copy-structure-unif-t text-path-0) "new line 1"))
|
||||
(t/is (= (get-in copy-structure-unif-t font-size-path-1) "25"))
|
||||
@@ -1111,6 +1216,8 @@
|
||||
;; Before the switch, second line:
|
||||
;; * font size 40
|
||||
;; * text "new line 2"
|
||||
|
||||
|
||||
(t/is (= (get-in copy-structure-mixed-t font-size-path-0) "35"))
|
||||
(t/is (= (get-in copy-structure-mixed-t text-path-0) "new line 1"))
|
||||
(t/is (= (get-in copy-structure-mixed-t font-size-path-1) "40"))
|
||||
@@ -1124,7 +1231,6 @@
|
||||
(t/is (= (get-in copy-structure-mixed-t' text-path-0) "bye"))
|
||||
(t/is (nil? (get-in copy-structure-mixed-t' font-size-path-1)))))
|
||||
|
||||
|
||||
(t/deftest test-switch-variant-for-other-with-same-nested-component
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
@@ -1144,6 +1250,8 @@
|
||||
|
||||
|
||||
;; On :copy-cp01, change the width of the rect
|
||||
|
||||
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{copy-cp01-rect-id}
|
||||
(fn [shape]
|
||||
@@ -1166,8 +1274,6 @@
|
||||
;; The width of copy-cp02-rect' is 25 (change is preserved)
|
||||
(t/is (= (:width copy-cp02-rect') 25))))
|
||||
|
||||
|
||||
|
||||
(t/deftest test-switch-variant-that-has-swaped-copy
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
@@ -1193,7 +1299,6 @@
|
||||
;; Switch :c01 for :c02
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
copy-cp02' (ths/get-shape file' :copy-cp02)]
|
||||
(thf/dump-file file')
|
||||
@@ -1207,7 +1312,6 @@
|
||||
;;copy-02' had copy-cp02' as child
|
||||
(t/is (= (-> copy02' :shapes first) (:id copy-cp02')))))
|
||||
|
||||
|
||||
(t/deftest test-switch-variant-that-has-swaped-copy-with-changed-attr
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
@@ -1244,7 +1348,6 @@
|
||||
;; Switch :c01 for :c02
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
copy-cp02' (ths/get-shape file' :copy-cp02)
|
||||
copy-cp02-rect' (ths/get-shape-by-id file' (-> copy-cp02' :shapes first))]
|
||||
@@ -1262,3 +1365,58 @@
|
||||
(t/is (= (-> copy02' :shapes first) (:id copy-cp02')))
|
||||
;; The width of copy-cp02-rect' is 25 (change is preserved)
|
||||
(t/is (= (:width copy-cp02-rect') 25))))
|
||||
|
||||
(t/deftest test-switch-variant-without-touched-but-touched-parent
|
||||
(let [;; ==== Setup
|
||||
file (-> (thf/sample-file :file1)
|
||||
(thv/add-variant-with-child
|
||||
:v01 :c01 :m01 :c02 :m02 :r01 :r02
|
||||
{:child1-params {:width 5}
|
||||
:child2-params {:width 5}})
|
||||
(tho/add-simple-component :external01 :external01-root :external01-child)
|
||||
|
||||
(thc/instantiate-component :c01
|
||||
:c01-in-root
|
||||
:children-labels [:r01-in-c01-in-root]
|
||||
:parent-label :external01-root))
|
||||
|
||||
;; Make a change on r01-in-c01-in-root so it is touched
|
||||
page (thf/current-page file)
|
||||
r01-in-c01-in-root (ths/get-shape file :r01-in-c01-in-root)
|
||||
|
||||
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
|
||||
#{(:id r01-in-c01-in-root)}
|
||||
(fn [shape]
|
||||
(assoc shape :width 25))
|
||||
(:objects page)
|
||||
{})
|
||||
|
||||
file (thf/apply-changes file changes)
|
||||
|
||||
|
||||
;; Instantiate the component :external01
|
||||
|
||||
|
||||
file (thc/instantiate-component file
|
||||
:external01
|
||||
:external-copy01
|
||||
:children-labels [:external-copy01-rect :c01-in-copy])
|
||||
page (thf/current-page file)
|
||||
c01-in-copy (ths/get-shape file :c01-in-copy)
|
||||
rect01 (get-in page [:objects (-> c01-in-copy :shapes first)])
|
||||
|
||||
|
||||
;; ==== Action
|
||||
|
||||
|
||||
file' (tho/swap-component file c01-in-copy :c02 {:new-shape-label :c02-in-copy :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
c02-in-copy' (ths/get-shape file' :c02-in-copy)
|
||||
rect02' (get-in page' [:objects (-> c02-in-copy' :shapes first)])]
|
||||
|
||||
(thf/dump-file file :keys [:width :touched])
|
||||
;; The rect had width 25 before the switch
|
||||
(t/is (= (:width rect01) 25))
|
||||
;; The rect still has width 25 after the switch
|
||||
(t/is (= (:width rect02') 25))))
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"test": {
|
||||
"shadow-single": {
|
||||
"$value": {
|
||||
"x": "0",
|
||||
"y": "2px",
|
||||
"blur": "4px",
|
||||
"spread": "0",
|
||||
"color": "#000"
|
||||
},
|
||||
"$type": "boxShadow"
|
||||
},
|
||||
"shadow-multiple": {
|
||||
"$value": [
|
||||
{
|
||||
"x": "0",
|
||||
"y": "2px",
|
||||
"blur": "4px",
|
||||
"spread": "0",
|
||||
"color": "#000",
|
||||
"inset": true
|
||||
},
|
||||
{
|
||||
"x": "0",
|
||||
"y": "8px",
|
||||
"blur": "16px",
|
||||
"spread": "0",
|
||||
"color": "#000",
|
||||
"inset": "true"
|
||||
}
|
||||
],
|
||||
"$type": "boxShadow"
|
||||
},
|
||||
"shadow-ref": {
|
||||
"$value": "{shadow-single}",
|
||||
"$type": "boxShadow"
|
||||
},
|
||||
"shadow-with-type": {
|
||||
"$value": {
|
||||
"x": "0",
|
||||
"y": "4px",
|
||||
"blur": "8px",
|
||||
"spread": "0",
|
||||
"color": "rgba(0,0,0,0.2)",
|
||||
"type": "innerShadow"
|
||||
},
|
||||
"$type": "boxShadow"
|
||||
},
|
||||
"shadow-with-description": {
|
||||
"$value": {
|
||||
"x": "1px",
|
||||
"y": "1px",
|
||||
"blur": "3px",
|
||||
"color": "gray"
|
||||
},
|
||||
"$type": "boxShadow",
|
||||
"$description": "A simple shadow token"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1362,9 +1362,7 @@
|
||||
{:name "button.primary.background"
|
||||
:type :color
|
||||
:value "{accent.default}"
|
||||
:description ""})))
|
||||
(t/testing "invalid tokens got discarded"
|
||||
(t/is (nil? (ctob/get-token-by-name lib "theme" "boxShadow.default")))))))
|
||||
:description ""}))))))
|
||||
|
||||
#?(:clj
|
||||
(t/deftest parse-multi-set-dtcg-json
|
||||
@@ -1392,9 +1390,7 @@
|
||||
{:name "button.primary.background"
|
||||
:type :color
|
||||
:value "{accent.default}"
|
||||
:description ""})))
|
||||
(t/testing "invalid tokens got discarded"
|
||||
(t/is (nil? (ctob/get-token-by-name lib "theme" "boxShadow.default")))))))
|
||||
:description ""}))))))
|
||||
|
||||
#?(:clj
|
||||
(t/deftest parse-multi-set-dtcg-json-default-team
|
||||
@@ -1893,3 +1889,130 @@
|
||||
(t/is (some? imported-single))
|
||||
(t/is (= (:type imported-single) (:type original-single)))
|
||||
(t/is (= (:value imported-single) (:value original-single))))))))
|
||||
|
||||
#?(:clj
|
||||
(t/deftest parse-shadow-tokens
|
||||
(let [json (-> (slurp "test/common_tests/types/data/tokens-shadow-example.json")
|
||||
(json/decode {:key-fn identity}))
|
||||
lib (ctob/parse-decoded-json json "shadow-test")]
|
||||
|
||||
(t/testing "single shadow token"
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-single")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= [{:offsetX "0", :offsetY "2px", :blur "4px", :spread "0", :color "#000", :inset false}]
|
||||
(:value token)))))
|
||||
|
||||
(t/testing "multiple shadow token"
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-multiple")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= [{:offsetX "0", :offsetY "2px", :blur "4px", :spread "0", :color "#000", :inset true}
|
||||
{:offsetX "0", :offsetY "8px", :blur "16px", :spread "0", :color "#000", :inset true}]
|
||||
(:value token)))))
|
||||
|
||||
(t/testing "shadow token with reference"
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-ref")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= "{shadow-single}" (:value token)))))
|
||||
|
||||
(t/testing "shadow token with type"
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-with-type")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= [{:offsetX "0", :offsetY "4px", :blur "8px", :spread "0", :color "rgba(0,0,0,0.2)", :inset false}]
|
||||
(:value token)))))
|
||||
|
||||
(t/testing "shadow token with description"
|
||||
(let [token (ctob/get-token-by-name lib "shadow-test" "test.shadow-with-description")]
|
||||
(t/is (some? token))
|
||||
(t/is (= :shadow (:type token)))
|
||||
(t/is (= "A simple shadow token" (:description token))))))))
|
||||
|
||||
#?(:clj
|
||||
(t/deftest export-shadow-tokens
|
||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||
(ctob/add-set
|
||||
(ctob/make-token-set
|
||||
:name "shadow-set"
|
||||
:tokens {"shadow.single"
|
||||
(ctob/make-token
|
||||
{:name "shadow.single"
|
||||
:type :shadow
|
||||
:value [{:offsetX "0" :offsetY "2px" :blur "4px" :spread "0" :color "#0000001A"}]
|
||||
:description "A single shadow"})
|
||||
"shadow.multiple"
|
||||
(ctob/make-token
|
||||
{:name "shadow.multiple"
|
||||
:type :shadow
|
||||
:value [{:offsetX "0" :offsetY "2px" :blur "4px" :spread "0" :color "#0000001A"}
|
||||
{:offsetX "0" :offsetY "8px" :blur "16px" :spread "0" :color "#0000001A"}]})
|
||||
"shadow.ref"
|
||||
(ctob/make-token
|
||||
{:name "shadow.ref"
|
||||
:type :shadow
|
||||
:value "{shadow.single}"})
|
||||
"shadow.empty"
|
||||
(ctob/make-token
|
||||
{:name "shadow.empty"
|
||||
:type :shadow
|
||||
:value {}})})))
|
||||
result (ctob/export-dtcg-json tokens-lib)
|
||||
shadow-set (get result "shadow-set")]
|
||||
|
||||
(t/testing "single shadow token export"
|
||||
(let [single-token (get-in shadow-set ["shadow" "single"])]
|
||||
(t/is (= "shadow" (get single-token "$type")))
|
||||
(t/is (= [{"offsetX" "0" "offsetY" "2px" "blur" "4px" "spread" "0" "color" "#0000001A"}] (get single-token "$value")))
|
||||
(t/is (= "A single shadow" (get single-token "$description")))))
|
||||
|
||||
(t/testing "multiple shadow token export"
|
||||
(let [multiple-token (get-in shadow-set ["shadow" "multiple"])]
|
||||
(t/is (= "shadow" (get multiple-token "$type")))
|
||||
(t/is (= [{"offsetX" "0" "offsetY" "2px" "blur" "4px" "spread" "0" "color" "#0000001A"}
|
||||
{"offsetX" "0" "offsetY" "8px" "blur" "16px" "spread" "0" "color" "#0000001A"}]
|
||||
(get multiple-token "$value")))))
|
||||
|
||||
(t/testing "reference shadow token export"
|
||||
(let [ref-token (get-in shadow-set ["shadow" "ref"])]
|
||||
(t/is (= "shadow" (get ref-token "$type")))
|
||||
(t/is (= "{shadow.single}" (get ref-token "$value")))))
|
||||
|
||||
(t/testing "empty shadow token export"
|
||||
(let [empty-token (get-in shadow-set ["shadow" "empty"])]
|
||||
(t/is (= "shadow" (get empty-token "$type")))
|
||||
(t/is (= {} (get empty-token "$value"))))))))
|
||||
|
||||
#?(:clj
|
||||
(t/deftest shadow-token-round-trip
|
||||
(let [original-lib (-> (ctob/make-tokens-lib)
|
||||
(ctob/add-set
|
||||
(ctob/make-token-set
|
||||
:name "test-set"
|
||||
:tokens {"shadow.test"
|
||||
(ctob/make-token
|
||||
{:name "shadow.test"
|
||||
:type :shadow
|
||||
:value [{:offsetX "1" :offsetY "1" :blur "1" :spread "1" :color "red" :inset true}]
|
||||
:description "Round trip test"})
|
||||
"shadow.ref"
|
||||
(ctob/make-token
|
||||
{:name "shadow.ref"
|
||||
:type :shadow
|
||||
:value "{shadow.test}"})})))
|
||||
exported (ctob/export-dtcg-json original-lib)
|
||||
imported-lib (ctob/parse-decoded-json exported "")]
|
||||
|
||||
(t/testing "round trip preserves shadow tokens"
|
||||
(let [original-token (ctob/get-token-by-name original-lib "test-set" "shadow.test")
|
||||
imported-token (ctob/get-token-by-name imported-lib "test-set" "shadow.test")]
|
||||
(t/is (some? imported-token))
|
||||
(t/is (= (:type original-token) (:type imported-token)))
|
||||
(t/is (= (:value original-token) (:value imported-token)))
|
||||
(t/is (= (:description original-token) (:description imported-token))))
|
||||
(let [original-ref (ctob/get-token-by-name original-lib "test-set" "shadow.ref")
|
||||
imported-ref (ctob/get-token-by-name imported-lib "test-set" "shadow.ref")]
|
||||
(t/is (some? imported-ref))
|
||||
(t/is (= (:type original-ref) (:type imported-ref)))
|
||||
(t/is (= (:value imported-ref) (:value original-ref))))))))
|
||||
|
||||
@@ -159,48 +159,3 @@
|
||||
|
||||
(t/testing "update-number-in-repeated-prop-names"
|
||||
(t/is (= (ctv/update-number-in-repeated-prop-names props) numbered-props)))))
|
||||
|
||||
|
||||
(t/deftest reorder-by-moving-to-position
|
||||
(let [props [{:name "border" :value "no"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "background" :value "none"}]]
|
||||
|
||||
(t/testing "reorder-by-moving-to-position"
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 0 2) [{:name "color" :value "blue"}
|
||||
{:name "border" :value "no"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "background" :value "none"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 0 3) [{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "border" :value "no"}
|
||||
{:name "background" :value "none"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 0 4) [{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 3 0) [{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 3 2) [{:name "border" :value "no"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "background" :value "none"}
|
||||
{:name "shadow" :value "yes"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 0 5) [{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 3 -1) [{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props 5 -1) [{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}
|
||||
{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}]))
|
||||
(t/is (= (ctv/reorder-by-moving-to-position props -1 5) [{:name "color" :value "blue"}
|
||||
{:name "shadow" :value "yes"}
|
||||
{:name "background" :value "none"}
|
||||
{:name "border" :value "no"}])))))
|
||||
|
||||
@@ -73,7 +73,7 @@ RUN set -eux; \
|
||||
|
||||
FROM base AS setup-node
|
||||
|
||||
ENV NODE_VERSION=v22.19.0 \
|
||||
ENV NODE_VERSION=v22.21.1 \
|
||||
PATH=/opt/node/bin:$PATH
|
||||
|
||||
RUN set -eux; \
|
||||
@@ -113,12 +113,12 @@ RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
ESUM='b60eb9d54c97ba4159547834a98cc5d016281dd2b3e60e7475cba4911324bcb4'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.28.85-ca-jdk25.0.0-linux_aarch64.tar.gz'; \
|
||||
ESUM='8c5321f16d9f1d8149f83e4e9ff8ca5d9e94320b92d205e6db42a604de3d1140'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.30.17-ca-jdk25.0.1-linux_aarch64.tar.gz'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
ESUM='164d901e5a240b8c18516f5ab55bc11fc9689ab6e829045aea8467356dcdb340'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.28.85-ca-jdk25.0.0-linux_x64.tar.gz'; \
|
||||
ESUM='471b3e62bdffaed27e37005d842d8639f10d244ccce1c7cdebf7abce06c8313e'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.30.17-ca-jdk25.0.1-linux_x64.tar.gz'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
@@ -149,18 +149,24 @@ FROM base AS setup-rust
|
||||
ENV PATH=/opt/cargo/bin:$PATH \
|
||||
RUSTUP_HOME=/opt/rustup \
|
||||
CARGO_HOME=/opt/cargo \
|
||||
RUSTUP_VERSION=1.27.1 \
|
||||
RUST_VERSION=1.85.0 \
|
||||
RUSTUP_VERSION=1.28.2 \
|
||||
RUST_VERSION=1.91.0 \
|
||||
EMSCRIPTEN_VERSION=4.0.6
|
||||
|
||||
WORKDIR /opt
|
||||
|
||||
RUN set -eux; \
|
||||
# Same steps as in Rust official Docker image https://github.com/rust-lang/docker-rust/blob/9f287282d513a84cb7c7f38f197838f15d37b6a9/1.81.0/bookworm/Dockerfile
|
||||
dpkgArch="$(dpkg --print-architecture)"; \
|
||||
case "${dpkgArch##*-}" in \
|
||||
amd64) rustArch='x86_64-unknown-linux-gnu'; rustupSha256='6aeece6993e902708983b209d04c0d1dbb14ebb405ddb87def578d41f920f56d' ;; \
|
||||
arm64) rustArch='aarch64-unknown-linux-gnu'; rustupSha256='1cffbf51e63e634c746f741de50649bbbcbd9dbe1de363c9ecef64e278dba2b2' ;; \
|
||||
arch="$(dpkg --print-architecture)"; \
|
||||
case "$arch" in \
|
||||
'amd64') \
|
||||
rustArch='x86_64-unknown-linux-gnu'; \
|
||||
rustupSha256='20a06e644b0d9bd2fbdbfd52d42540bdde820ea7df86e92e533c073da0cdd43c'; \
|
||||
;; \
|
||||
'arm64') \
|
||||
rustArch='aarch64-unknown-linux-gnu'; \
|
||||
rustupSha256='e3853c5a252fca15252d07cb23a1bdd9377a8c6f3efa01531109281ae47f841c'; \
|
||||
;; \
|
||||
*) echo >&2 "unsupported architecture: ${dpkgArch}"; exit 1 ;; \
|
||||
esac; \
|
||||
wget "https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${rustArch}/rustup-init"; \
|
||||
|
||||
@@ -5,7 +5,7 @@ ENV LANG='C.UTF-8' \
|
||||
LC_ALL='C.UTF-8' \
|
||||
JAVA_HOME="/opt/jdk" \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
NODE_VERSION=v22.19.0 \
|
||||
NODE_VERSION=v22.21.1 \
|
||||
TZ=Etc/UTC
|
||||
|
||||
RUN set -ex; \
|
||||
@@ -46,12 +46,12 @@ RUN set -eux; \
|
||||
ARCH="$(dpkg --print-architecture)"; \
|
||||
case "${ARCH}" in \
|
||||
aarch64|arm64) \
|
||||
ESUM='6f8725d186d05c627176db9c46c732a6ef3ba41d9e9b3775c4727fc8ac642bb2'; \
|
||||
BINARY_URL='https://github.com/adoptium/temurin24-binaries/releases/download/jdk-24.0.2%2B12/OpenJDK24U-jdk_aarch64_linux_hotspot_24.0.2_12.tar.gz'; \
|
||||
ESUM='8c5321f16d9f1d8149f83e4e9ff8ca5d9e94320b92d205e6db42a604de3d1140'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.30.17-ca-jdk25.0.1-linux_aarch64.tar.gz'; \
|
||||
;; \
|
||||
amd64|x86_64) \
|
||||
ESUM='aea1cc55e51cf651c85f2f00ad021603fe269c4bb6493fa97a321ad770c9b096'; \
|
||||
BINARY_URL='https://github.com/adoptium/temurin24-binaries/releases/download/jdk-24.0.2%2B12/OpenJDK24U-jdk_x64_linux_hotspot_24.0.2_12.tar.gz'; \
|
||||
ESUM='471b3e62bdffaed27e37005d842d8639f10d244ccce1c7cdebf7abce06c8313e'; \
|
||||
BINARY_URL='https://cdn.azul.com/zulu/bin/zulu25.30.17-ca-jdk25.0.1-linux_x64.tar.gz'; \
|
||||
;; \
|
||||
*) \
|
||||
echo "Unsupported arch: ${ARCH}"; \
|
||||
|
||||
@@ -3,7 +3,7 @@ LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
ENV LANG=en_US.UTF-8 \
|
||||
LC_ALL=en_US.UTF-8 \
|
||||
NODE_VERSION=v22.19.0 \
|
||||
NODE_VERSION=v22.21.1 \
|
||||
DEBIAN_FRONTEND=noninteractive \
|
||||
PATH=/opt/node/bin:$PATH
|
||||
|
||||
|
||||
20
docker/images/Dockerfile.storybook
Normal file
20
docker/images/Dockerfile.storybook
Normal file
@@ -0,0 +1,20 @@
|
||||
FROM nginxinc/nginx-unprivileged:1.29.1
|
||||
LABEL maintainer="Penpot <docker@penpot.app>"
|
||||
|
||||
USER root
|
||||
|
||||
RUN set -ex; \
|
||||
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot;
|
||||
|
||||
ARG BUNDLE_PATH="./bundle-storybook/"
|
||||
COPY $BUNDLE_PATH /var/www/
|
||||
COPY ./files/nginx.storybook.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
RUN chown -R 1001:0 /var/cache/nginx; \
|
||||
chmod -R g+w /var/cache/nginx; \
|
||||
chown -R 1001:0 /etc/nginx; \
|
||||
chmod -R g+w /etc/nginx; \
|
||||
chown -R 1001:0 /var/www; \
|
||||
chmod -R g+w /var/www;
|
||||
|
||||
USER penpot:penpot
|
||||
@@ -247,6 +247,11 @@ services:
|
||||
networks:
|
||||
- penpot
|
||||
|
||||
environment:
|
||||
# You can increase the max memory size if you have sufficient resources,
|
||||
# although this should not be necessary.
|
||||
- VALKEY_EXTRA_FLAGS=--maxmemory 128mb --maxmemory-policy volatile-lfu
|
||||
|
||||
## A mailcatch service, used as temporal SMTP server. You can access via HTTP to the
|
||||
## port 1080 for read all emails the penpot platform has sent. Should be only used as a
|
||||
## temporal solution while no real SMTP provider is configured.
|
||||
|
||||
27
docker/images/files/nginx.storybook.conf
Normal file
27
docker/images/files/nginx.storybook.conf
Normal file
@@ -0,0 +1,27 @@
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
charset utf-8;
|
||||
etag off;
|
||||
|
||||
gzip on;
|
||||
gzip_static on;
|
||||
gzip_types text/plain text/css application/javascript application/json application/vnd.api+json application/xml application/x-javascript text/xml image/svg+xml;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_vary on;
|
||||
|
||||
error_log /dev/stderr;
|
||||
access_log /dev/stdout;
|
||||
|
||||
root /var/www;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
27
docker/images/nginx.storybook.conf
Normal file
27
docker/images/nginx.storybook.conf
Normal file
@@ -0,0 +1,27 @@
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
server_name _;
|
||||
|
||||
charset utf-8;
|
||||
etag off;
|
||||
|
||||
gzip on;
|
||||
gzip_static on;
|
||||
gzip_types text/plain text/css application/javascript application/json application/vnd.api+json application/xml application/x-javascript text/xml image/svg+xml;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_vary on;
|
||||
|
||||
error_log /dev/stderr;
|
||||
access_log /dev/stdout;
|
||||
|
||||
root /var/www;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,9 @@ module.exports = function(eleventyConfig) {
|
||||
eleventyConfig.addPassthroughCopy("css");
|
||||
eleventyConfig.addPassthroughCopy("js");
|
||||
|
||||
// Redirects (for Cloudflare)
|
||||
eleventyConfig.addPassthroughCopy({"_redirects": "_redirects" });
|
||||
|
||||
/* Markdown Overrides */
|
||||
let markdownLibrary = markdownIt({
|
||||
html: true,
|
||||
|
||||
@@ -4,7 +4,7 @@ templateClass: tmpl-user-guide
|
||||
---
|
||||
|
||||
{%- macro show_children(item) -%}
|
||||
{%- for child in item | children | sorted('data.title') %}
|
||||
{%- for child in item | children | sorted('data.order') %}
|
||||
{%- if loop.first -%}<ul>{%- endif -%}
|
||||
<li>
|
||||
<a href="{{ child.url }}">{{ child.data.title }}</a>
|
||||
|
||||
239
docs/_redirects
Normal file
239
docs/_redirects
Normal file
@@ -0,0 +1,239 @@
|
||||
/user-guide/introduction/ /user-guide/first-steps/
|
||||
/user-guide/introduction/quickstart/ /user-guide/first-steps/cloud-selfhost/
|
||||
/user-guide/introduction/shortcuts/ /user-guide/first-steps/shortcuts/
|
||||
/user-guide/introduction/shortcuts/#workspace-section /user-guide/first-steps/shortcuts/#workspace-section
|
||||
/user-guide/introduction/shortcuts/#alignment /user-guide/first-steps/shortcuts/#alignment
|
||||
/user-guide/introduction/shortcuts/#edit /user-guide/first-steps/shortcuts/#edit
|
||||
/user-guide/introduction/shortcuts/#main-menu /user-guide/first-steps/shortcuts/#main-menu
|
||||
/user-guide/introduction/shortcuts/#modify-layers /user-guide/first-steps/shortcuts/#modify-layers
|
||||
/user-guide/introduction/shortcuts/#goto-screens-workspace /user-guide/first-steps/shortcuts/#goto-screens-workspace
|
||||
/user-guide/introduction/shortcuts/#panels /user-guide/first-steps/shortcuts/#panels
|
||||
/user-guide/introduction/shortcuts/#path-editor /user-guide/first-steps/shortcuts/#path-editor
|
||||
/user-guide/introduction/shortcuts/#shapes /user-guide/first-steps/shortcuts/#shapes
|
||||
/user-guide/introduction/shortcuts/#tools /user-guide/first-steps/shortcuts/#tools
|
||||
/user-guide/introduction/shortcuts/#zoom-workspace /user-guide/first-steps/shortcuts/#zoom-workspace
|
||||
/user-guide/introduction/shortcuts/#text /user-guide/first-steps/shortcuts/#text
|
||||
/user-guide/introduction/shortcuts/#dashboard-section /user-guide/first-steps/shortcuts/#dashboard-section
|
||||
/user-guide/introduction/shortcuts/#generic-dashboard /user-guide/first-steps/shortcuts/#generic-dashboard
|
||||
/user-guide/introduction/shortcuts/#navigation-dashboard /user-guide/first-steps/shortcuts/#navigation-dashboard
|
||||
/user-guide/introduction/shortcuts/#viewer-section /user-guide/first-steps/shortcuts/#viewer-section
|
||||
/user-guide/introduction/shortcuts/#generic-viewer /user-guide/first-steps/shortcuts/#generic-viewer
|
||||
/user-guide/introduction/shortcuts/#navigation-viewer /user-guide/first-steps/shortcuts/#navigation-viewer
|
||||
/user-guide/introduction/shortcuts/#zoom-viewer /user-guide/first-steps/shortcuts/#zoom-viewer
|
||||
/user-guide/introduction/info/ /user-guide/first-steps/info/
|
||||
/user-guide/introduction/info/#dev-diaries /user-guide/first-steps/info/
|
||||
/user-guide/introduction/info/#video-tutorials /user-guide/first-steps/info/
|
||||
/user-guide/introduction/info/#faqs /user-guide/first-steps/info/
|
||||
/user-guide/the-interface/ /user-guide/first-steps/the-interface/
|
||||
/user-guide/the-interface/#interface-workspace /user-guide/first-steps/the-interface/#interface-workspace
|
||||
/user-guide/the-interface/#interface-viewmode /user-guide/first-steps/the-interface/#interface-viewmode
|
||||
/user-guide/the-interface/#interface-dashboard /user-guide/first-steps/the-interface/#interface-dashboard
|
||||
/user-guide/the-interface/#your-account /user-guide/account-teams/your-account/
|
||||
/user-guide/the-interface/#interface-ui-theme /user-guide/account-teams/your-account/#interface-ui-theme
|
||||
/user-guide/workspace-basics/ /user-guide/designing/workspace-basics/
|
||||
/user-guide/workspace-basics/#viewport /user-guide/designing/workspace-basics/#viewport
|
||||
/user-guide/workspace-basics/#workspace-menu /user-guide/designing/workspace-basics/#workspace-menu
|
||||
/user-guide/workspace-basics/#zoom /user-guide/designing/workspace-basics/#zoom
|
||||
/user-guide/workspace-basics/#dynamic-alignment /user-guide/designing/workspace-basics/#dynamic-alignment
|
||||
/user-guide/workspace-basics/#rulers /user-guide/designing/workspace-basics/#rulers
|
||||
/user-guide/workspace-basics/#ruler-guides /user-guide/designing/workspace-basics/#ruler-guides
|
||||
/user-guide/workspace-basics/#guides /user-guide/designing/workspace-basics/#guides
|
||||
/user-guide/workspace-basics/#add-guides /user-guide/designing/workspace-basics/#add-guides
|
||||
/user-guide/workspace-basics/#hide-remove-guides /user-guide/designing/workspace-basics/#hide-remove-guides
|
||||
/user-guide/workspace-basics/#square-guides /user-guide/designing/workspace-basics/#square-guides
|
||||
/user-guide/workspace-basics/#row-guides /user-guide/designing/workspace-basics/#row-guides
|
||||
/user-guide/workspace-basics/#column-guides /user-guide/designing/workspace-basics/#column-guides
|
||||
/user-guide/workspace-basics/#guides-defaults /user-guide/designing/workspace-basics/#guides-defaults
|
||||
/user-guide/workspace-basics/#guides-visibility /user-guide/designing/workspace-basics/#guides-visibility
|
||||
/user-guide/workspace-basics/#guides-snap /user-guide/designing/workspace-basics/#guides-snap
|
||||
/user-guide/workspace-basics/#snap-to-pixel /user-guide/designing/workspace-basics/#snap-to-pixel
|
||||
/user-guide/workspace-basics/#nudge-amount /user-guide/designing/workspace-basics/#nudge-amount
|
||||
/user-guide/workspace-basics/#shortcuts-panel /user-guide/designing/workspace-basics/#shortcuts-panel
|
||||
/user-guide/workspace-basics/#history /user-guide/designing/workspace-basics/#history
|
||||
/user-guide/workspace-basics/#comments /user-guide/designing/workspace-basics/#comments
|
||||
/user-guide/layer-basics/ /user-guide/designing/layers/
|
||||
/user-guide/layer-basics/#pages /user-guide/designing/workspace-basics/#layer-basics
|
||||
/user-guide/layer-basics/#layers-panel /user-guide/layer-basics/#layers-panel
|
||||
/user-guide/layer-basics/#hide-lock /user-guide/designing/layers/#hide-lock
|
||||
/user-guide/layer-basics/#creating-layers /user-guide/designing/layers/#creating-layers
|
||||
/user-guide/layer-basics/#duplicating-layers /user-guide/designing/layers/#duplicating-layers
|
||||
/user-guide/layer-basics/#delete-layers /user-guide/designing/layers/#delete-layers
|
||||
/user-guide/layer-basics/#select-layers /user-guide/designing/layers/#select-layers
|
||||
/user-guide/layer-basics/#group-layers /user-guide/designing/layers/#group-layers
|
||||
/user-guide/layer-basics/#mask-layers /user-guide/designing/layers/#mask-layers
|
||||
/user-guide/layer-basics/#move-layers /user-guide/designing/layers/#move-layers
|
||||
/user-guide/layer-basics/#resize-layers /user-guide/designing/layers/#resize-layers
|
||||
/user-guide/layer-basics/#rotate-layers /user-guide/designing/layers/#rotate-layers
|
||||
/user-guide/layer-basics/#flip-layers /user-guide/designing/layers/#flip-layers
|
||||
/user-guide/layer-basics/#scale-elements /user-guide/designing/layers/#scale-elements
|
||||
/user-guide/layer-basics/#aling-distribute-layers /user-guide/designing/layers/#aling-distribute-layers
|
||||
/user-guide/layer-basics/#layers-search /user-guide/designing/workspace-basics/#layer-basics
|
||||
/user-guide/layer-basics/#collapse-groups /user-guide/designing/workspace-basics/#layer-basics
|
||||
/user-guide/layer-basics/#boolean-operators /user-guide/designing/layers/#boolean-operators
|
||||
/user-guide/layer-basics/#constraints /user-guide/designing/layers/#constraints
|
||||
/user-guide/layer-basics/#focus-mode /user-guide/designing/workspace-basics/#focus-mode
|
||||
/user-guide/layer-basics/#rtl-support /user-guide/designing/text-typo/#rtl-support
|
||||
/user-guide/objects/ /user-guide/designing/layers/
|
||||
/user-guide/objects/#Boards /user-guide/designing/layers/#Boards
|
||||
/user-guide/objects/#rectangles-ellipses /user-guide/designing/layers/#rectangles-ellipses
|
||||
/user-guide/objects/#text /user-guide/designing/layers/#text
|
||||
/user-guide/objects/#curves /user-guide/designing/layers/#curves
|
||||
/user-guide/objects/#paths /user-guide/designing/layers/#paths
|
||||
/user-guide/objects/#images /user-guide/designing/layers/#images
|
||||
/user-guide/styling/ /user-guide/designing/layers/#styling-layers
|
||||
/user-guide/styling/#fill /user-guide/designing/color-stroke/#fill
|
||||
/user-guide/styling/#color-picker /user-guide/designing/color-stroke/#color-picker
|
||||
/user-guide/styling/#color-picker-gradients /user-guide/designing/color-stroke/#color-picker-gradients
|
||||
/user-guide/styling/#color-palette /user-guide/designing/color-stroke/#color-palette
|
||||
/user-guide/styling/#selected-colors /user-guide/designing/color-stroke/#selected-colors
|
||||
/user-guide/styling/#strokes /user-guide/designing/color-stroke/#strokes
|
||||
/user-guide/styling/#stroke-caps /user-guide/designing/color-stroke/#stroke-caps
|
||||
/user-guide/styling/#radius /user-guide/designing/layers/#radius
|
||||
/user-guide/styling/#shadow /user-guide/designing/layers/#shadow
|
||||
/user-guide/styling/#blur /user-guide/designing/layers/#blur
|
||||
/user-guide/styling/#blend /user-guide/designing/layers/#blend
|
||||
/user-guide/styling/#copy-paste-properties /user-guide/designing/layers/#copy-paste-properties
|
||||
/user-guide/exporting/ /user-guide/export-import/exporting-layers/
|
||||
/user-guide/exporting/#export-howto /user-guide/export-import/exporting-layers/#export-howto
|
||||
/user-guide/exporting/#export-options /user-guide/export-import/exporting-layers/#export-options
|
||||
/user-guide/exporting/#export-multiple-elements /user-guide/export-import/exporting-layers/#export-multiple-elements
|
||||
/user-guide/exporting/#export-artboards-pdf /user-guide/export-import/exporting-layers/#export-artboards-pdf
|
||||
/user-guide/exporting/#export-technical /user-guide/export-import/exporting-layers/#export-technical
|
||||
/user-guide/flexible-layouts/ /user-guide/designing/flexible-layouts/
|
||||
/user-guide/flexible-layouts/#layouts-flex /user-guide/designing/flexible-layouts/#layouts-flex
|
||||
/user-guide/flexible-layouts/#layouts-flex-css /user-guide/designing/flexible-layouts/#layouts-flex
|
||||
/user-guide/flexible-layouts/#layouts-flex-add /user-guide/designing/flexible-layouts/#layouts-flex-add
|
||||
/user-guide/flexible-layouts/#layouts-flex-arrange-reorder /user-guide/designing/flexible-layouts/#layouts-flex-arrange-reorder
|
||||
/user-guide/flexible-layouts/#layouts-flex-properties /user-guide/designing/flexible-layouts/#layouts-flex-properties
|
||||
/user-guide/flexible-layouts/#layouts-flex-elements /user-guide/designing/flexible-layouts/#layouts-flex-elements
|
||||
/user-guide/flexible-layouts/#layouts-flex-spacing /user-guide/designing/flexible-layouts/#layouts-flex-spacing
|
||||
/user-guide/flexible-layouts/#layouts-flex-code /user-guide/designing/flexible-layouts/#layouts-flex-code
|
||||
/user-guide/flexible-layouts/#layouts-flex-examples /user-guide/designing/flexible-layouts/#layouts-flex-examples
|
||||
/user-guide/flexible-layouts/#layouts-grid /user-guide/designing/flexible-layouts/#layouts-grid
|
||||
/user-guide/flexible-layouts/#layouts-flex-css /user-guide/designing/flexible-layouts/#layouts-grid
|
||||
/user-guide/flexible-layouts/#layouts-grid-add /user-guide/designing/flexible-layouts/#layouts-grid-add
|
||||
/user-guide/flexible-layouts/#layouts-grid-terminology /user-guide/designing/flexible-layouts/#layouts-grid-terminology
|
||||
/user-guide/flexible-layouts/#layouts-grid-properties /user-guide/designing/flexible-layouts/#layouts-grid-properties
|
||||
/user-guide/flexible-layouts/#layouts-grid-elements /user-guide/designing/flexible-layouts/#layouts-grid-elements
|
||||
/user-guide/flexible-layouts/#layouts-grid-colsrows /user-guide/designing/flexible-layouts/#layouts-grid-colsrows
|
||||
/user-guide/flexible-layouts/#layouts-grid-units /user-guide/designing/flexible-layouts/#layouts-grid-units
|
||||
/user-guide/flexible-layouts/#layouts-grid-areas /user-guide/designing/flexible-layouts/#layouts-grid-areas
|
||||
/user-guide/flexible-layouts/#layouts-grid-code /user-guide/designing/flexible-layouts/#layouts-grid-code
|
||||
/user-guide/libraries/ /user-guide/design-systems/assets/
|
||||
/user-guide/libraries/#assets /user-guide/design-systems/assets/
|
||||
/user-guide/libraries/#asset-types /user-guide/design-systems/assets/#asset-types
|
||||
/user-guide/libraries/#add-assets-to-library /user-guide/design-systems/assets/#add-assets-to-library
|
||||
/user-guide/libraries/#edit-assets /user-guide/design-systems/assets/#edit-assets
|
||||
/user-guide/libraries/#use-assets /user-guide/design-systems/assets/#use-assets
|
||||
/user-guide/libraries/#organize-assets /user-guide/design-systems/assets/#organize-assets
|
||||
/user-guide/libraries/#libraries /user-guide/design-systems/libraries/
|
||||
/user-guide/libraries/#file-libraries /user-guide/design-systems/libraries/#file-libraries
|
||||
/user-guide/libraries/#shared-libraries /user-guide/design-systems/libraries/#shared-libraries
|
||||
/user-guide/design-tokens/ /user-guide/design-systems/design-tokens/
|
||||
/user-guide/design-tokens/#design-tokens-why /user-guide/design-systems/design-tokens/
|
||||
/user-guide/design-tokens/#design-tokens-format /user-guide/design-systems/design-tokens/
|
||||
/user-guide/design-tokens/#design-tokens-use /user-guide/design-systems/design-tokens/#design-tokens-use-create
|
||||
/user-guide/design-tokens/#design-tokens-use-create /user-guide/design-systems/design-tokens/#design-tokens-use-create
|
||||
/user-guide/design-tokens/#design-tokens-aliases /user-guide/design-systems/design-tokens/#design-tokens-aliases
|
||||
/user-guide/design-tokens/#design-tokens-equations /user-guide/design-systems/design-tokens/#design-tokens-equations
|
||||
/user-guide/design-tokens/#design-tokens-edit /user-guide/design-systems/design-tokens/#design-tokens-edit
|
||||
/user-guide/design-tokens/#design-tokens-duplicate /user-guide/design-systems/design-tokens/#design-tokens-duplicate
|
||||
/user-guide/design-tokens/#design-tokens-delete /user-guide/design-systems/design-tokens/#design-tokens-delete
|
||||
/user-guide/design-tokens/#design-tokens-available /user-guide/design-systems/design-tokens/#design-tokens-available
|
||||
/user-guide/design-tokens/#design-tokens-radius /user-guide/design-systems/design-tokens/#design-tokens-radius
|
||||
/user-guide/design-tokens/#design-tokens-color /user-guide/design-systems/design-tokens/#design-tokens-color
|
||||
/user-guide/design-tokens/#design-tokens-dimensions /user-guide/design-systems/design-tokens/#design-tokens-dimensions
|
||||
/user-guide/design-tokens/#design-tokens-opacity /user-guide/design-systems/design-tokens/#design-tokens-opacity
|
||||
/user-guide/design-tokens/#design-tokens-rotation /user-guide/design-systems/design-tokens/#design-tokens-rotation
|
||||
/user-guide/design-tokens/#design-tokens-sizing /user-guide/design-systems/design-tokens/#design-tokens-sizing
|
||||
/user-guide/design-tokens/#design-tokens-spacing /user-guide/design-systems/design-tokens/#design-tokens-spacing
|
||||
/user-guide/design-tokens/#design-tokens-stroke-width /user-guide/design-systems/design-tokens/#design-tokens-stroke-width
|
||||
/user-guide/design-tokens/#design-tokens-number /user-guide/design-systems/design-tokens/#design-tokens-number
|
||||
/user-guide/design-tokens/#design-tokens-typography /user-guide/design-systems/design-tokens/#design-tokens-typography
|
||||
/user-guide/design-tokens/#design-tokens-sets /user-guide/design-systems/design-tokens/#design-tokens-sets
|
||||
/user-guide/design-tokens/#design-tokens-sets-create /user-guide/design-systems/design-tokens/#design-tokens-sets
|
||||
/user-guide/design-tokens/#design-tokens-sets-edit /user-guide/design-systems/design-tokens/#design-tokens-sets
|
||||
/user-guide/design-tokens/#design-tokens-groups /user-guide/design-systems/design-tokens/#design-tokens-sets
|
||||
/user-guide/design-tokens/#design-tokens-themes /user-guide/design-systems/design-tokens/#design-tokens-themes
|
||||
/user-guide/design-tokens/#design-tokens-themes-create /user-guide/design-systems/design-tokens/#design-tokens-themes
|
||||
/user-guide/design-tokens/#design-tokens-themes-edit /user-guide/design-systems/design-tokens/#design-tokens-themes
|
||||
/user-guide/design-tokens/#design-tokens-themes-group /user-guide/design-systems/design-tokens/#design-tokens-themes
|
||||
/user-guide/design-tokens/#design-tokens-import-export /user-guide/design-systems/design-tokens/#design-tokens-import-export
|
||||
/user-guide/design-tokens/#design-tokens-import-options /user-guide/design-systems/design-tokens/#design-tokens-import-export
|
||||
/user-guide/design-tokens/#design-tokens-export-options /user-guide/design-systems/design-tokens/#design-tokens-import-export
|
||||
/user-guide/components/ /user-guide/design-systems/components/
|
||||
/user-guide/components/#components-basics /user-guide/design-systems/components/
|
||||
/user-guide/components/#component-create /user-guide/design-systems/components/#component-create
|
||||
/user-guide/components/#component-find /user-guide/design-systems/components/#component-find
|
||||
/user-guide/components/#component-main-components-page /user-guide/design-systems/components/#component-main-components-page
|
||||
/user-guide/components/#working-with-components /user-guide/design-systems/components/#component-group
|
||||
/user-guide/components/#component-group /user-guide/design-systems/components/#component-group
|
||||
/user-guide/components/#component-detach /user-guide/design-systems/components/#component-detach
|
||||
/user-guide/components/#component-annotate /user-guide/design-systems/components/#component-annotate
|
||||
/user-guide/components/#component-overrides-relationships /user-guide/design-systems/components/#component-overrides
|
||||
/user-guide/components/#component-overrides /user-guide/design-systems/components/#component-overrides
|
||||
/user-guide/components/#component-update /user-guide/design-systems/components/#component-update
|
||||
/user-guide/components/#component-swap /user-guide/design-systems/components/#component-swap
|
||||
/user-guide/components/#component-variants /user-guide/design-systems/variants/
|
||||
/user-guide/components/#component-variants-why-are-variants-important /user-guide/design-systems/variants/#component-variants-why-are-variants-important
|
||||
/user-guide/components/#component-understanding-variants-properties-and-values /user-guide/design-systems/variants/#component-understanding-variants-properties-and-values
|
||||
/user-guide/components/#component-create-and-modify-variants /user-guide/design-systems/variants/#component-create-and-modify-variants
|
||||
/user-guide/components/#component-use-variants /user-guide/design-systems/variants/#component-use-variants
|
||||
/user-guide/prototyping/ /user-guide/prototyping-testing/prototyping/
|
||||
/user-guide/prototyping/#prototyping-connection /user-guide/prototyping-testing/prototyping/#prototyping-connection
|
||||
/user-guide/prototyping/#prototype-anatomy /user-guide/prototyping-testing/prototyping/#prototype-anatomy
|
||||
/user-guide/prototyping/#interaction-triggers /user-guide/prototyping-testing/prototyping/#interaction-triggers
|
||||
/user-guide/prototyping/#prototyping-actions /user-guide/prototyping-testing/prototyping/#prototyping-actions
|
||||
/user-guide/prototyping/#prototyping-actions-navigate /user-guide/prototyping-testing/prototyping/#prototyping-actions-navigate
|
||||
/user-guide/prototyping/#prototyping-actions-overlay /user-guide/prototyping-testing/prototyping/#prototyping-actions-overlay
|
||||
/user-guide/prototyping/#prototyping-actions-overlay-toggle /user-guide/prototyping-testing/prototyping/#prototyping-actions-overlay-toggle
|
||||
/user-guide/prototyping/#prototyping-actions-overlay-close /user-guide/prototyping-testing/prototyping/#prototyping-actions-overlay-close
|
||||
/user-guide/prototyping/#prototyping-actions-previous /user-guide/prototyping-testing/prototyping/#prototyping-actions-previous
|
||||
/user-guide/prototyping/#prototyping-actions-url /user-guide/prototyping-testing/prototyping/#prototyping-actions-url
|
||||
/user-guide/prototyping/#prototyping-animations /user-guide/prototyping-testing/prototyping/#prototyping-animations
|
||||
/user-guide/prototyping/#prototyping-animations-dissolve /user-guide/prototyping-testing/prototyping/#prototyping-animations-dissolve
|
||||
/user-guide/prototyping/#prototyping-animations-Slide /user-guide/prototyping-testing/prototyping/#prototyping-animations-Slide
|
||||
/user-guide/prototyping/#prototyping-animations-push /user-guide/prototyping-testing/prototyping/#prototyping-animations-push
|
||||
/user-guide/prototyping/#prototyping-flows /user-guide/prototyping-testing/prototyping/#prototyping-flows
|
||||
/user-guide/prototyping/#prototyping-flows-starting /user-guide/prototyping-testing/prototyping/#prototyping-flows-starting
|
||||
/user-guide/prototyping/#prototyping-flows-multiple /user-guide/prototyping-testing/prototyping/#prototyping-flows-multiple
|
||||
/user-guide/prototyping/#prototyping-fix-scroll /user-guide/prototyping-testing/prototyping/#prototyping-fix-scroll
|
||||
/user-guide/view-mode/ /user-guide/prototyping-testing/testing-view-mode/
|
||||
/user-guide/view-mode/#viewmode-interface /user-guide/prototyping-testing/testing-view-mode/#viewmode-interface
|
||||
/user-guide/view-mode/#viewmode-launch /user-guide/prototyping-testing/testing-view-mode/#viewmode-launch
|
||||
/user-guide/view-mode/#viewmode-features /user-guide/prototyping-testing/testing-view-mode/#viewmode-features
|
||||
/user-guide/view-mode/#viewmode-comments /user-guide/prototyping-testing/testing-view-mode/#viewmode-comments
|
||||
/user-guide/view-mode/#viewmode-sharing /user-guide/prototyping-testing/testing-view-mode/#viewmode-sharing
|
||||
/user-guide/view-mode/#viewmode-inspect /user-guide/prototyping-testing/testing-view-mode/#viewmode-inspect
|
||||
/user-guide/inspect/ /user-guide/dev-tools/
|
||||
/user-guide/inspect/#inspect-activate /user-guide/dev-tools/#inspect-design
|
||||
/user-guide/inspect/#inspect-viewmode /user-guide/dev-tools/#inspect-design
|
||||
/user-guide/inspect/#inspect-workspace /user-guide/dev-tools/#inspect-design
|
||||
/user-guide/inspect/#inspect-measure /user-guide/dev-tools/#inspect-measure
|
||||
/user-guide/inspect/#inspect-info /user-guide/dev-tools/#inspect-info
|
||||
/user-guide/inspect/#inspect-copy /user-guide/dev-tools/#inspect-copy
|
||||
/user-guide/inspect/#inspect-code /user-guide/dev-tools/#inspect-code
|
||||
/user-guide/inspect/#inspect-export /user-guide/dev-tools/#inspect-export
|
||||
/user-guide/import-export/ /user-guide/export-import/export-import-files/
|
||||
/user-guide/import-export/#files-export /user-guide/export-import/export-import-files/#files-export
|
||||
/user-guide/import-export/#export-penpot-files /user-guide/export-import/export-import-files/#files-export
|
||||
/user-guide/import-export/#files-import /user-guide/export-import/export-import-files/#files-import
|
||||
/user-guide/import-export/#penpot-formats /user-guide/export-import/export-import-files/#penpot-formats
|
||||
/user-guide/teams/ /user-guide/account-teams/teams/
|
||||
/user-guide/teams/#teams-management /user-guide/account-teams/teams/#teams-management
|
||||
/user-guide/teams/#teams-members /user-guide/account-teams/teams/#teams-members
|
||||
/user-guide/teams/#teams-invites /user-guide/account-teams/teams/#teams-invites
|
||||
/user-guide/teams/#teams-webhooks /user-guide/plugins-integrations/#teams-webhooks
|
||||
/user-guide/custom-fonts/ /user-guide/designing/text-typo/#custom-fonts
|
||||
/user-guide/custom-fonts/#customfonts-upload /user-guide/designing/text-typo/#customfonts-upload
|
||||
/user-guide/custom-fonts/#customfonts-families /user-guide/designing/text-typo/#customfonts-families
|
||||
/user-guide/custom-fonts/#customfonts-edit /user-guide/designing/text-typo/#customfonts-edit
|
||||
/user-guide/custom-fonts/#customfonts-using /user-guide/designing/text-typo/#customfonts-using
|
||||
/user-guide/plugins/ /user-guide/plugins-integrations/
|
||||
/user-guide/plugins/#plugins /user-guide/plugins-integrations/
|
||||
/user-guide/plugins/#installation /user-guide/plugins-integrations/#installation
|
||||
/user-guide/plugins/#hub-installation /user-guide/plugins-integrations/#installation
|
||||
/user-guide/plugins/#url-installation /user-guide/plugins-integrations/#installation
|
||||
/user-guide/plugins/#plugin-manager /user-guide/plugins-integrations/#plugin-manager
|
||||
/user-guide/plugins/#using-plugins /user-guide/plugins-integrations/#using-plugins
|
||||
/user-guide/plugins/#create-plugin /user-guide/plugins-integrations/#create-plugin
|
||||
BIN
docs/img/design-tokens/34-tokens-composite-typography-alias.webp
Normal file
BIN
docs/img/design-tokens/34-tokens-composite-typography-alias.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
BIN
docs/img/design-tokens/36-tokens-composite-typography.webp
Normal file
BIN
docs/img/design-tokens/36-tokens-composite-typography.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -23,7 +23,7 @@ Flags and evironment variables are also used together; for example:
|
||||
|
||||
```bash
|
||||
# This flag enables the use of SMTP email
|
||||
PENPOT_FLAGS: enable-smtp
|
||||
PENPOT_FLAGS: [...] enable-smtp
|
||||
|
||||
# These environment variables configure the specific SMPT service
|
||||
# Backend
|
||||
@@ -36,7 +36,7 @@ the exporter, or all of them; on the other hand, **environment variables** are c
|
||||
each specific service. For example:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: enable-login-with-google
|
||||
PENPOT_FLAGS: [...] enable-login-with-google
|
||||
|
||||
# Backend
|
||||
PENPOT_GOOGLE_CLIENT_ID: <client-id>
|
||||
@@ -56,7 +56,7 @@ Penpot uses anonymous telemetries from the self-hosted instances to improve the
|
||||
Consider sharing these anonymous telemetries enabling the corresponding flag:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: enable-telemetries
|
||||
PENPOT_FLAGS: [...] enable-telemetries
|
||||
```
|
||||
|
||||
## Registration and authentication
|
||||
@@ -402,7 +402,7 @@ This is implemented as specific locations in the penpot-front Nginx. If your org
|
||||
in a 100% air-gapped environment, you can use the following configuration:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: enable-air-gapped-conf
|
||||
PENPOT_FLAGS: [...] enable-air-gapped-conf
|
||||
```
|
||||
|
||||
When Penpot starts, it will leave out the Nginx configuration related to external requests. This means that,
|
||||
@@ -459,11 +459,15 @@ POSTGRES_PASSWORD: penpot
|
||||
|
||||
### Storage
|
||||
|
||||
Storage refers to storing the user uploaded assets.
|
||||
Storage refers to storing the user uploaded different objects in Penpot (assets, file data,...).
|
||||
|
||||
Assets storage is implemented using "plugable" backends. Currently there are two
|
||||
Objects storage is implemented using "plugable" backends. Currently there are two
|
||||
backends available: <code class="language-bash">fs</code> and <code class="language-bash">s3</code> (for AWS S3).
|
||||
|
||||
__Since version 2.11.0__
|
||||
The configuration variables related to storage has been renamed, `PENPOT_STORAGE_ASSETS_*` are now `PENPOT_OBJECTS_STORAGE_*`.
|
||||
`PENPOT_ASSETS_STORAGE_BACKEND` becomes `PENPOT_OBJECTS_STORAGE_BACKEND` and its values now are `fs` and `s3` instead of `assets-fs` or `assets-s3`.
|
||||
|
||||
#### FS Backend (default)
|
||||
|
||||
This is the default backend when you use the official docker images and the default
|
||||
@@ -471,8 +475,8 @@ configuration looks like this:
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
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/objects
|
||||
```
|
||||
|
||||
The main downside of this backend is the hard dependency on nginx approach to serve files
|
||||
@@ -485,7 +489,7 @@ configuration file][4] used in the docker images.
|
||||
|
||||
#### AWS S3 Backend
|
||||
|
||||
This backend uses AWS S3 bucket for store the user uploaded assets. For use it you should
|
||||
This backend uses AWS S3 bucket for store the user uploaded objects. For use it you should
|
||||
have an appropriate account on AWS cloud and have the credentials, region and the bucket.
|
||||
|
||||
This is how configuration looks for S3 backend:
|
||||
@@ -494,18 +498,36 @@ This is how configuration looks for S3 backend:
|
||||
# Backend
|
||||
AWS_ACCESS_KEY_ID: <you-access-key-id-here>
|
||||
AWS_SECRET_ACCESS_KEY: <your-secret-access-key-here>
|
||||
PENPOT_ASSETS_STORAGE_BACKEND: assets-s3
|
||||
PENPOT_STORAGE_ASSETS_S3_REGION: <aws-region>
|
||||
PENPOT_STORAGE_ASSETS_S3_BUCKET: <bucket-name>
|
||||
PENPOT_OBJECTS_STORAGE_BACKEND: s3
|
||||
PENPOT_OBJECTS_STORAGE_S3_REGION: <aws-region>
|
||||
PENPOT_OBJECTS_STORAGE_S3_BUCKET: <bucket-name>
|
||||
|
||||
# Optional if you want to use it with non AWS, S3 compatible service:
|
||||
PENPOT_STORAGE_ASSETS_S3_ENDPOINT: <endpoint-uri>
|
||||
PENPOT_OBJECTS_STORAGE_S3_ENDPOINT: <endpoint-uri>
|
||||
```
|
||||
|
||||
<p class="advice">
|
||||
These settings are equally useful if you have a Minio storage system.
|
||||
</p>
|
||||
|
||||
### File Data Storage
|
||||
|
||||
__Since version 2.11.0__
|
||||
|
||||
You can change the default file data storage backend with `PENPOT_FILE_DATA_BACKEND` environment variable. Possible values are:
|
||||
|
||||
- `legacy-db`: the current default backend, continues storing the file data of files and snapshots in the same location as previous versions of Penpot (< 2.11.0), this is a conservative default behaviour and will be changed to `db` in next versions.
|
||||
- `db`: stores the file data on an specific table (the future default backend).
|
||||
- `storage`: stores the file data using the objects storage system (S3 or FS, depending on which one is configured)
|
||||
|
||||
This also comes with an additional feature that allows offload the "inactive" files on file storage backend and leaves the database only for the active files. To enable it, you should use the `enable-tiered-file-data-storage` flag and `db` as file data storage backend.
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
PENPOT_FLAGS: [...] enable-tiered-file-data-storage
|
||||
PENPOT_FILE_DATA_BACKEND: db
|
||||
```
|
||||
|
||||
### Autosave
|
||||
|
||||
By default, Penpot stores manually saved versions indefinitely; these can be found in the History tab and can be renamed, restored, deleted, etc. Additionally, the default behavior of on-premise instances is to not keep automatic version history. This automatic behavior can be modified and adapted to each on-premise installation with the corresponding configuration.
|
||||
@@ -517,7 +539,7 @@ You need to be very careful when configuring automatic versioning, as it can sig
|
||||
This is how configuration looks for auto-file-snapshot
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: enable-auto-file-snapshot # Enable automatic version saving
|
||||
PENPOT_FLAGS: [...] enable-auto-file-snapshot # Enable automatic version saving
|
||||
|
||||
# Backend
|
||||
PENPOT_AUTO_FILE_SNAPSHOT_EVERY: 5 # How many save operations trigger the auto-save-version?
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 1.1 Recommended storage
|
||||
title: 1.1 Recommended settings
|
||||
desc: Learn recommended self-hosting settings, Docker & Kubernetes installs, configuration, and troubleshooting tips in Penpot's technical guide.
|
||||
---
|
||||
|
||||
@@ -10,3 +10,33 @@ Disk requirements depend on your usage, with the primary factors being database
|
||||
As a rule of thumb, start with a **minimum** database size of **50GB** to **100GB** with elastic sizing capability — this configuration should adequately support up to 10 editors. For environments with **more than 10 users**, we recommend adding approximately **5GB** of capacity per additional editor.
|
||||
|
||||
Keep in mind that database size doesn't grow strictly proportionally with user count, as it depends heavily on how Penpot is used and the complexity of files created. Most organizations begin with this baseline and elastic sizing approach, then monitor usage patterns monthly until resource requirements stabilize.
|
||||
|
||||
|
||||
# About Valkey / Redis requirements
|
||||
|
||||
"Valkey is mainly used for coordinating websocket notifications and, since Penpot 2.11, as a cache. Therefore, disk storage will not be necessary as it will use the instance's RAM.
|
||||
|
||||
To prevent the cache from hogging all the system's RAM usage, it is recommended to use two configuration parameters which, both in the docker-compose.yaml provided by Penpot and in the official Helm Chart, come with default parameters that should be sufficient for most deployments:
|
||||
|
||||
```bash
|
||||
## Recommended values for most Penpot instances.
|
||||
## You can modify this value to follow your policies.
|
||||
|
||||
# Set maximum memory Valkey/Redis will use.
|
||||
# Accepted units: b, k, kb, m, mb, g, gb
|
||||
maxmemory 128mb
|
||||
|
||||
# Choose an eviction policy (see Valkey docs:
|
||||
# https://valkey.io/topics/memory-optimization/ or for Redis
|
||||
# https://redis.io/docs/latest/develop/reference/eviction/
|
||||
# Common choices:
|
||||
# noeviction, allkeys-lru, volatile-lru, allkeys-random, volatile-random,
|
||||
# volatile-ttl, volatile-lfu, allkeys-lfu
|
||||
#
|
||||
# For Penpot, volatile-lfu is recommended
|
||||
maxmemory-policy volatile-lfu
|
||||
```
|
||||
|
||||
The `maxmemory` configuration directive specifies the maximum amount of memory to use for the cache data. If you are using a dedicated instance to host Valkey/Redis, we do not recommend using more than 60% of the available RAM.
|
||||
|
||||
With `maxmemory-policy` configuration directive, you can select the eviction policy you want to use when the limit set by `maxmemory` is reached. Penpot works fine with `volatile-lfu`, which evicts the least frequently used keys that have been marked as expired.
|
||||
|
||||
14
docs/user-guide/account-teams/comments.njk
Normal file
14
docs/user-guide/account-teams/comments.njk
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Comments
|
||||
order: 4
|
||||
desc: Learn how to import and export files in Penpot, the free, open-source design tool. Discover file formats, backups, sharing, and library management.
|
||||
---
|
||||
|
||||
<h1 id="comments">Comments</h1>
|
||||
<p class="main-paragraph">Comments allow the team to have one priceless conversation getting and providing feedback right over the designs and prototypes.<p>
|
||||
|
||||
<h2 id="comment-workspace">At the workspace</h2>
|
||||
<p>At the workspace, activate the comment tool by clicking the comment icon in the navbar or pressing the <kbd>C</kbd> key. <a href="/user-guide/designing/workspace-basics/#comments">More about comments at the Workspace</a></p>
|
||||
|
||||
<h2 id="comment-viewmode">At the View mode</h2>
|
||||
<p>You can activate comments at the View mode by pressing the comments icon at the top navbar. <a href="/user-guide/prototyping-testing/testing-view-mode/#viewmode-comments">More about comments at the View mode</a>.</p>
|
||||
28
docs/user-guide/account-teams/index.njk
Normal file
28
docs/user-guide/account-teams/index.njk
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Account & teams
|
||||
order: 8
|
||||
desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorials. Learn the interface, layers, objects, styling, and more.
|
||||
---
|
||||
|
||||
<h1 id="section-1">Account & teams</h1>
|
||||
|
||||
<ul class="intro-sections">
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/your-account">
|
||||
<h2>Your account →</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/teams">
|
||||
<h2>Teams →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/account-teams/comments/">
|
||||
<h2>Comments →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 16· Teams
|
||||
title: Teams
|
||||
order: 2
|
||||
desc: Manage teams and roles with Penpot's collaboration features! Learn how to manage teams, roles (Viewer, Editor, Admin, Owner), send invites and use webhooks.
|
||||
---
|
||||
|
||||
@@ -65,9 +66,4 @@ member is allowed to do depends on their permissions.</p>
|
||||
</ul>
|
||||
<figure><img src="/img/teams/team-invitations-actions.webp" alt="Team invitations actions" /></figure>
|
||||
|
||||
<h2 id="teams-webhooks">Webhooks</h2>
|
||||
|
||||
<p>Webhooks allow other websites and apps to be notified when certain events happen on Penpot, ensuring to create integrations with other services. While we are still working on a plugin system, this is a clever and simple way to create integrations with other services.</p>
|
||||
<figure><img src="/img/teams/webhooks.webp" alt="Webhooks" /></figure>
|
||||
|
||||
<p>You can find detailed info about Penpot webhooks at the <a href="/technical-guide/integration/#webhooks">Technical Guide</a>.</p>
|
||||
74
docs/user-guide/account-teams/your-account.njk
Normal file
74
docs/user-guide/account-teams/your-account.njk
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Your account
|
||||
order: 1
|
||||
desc: Learn how to import and export files in Penpot, the free, open-source design tool. Discover file formats, backups, sharing, and library management.
|
||||
---
|
||||
|
||||
<h1 id="account">Your account</h1>
|
||||
<p class="main-paragraph">Your account settings can be changed at the user area, in <b>Your account</b>. Here you can make changes to your profile, password or account language, as well as generate personal access tokens and access release notes.</p>
|
||||
|
||||
<h3 id="your-account-profile">Profile
|
||||
<a class="direct-link" href="#your-account-profile">#</a>
|
||||
</h3>
|
||||
<p>If you want to change the email address associated to your account or remove your account entirely, this can be done in the <b>Profile</b> section.</p>
|
||||
<figure>
|
||||
<img src="/img/interface/youraccount-profile.webp" alt="Penpot's profile" />
|
||||
</figure>
|
||||
|
||||
<h3 id="your-account-password">Password
|
||||
<a class="direct-link" href="#your-account-password">#</a>
|
||||
</h3>
|
||||
<p>If you want to change your password to a new one, this can be done in the <b>Password</b> section.</p>
|
||||
<figure>
|
||||
<img src="/img/interface/youraccount-password.webp" alt="Penpot's password" />
|
||||
</figure>
|
||||
|
||||
<h3 id="your-account-notifications">Notifications
|
||||
<a class="direct-link" href="#your-account-notifications">#</a>
|
||||
</h3>
|
||||
<p>At the <strong>Notifications</strong> section you can configure the email and dashboard notifications.</p>
|
||||
<figure>
|
||||
<img src="/img/interface/youraccount-notifications.webp" alt="Penpot's notifications" />
|
||||
</figure>
|
||||
|
||||
<h3 id="your-account-settings">Settings
|
||||
<a class="direct-link" href="#your-account-settings">#</a>
|
||||
</h3>
|
||||
<p>At the <strong>Settings</strong> section you can change the language and the UI color theme.</p>
|
||||
<figure>
|
||||
<img src="/img/interface/youraccount-settings.webp" alt="Penpot's settings" />
|
||||
</figure>
|
||||
|
||||
<h3 id="interface-ui-theme">UI Theme</h3>
|
||||
<p>Penpot's default interface is dark but you can switch anytime to a light option. You have 2 ways to change the theme:</p>
|
||||
<ul>
|
||||
<li>From "Your account" > "Settings".</li>
|
||||
<li>Using the shortcut <kbd>Alt/⌥</kbd> + <kbd>M</kbd>.</li>
|
||||
</ul>
|
||||
|
||||
<figure>
|
||||
<a href="/img/interface/dashboard-light.webp" target="_blank">
|
||||
<img src="/img/interface/dashboard-light.webp" alt="Penpot's dashboard" />
|
||||
</a>
|
||||
<figcaption>Penpot's dashboard in light mode</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<a href="/img/interface/workspace-light.webp" target="_blank">
|
||||
<img src="/img/interface/workspace-light.webp" alt="Penpot's workspace" />
|
||||
</a>
|
||||
<figcaption>Penpot's workspace in light mode</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<a href="/img/interface/viewmode-light.webp" target="_blank">
|
||||
<img src="/img/interface/viewmode-light.webp" alt="Penpot's view mode" />
|
||||
</a>
|
||||
<figcaption>Penpot's view mode in light mode</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
<h3 id="your-account-accesstokens">Access tokens
|
||||
<a class="direct-link" href="#your-account-accesstokens">#</a>
|
||||
</h3>
|
||||
<p>At the <strong>Asset tokens</strong> section you can manage your access tokens. <a href="https://help.penpot.app/technical-guide/integration/#access-tokens" target="_blank">Read more about access tokens here</a>.</p>
|
||||
|
||||
|
||||
@@ -1,338 +0,0 @@
|
||||
---
|
||||
title: 11· Components
|
||||
desc: Streamline your design workflow with Penpot's Components guide! Learn to create, duplicate, group, and manage reusable components.
|
||||
---
|
||||
|
||||
<h1 id="components">Components</h1>
|
||||
<p class="main-paragraph">Speed your workflow with the reusable power of components.</p>
|
||||
<p>A component is an object or group of objects that can be reused multiple times across files. This can help you maintain consistency across a group of designs.</p>
|
||||
|
||||
<h2 id="components-basics">Components basics</h2>
|
||||
<p>A component consists of two elements:</p>
|
||||
<ul>
|
||||
<li><strong>Main component</strong>: The original source of truth. It defines the core properties of the component.</li>
|
||||
<li><strong>Component copy</strong> (also known as instance): A duplicate of the main component that inherits its properties.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/components/components-main-copy.webp" alt="Components main and copy" />
|
||||
<figcaption>Mains and copies have different icons. Mains also have a title header at the viewport.</figcaption>
|
||||
</figure>
|
||||
<p>All component copies used in a file are linked in a way that updates made to the Main component can reflect in their component copies. You can override properties for component copies, so that you can manage singularities while maintaining properties in common.</p>
|
||||
|
||||
<h3 id="component-create">Create components</h3>
|
||||
<h4>Create a component</h4>
|
||||
<ol>
|
||||
<li>Select an object or a group of them.</li>
|
||||
<li>Press <kbd>Ctrl</kbd> + <kbd>K</kbd> or right click and select the option “Create component” at the object menu.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Creating a component" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-create.webp" height="auto">
|
||||
<source src="/img/components/components-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Duplicate a component</h4>
|
||||
<p>You can duplicate a component <a href="/user-guide/layer-basics/#duplicating-layers">the same way</a> you can duplicate any other layer. When duplicating a component, you are creating a component copy that will be linked to its main component.</p>
|
||||
|
||||
<h4>Duplicate as main component</h4>
|
||||
<p>You can duplicate a component as a new main component from the assets sidebar. Just select the component at the library, open the menu with right click and select the option "Duplicate main".</p>
|
||||
<figure>
|
||||
<video title="Duplicate main component" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-duplicate-main.webp" height="auto">
|
||||
<source src="/img/components/components-duplicate-main.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Delete a main component</h4>
|
||||
<p>You can delete main components and its copies anytime <a href="/user-guide/layer-basics/#deleting-layers">the same way</a> you can delete any other layer.</p>
|
||||
<p>Deleting a main component at the viewport means deleting it at the assets library and viceversa, so be careful!</p>
|
||||
<figure>
|
||||
<video title="Deleting main components" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-delete.webp" height="auto">
|
||||
<source src="/img/components/components-delete.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Restore a main component</h4>
|
||||
<p>If a main component has been deleted and you have access to a copy of it, you can use the copy to restore its main. There are two ways to do it:</p>
|
||||
<ul>
|
||||
<li>From the <strong>viewport menu</strong>: Select the component copy of a deleted main component, right click and press the option "Restore main component".</li>
|
||||
<li>From the <strong>sidebar menu</strong>: Open the sidebar menu of the component copy and press the option "Restore main component".</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/components/components-restore.webp" alt="Components main and copy" />
|
||||
<figcaption>Mains and copies have different icons. Mains also have a title header at the viewport.</figcaption>
|
||||
</figure>
|
||||
|
||||
<h3 id="component-find">Find main components</h3>
|
||||
<p>Where's my component? There are ways to find main components at the assets panel and at the design viewport.</p>
|
||||
|
||||
<h4>Find a main component at the assets panel</h4>
|
||||
<p>Select a main component at the viewport and then press "Show in assets panel" at the options of the right sidebar.</p>
|
||||
<figure>
|
||||
<video title="Show main component in the assets library" muted="" playsinline="" controls="" width="100%" poster="/img/components/components-show-asset.webp" height="auto">
|
||||
<source src="/img/components/components-show-asset.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Find a main component at the viewport</h4>
|
||||
<p>Select a component copy and then press "Show main component" at the viewport menu or the right sidebar menu.</p>
|
||||
<figure>
|
||||
<video title="Show main component" muted="" playsinline="" controls="" width="100%" poster="/img/components/components-show-main.webp" height="auto">
|
||||
<source src="/img/components/components-show-main.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="component-main-components-page">Main components page</h3>
|
||||
<p>If you find a page at a file called "Main components" this will probably mean that the file had assets with the previous components system and has been migrated to the current components system. The previous system didn't have the components as layers at the design file, only at the assets library, so when migrating a file to the new version Penpot automatically creates a page where to place all the components, grouping them using the library groups structure.</p>
|
||||
<figure>
|
||||
<img src="/img/components/components-page-main.webp" alt="Main components page" />
|
||||
</figure>
|
||||
|
||||
<h2 id="working-with-components">Working with components</h2>
|
||||
<h3 id="component-group">Group components</h3>
|
||||
<p>At the Components section from the Assets library, there are two ways to create groups in a components library.</p>
|
||||
<ol>
|
||||
<li><strong>Using slashes (/):</strong> Select one component and rename it as follows: "<i>FOLDER NAME/COMPONENT NAME</i>". For example, "<i>Buttons/Alert Button</i>".</li>
|
||||
<li><strong>Using the "Group" option:</strong> Select one or more components at the Assets library, right click to show the menu and then select "Group".</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Grouping components" muted="" playsinline="" controls="" width="100%" poster="/img/components/components-group.webp" height="auto">
|
||||
<source src="/img/components/components-group.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Ungroup components</h4>
|
||||
<p>You can ungroup the components the same ways you can group them, via the menu option ("Ungroup" in this case) or renaming them.</p>
|
||||
|
||||
<h4>Drag components to groups</h4>
|
||||
<p>One very direct way to move components between groups at the assets library is by dragging them.</p>
|
||||
<figure>
|
||||
<video title="Drag components" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-drag.webp" height="auto">
|
||||
<source src="/img/components/components-drag.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="component-detach">Detach components</h3>
|
||||
<p>Detach a component copy to unlink it from its Main component and transform it into a group layer. Press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd> or right click and select the option “Detach instance” at the component menu.</p>
|
||||
<p>You can also detach components in bulk by selecting several components and performing the same action.</p>
|
||||
|
||||
<h3 id="component-annotate">Annotate components</h3>
|
||||
<p>You can add text annotations to main components. The annotations are shown in every component copy. It is extremely useful to attach specifications that can be read at each component copy.</p>
|
||||
<figure>
|
||||
<video title="Annotating components at Penpot" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-annotation.webp" height="auto">
|
||||
<source src="/img/components/components-annotation.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<p>The annotations are also shown at the <a href="/user-guide/inspect">Inspect tab</a>, as another option to improve communication between designers and developers.</p>
|
||||
<figure>
|
||||
<img src="/img/components/components-annotations-inspect.webp" alt="Annotations at inspect tab" />
|
||||
</figure>
|
||||
|
||||
<h2 id="component-overrides-relationships">Component Overrides & Relationships</h2>
|
||||
<h3 id="component-overrides">Component overrides</h3>
|
||||
<p>Main components represent the more generic information of an element in a design system. You will usually need to change specific things (like a text, a color or an icon) in a component while maintaining the inheritance of the rest of it properties. Component overrides allows you to do that in Penpot.</p>
|
||||
<p>Overrides are modifications made in a specific copy that are not in its main component. With overrides you can keep changes at the component copies while maintaining synchronization with the Main component.</p>
|
||||
<figure>
|
||||
<img src="/img/components/components-overrides.webp" alt="Components overrides" />
|
||||
</figure>
|
||||
|
||||
<h4>Reset overrides</h4>
|
||||
<p>Right click and select the option “Reset overrides” at the component menu to get it to the state of the Main component.</p>
|
||||
<figure>
|
||||
<video title="Reset component overrides" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-reset-overrides.webp" height="auto">
|
||||
<source src="/img/components/components-reset-overrides.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="component-update">Update main from copies</h3>
|
||||
<p>You can push changes made at a component copy to a main component:</p>
|
||||
<ol>
|
||||
<li>Select a component copy that has changes that override one or more properties of its main component.</li>
|
||||
<li>Right click and select the option “Update main component” at the component menu. You can find this option at the viewport menu and at the sidebar menu.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<img src="/img/components/components-update.webp" alt="Updating a main component from a copy" />
|
||||
</figure>
|
||||
<p>If the component that is about to be updated is located in a different file which is connected to this file as a <a href="/user-guide/libraries/#shared-libraries">shared library</a>, a notification will be shown offering the options to update or dismiss.</p>
|
||||
<figure>
|
||||
<img src="/img/components/components-update-shared.webp" alt="Prompt shown to update a main component that is in a shared library" />
|
||||
</figure>
|
||||
|
||||
<h3 id="component-swap">Swap components</h3>
|
||||
<p>Penpot allows you to easily substitute component copies with other component copies.</p>
|
||||
<ol>
|
||||
<li>Select a component <strong>copy</strong>. You can not swap main components.</li>
|
||||
<li>At the right sidebar, press the component name to launch the swap menu.</li>
|
||||
<li>Choose the component you want to swap with and click on it.</li>
|
||||
</ol>
|
||||
<p class="advice"><strong>Tip:</strong> The first options shown to swap a component are the ones at the same level inside the assets library, so group them properly.</p>
|
||||
<figure>
|
||||
<video title="Swapping components at Penpot" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-swap.webp" height="auto">
|
||||
<source src="/img/components/components-swap.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
<h2 id="component-variants">Component Variants</h2>
|
||||
<p>Variants allow you to group similar components, such as buttons, icons, or toggles, into a single, customizable component. Rather than navigating through separate components for every possible state, size, or style, you can manage them all from one unified component using clearly defined properties.</p>
|
||||
<p>Imagine a single button component that can switch between primary and secondary styles, active and disabled states, and small to large sizes. Useful, right? That’s the power of Variants.</p>
|
||||
|
||||
<h3 id="component-variants-why-are-variants-important">Why are Variants Important?</h3>
|
||||
<ul>
|
||||
<li><strong>Cleaner libraries</strong><br>
|
||||
Keep related designs organized in the Design viewport, Layers panel, and swap menu. Variants streamline your components into tidy, manageable sets, allowing you to retain overrides when switching between them.
|
||||
</li>
|
||||
<li><strong>Faster design workflows</strong><br>
|
||||
Make it easier to find and select the right version by quickly switching between states or styles using simple property controls.
|
||||
</li>
|
||||
<li><strong>Better team collaboration</strong><br>
|
||||
With variants, you can match the way states are handled in code, helping designers and developers stay in sync, fostering better collaboration between design and development teams.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<figure>
|
||||
<iframe
|
||||
width="672px"
|
||||
height="378px"
|
||||
src="https://peertube.kaleidos.net/videos/embed/v9Yh79hom5otcBEnqondBY"
|
||||
title="Penpot Variants Demo"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
<figcaption>Penpot – Variants release</figcaption>
|
||||
</figure>
|
||||
|
||||
<h3 id="component-understanding-variants-properties-and-values">Understanding variants: properties and values</h3>
|
||||
<p>A component’s variants are organized by properties and their values.</p>
|
||||
<ul>
|
||||
<li><strong>Properties</strong> define the dimensions that distinguish your variants (for example: <em>Color</em>, <em>Size</em>, <em>State</em>).</li>
|
||||
<li><strong>Values</strong> are the specific options within a property (for example: <em>Primary/Secondary</em>, <em>Small/Large, Default/Hover/Pressed</em>)</li>
|
||||
</ul>
|
||||
<p>Each variant is simply one unique combination of values across all properties (for example, <code class="language-js">Color=Primary + Size=Small + State=Hover</code>).</p>
|
||||
<p>Variants must have at least one property, and property values should be kept consistent to make switching predictable and to preserve overrides across connected layers.</p>
|
||||
|
||||
<h3 id="component-create-and-modify-variants">Create and modify variants</h3>
|
||||
|
||||
<h4 id="component-create-variants">Create variants</h4>
|
||||
<p>You can create variants from an existing component or from another variant:</p>
|
||||
<ul>
|
||||
<li><strong>From a component:</strong> Press Ctrl + K or right-click and select the menu option <strong>Create variant</strong>.</li>
|
||||
<li><strong>From a variant:</strong> Select the variant and press Ctrl + K or right-click and select the menu option <strong>Create variant</strong>.</li>
|
||||
<li><strong>By dragging:</strong> Drag a main component into an existing component with variants to add it as a new variant.</li>
|
||||
<li><strong>From the Design tab</strong> (right sidebar): Select a component or a variant, open the context menu next to the component name and select the menu option <strong>Create variant</strong>.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/variants/01-variants-create.webp" alt="Variants creation button" />
|
||||
</figure>
|
||||
<p><strong>When a variant is created:</strong></p>
|
||||
<ul>
|
||||
<li>It appears next to the original in a dedicated variant area (by default in horizontal flex layout).</li>
|
||||
<li>Shared layers between variants are automatically connected (<a href="#component-understanding-overrides">connection conditions</a>) so that overrides can be preserved.</li>
|
||||
<li>Variants are named using their property values (e.g., <em>Primary / Hover</em>).</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/variants/02-variants-created.webp" alt="Variant created" />
|
||||
</figure>
|
||||
|
||||
<h4 id="component-manage-variant-properties">Manage variant properties</h4>
|
||||
<p>Properties are key to defining and differentiating your variants. They appear in the Design tab when a variant or component with variants is selected.</p>
|
||||
<figure>
|
||||
<img src="/img/variants/03-variants-property-add.webp" alt="Add variant property" />
|
||||
</figure>
|
||||
<h5>Add new properties</h5>
|
||||
<ul>
|
||||
<li><strong>From the Design tab:</strong> When the component or one of its variants is selected, you can add a new property via a menu. This property will be added to all existing variants with a default value (e.g., <em>Value 1</em>).</li>
|
||||
<li><strong>From the Layers panel:</strong> using the formula <code class="language-js">[property_name]=[value]</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h5>Edit properties</h5>
|
||||
<ul>
|
||||
<li><strong>From the Design tab:</strong> Select a component or a variant, then click on the property name to edit its name and/or value.</li>
|
||||
<li><strong>From the Layers panel:</strong> using the formula <code class="language-js">[property_name]=[value]</code>.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/variants/04-variants-properties-edit.webp" alt="Edit variant property" />
|
||||
</figure>
|
||||
|
||||
<h5>Delete properties</h5>
|
||||
<ul>
|
||||
<li><strong>From the Design tab:</strong> Select the main component (not an individual variant) and press the minus button next to the property.</li>
|
||||
<li><strong>From the Layers panel:</strong> You can delete a property by editing the names of all variants so that none of them contain that property in their formula.</li>
|
||||
</ul>
|
||||
<p class="advice">Variants must have at least one property. You can’t delete the last one.</p>
|
||||
<p>When <strong>multiple variants are selected</strong>, the Design tab will show all their properties and values. If a property has different values across the selected variants, it will display “Mixed,” allowing you to override them collectively.</p>
|
||||
|
||||
<h4 id="component-delete-variants">Delete Variants</h4>
|
||||
<ul>
|
||||
<li>Select the variant, press right-click, and select the menu option <strong>Delete</strong>.</li>
|
||||
<li><strong>Dragging</strong> a variant outside its component turns it into an independent component instead of deleting it.</li>
|
||||
</ul>
|
||||
<p class="advice">If you delete the last variant, the entire component is removed.</p>
|
||||
|
||||
<h4 id="component-restore-variants">Restore Variants</h4>
|
||||
<p>If you have a copy of a variant whose original was deleted, you can restore it:</p>
|
||||
<ul>
|
||||
<li>Select the variant copy, press right-click, and select the menu option <strong>Restore variant</strong> (will show if the main component still exists).</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="component-use-variants">Use variants</h3>
|
||||
<p>Once you have created your variants, you can place a copy of a component with variants into your design and then switch between its different versions.</p>
|
||||
|
||||
<h4 id="component-from-the-assets-tab">From the Assets tab</h4>
|
||||
<p>Drag and drop a component with variants from the Assets tab onto the design viewport, just like you would with any other component. Once placed, you can then use its properties in the Design tab to switch to the desired variant.</p>
|
||||
|
||||
<h4 id="component-from-the-design-tab">From the Design tab</h4>
|
||||
<p>When a variant is selected:</p>
|
||||
<ul>
|
||||
<li>You’ll see its properties and values.</li>
|
||||
<li>Change one or more property values to switch to another variant.</li>
|
||||
<li>If your chosen combination doesn’t exist, Penpot will suggest the closest match.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/variants/05-variants-use.webp" alt="Using variants" />
|
||||
</figure>
|
||||
|
||||
<h4 id="component-understanding-overrides">Understanding overrides</h4>
|
||||
<p>A key benefit of variants is the ability to <strong>preserve overrides when you switch between them</strong>. An override is a specific change you make to a component instance that diverges from its original definition (e.g., changing text content or a specific color).</p>
|
||||
<p>Layers between variants are considered connected if they:</p>
|
||||
<ol>
|
||||
<li>Share the <strong>same name</strong>.</li>
|
||||
<li><strong>Are the same type</strong>. Rectangle, ellipse, paths, and boolean operations count as the same type.</li>
|
||||
<li><strong>Have the same hierarchy level.</strong> Groups, boards, and layouts are considered equivalent.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<img src="/img/variants/variants-connections-conditions.png" alt="Variants connections conditions" />
|
||||
</figure>
|
||||
<p><strong>Example:</strong> If <code class="language-js">Variant 1</code> has a text layer named <em>label</em> with red color, and you change its content to <em>Click here</em> in an instance, then switch to <code class="language-js">Variant 2</code> (which also has a <em>label</em> text layer), the <em>Click here</em> content will be preserved, and <code class="language-js">Variant 2</code>’s color will be applied.</p>
|
||||
<p><strong>Changing any of these</strong> (e.g., renaming or grouping a layer) breaks the connection, but reverting the change will restore it.</p>
|
||||
|
||||
<h4 id="component-bulk-converting-components-to-variants">Bulk converting components to variants</h4>
|
||||
<p>If you already have multiple related components, you can combine them into a single component with variants:</p>
|
||||
<ul>
|
||||
<li><strong>From Assets tab</strong>: Select components in the same group and right-click → <strong>Combine as variants</strong>.</li>
|
||||
<li><strong>From viewport</strong>: Select multiple components → Right-click → <strong>Combine as variants</strong>.</li>
|
||||
<li><strong>From Design tab</strong>: If conditions are met, a Combine as variants button appears on the component card.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/variants/06-variants-combine.webp" alt="Combining components as variants" />
|
||||
</figure>
|
||||
<p><strong>Conditions:</strong></p>
|
||||
<ul>
|
||||
<li>Components must be on the same page.</li>
|
||||
<li>Components that already have variants cannot be combined.</li>
|
||||
</ul>
|
||||
<p><strong>When combined:</strong></p>
|
||||
<ul>
|
||||
<li>A variant area is created containing all former components.</li>
|
||||
<li>Property names and values are generated from the component names.</li>
|
||||
</ul>
|
||||
|
||||
<h4 id="component-transforming-variants-back-into-components">Transforming Variants Back into Components</h4>
|
||||
<p>To turn a variant into an independent component:</p>
|
||||
<ul>
|
||||
<li>Drag it outside the variant area (Design viewport or Layers panel).</li>
|
||||
<li>Cut and paste it outside the variant area.</li>
|
||||
</ul>
|
||||
<p>The new component’s name includes the original component name and the variant’s property values.</p>
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
title: 17· Custom fonts
|
||||
desc: Penpot's guide on custom fonts! Upload, manage, and use custom fonts in Penpot! Enhance your designs with personalised typography.
|
||||
---
|
||||
|
||||
<h1 id="customfonts">Custom fonts</h1>
|
||||
<p class="main-paragraph">If you have purchased, personal or libre fonts that are not included in the catalog provided by Penpot, you can upload them from your computer and use them across the files of a team. <p>
|
||||
|
||||
<h2 id="customfonts-upload">Upload local fonts</h2>
|
||||
<p>To use a font that you have on your local machine, first you need to upload it to the Penpot team where you want to use it.</p>
|
||||
<p>You can find the “Fonts” section in the dashboard menu, at the left sidebar.</p>
|
||||
<p><a href="/img/customfonts.png" target="_blank"><img src="/img/customfonts.png" alt="local fonts" /></a></p>
|
||||
|
||||
<h3>To upload a local font:</h3>
|
||||
<ol>
|
||||
<li>Press “Add custom font”.</li>
|
||||
<li>Inspect your local files to select one or more fonts that you want to upload. <strong>You can upload fonts with
|
||||
the following formats: TTF, OTF and WOFF</strong>. Only one format will be needed.</li>
|
||||
<li>Change the font name if needed. The font name is the name that will be shown in the font list at the workspace.
|
||||
It is also what Penpot uses to group fonts in families. You can always edit it later.</li>
|
||||
<li>Once ready, press upload. That's it. The font will be available at the font list of this team’s files.</li>
|
||||
</ol>
|
||||
<p><a href="/img/customfonts-upload.png" target="_blank"><img src="/img/customfonts-upload.png" alt="local fonts" /></a></p>
|
||||
|
||||
<h2 id="customfonts-families">Group fonts in font families</h2>
|
||||
<p>Fonts with the same font family name will be grouped as a single font family. That means that at the font list that you will use at the files they will be shown as only one font with different variants available. </p>
|
||||
<p>If you want to add a font variant (eg: Light) to a font family (eg: Helvetica) you only need to ensure during the upload process that it has the same font family name.</p>
|
||||
<p><a href="/img/customfonts-families.png" target="_blank"><img src="/img/customfonts-families.png" alt="local fonts" /></a></p>
|
||||
|
||||
<h2 id="customfonts-edit">Edit custom fonts</h2>
|
||||
<p>At the right side of a font family of the custom fonts list you can find a menu that allows you to edit the name of a font family and delete it.</p>
|
||||
|
||||
<h2 id="customfonts-using">Using custom fonts</h2>
|
||||
<p>Custom fonts are added to the fonts catalog of a team and can be used at the workspace from the font list at the design sidebar.</p>
|
||||
<p><img src="/img/customfonts-use.gif" alt="local fonts" /></p>
|
||||
|
||||
<h2>Fonts Licensing and Usage</h2>
|
||||
<p>You should only upload fonts you own or have license to use in Penpot. Find out more in the Content rights section of <a href="https://penpot.app/terms" target="_blank">Penpot's Terms of Service</a>. You also might want to read about <a href="https://www.typography.com/faq" target="_blank">font licensing</a>.</p>
|
||||
@@ -1,28 +1,27 @@
|
||||
---
|
||||
title: 09· Asset Libraries
|
||||
title: Assets
|
||||
order: 1
|
||||
desc: Use Penpot's asset libraries for reusable design elements! Learn to create, manage, and share components, colors, and typography. Try Penpot - it's free!
|
||||
---
|
||||
|
||||
<h1 id="asset-libraries">Asset Libraries</h1>
|
||||
<p class="main-paragraph">Asset Libraries allow you to store elements and styles so that they can be easily reused. Libraries may include components, graphics, colors and typographies. Learn how to create and manage them to better organize the pieces of your designs and speed your workflow.</p>
|
||||
<h1 id="assets">Assets</h1>
|
||||
<p class="main-paragraph">Asset Libraries allow you to store elements and styles so that they can be easily reused. Learn how to create and manage them to better organize the pieces of your designs and speed your workflow.</p>
|
||||
|
||||
<h2 id="assets">Assets</h2>
|
||||
|
||||
<h3 id="asset-types">Asset types</h3>
|
||||
<h2 id="asset-types">Asset types</h2>
|
||||
<p>In Penpot you can store different type of assets:</p>
|
||||
<ul>
|
||||
<li><strong>Components:</strong> A component is an object or group of objects that can be reused multiple times across files (projects and teams in the future). Learn more at the <a href="/user-guide/components/">components section</a>.</li>
|
||||
<li><strong>Components:</strong> A component is a layer or group of layers that can be reused multiple times across files (projects and teams in the future). Learn more at the <a href="/user-guide/design-systems/components/">components section</a>.</li>
|
||||
<li><strong>Colors:</strong> Create your color styles and use them on fills and strokes.</li>
|
||||
<li><strong>Typographies:</strong> Penpot allows you to save typography styles with a set of reusable properties. You can always unlink a typography style maintaining the properties for the text layer.</li>
|
||||
<li><strong>[Deprecated] Graphics:</strong> In Penpot versions older than 2.0, bitmap and vector images can be stored at the Graphics section. This type was deprecated in favor of components.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="add-assets-to-library">Add assets to libraries</h3>
|
||||
<h2 id="add-assets-to-library">Add assets to libraries</h2>
|
||||
<p>You can use the “+” icon to add assets. Each of the categories have their own specific adding action.</p>
|
||||
<h4>Components</h4>
|
||||
<h3>Components</h3>
|
||||
<p>There are two ways to add components to an assets library:</p>
|
||||
<ol>
|
||||
<li>Main components are automatically stored in the local library at the moment of being created. Learn more about components at the <a href="/user-guide/components/">components section</a>.</li>
|
||||
<li>Main components are automatically stored in the local library at the moment of being created. Learn more about components at the <a href="/user-guide/design-systems/components/">components section</a>.</li>
|
||||
<li>Clicking the “+” to launch import an image that will be added as a component.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
@@ -30,14 +29,14 @@ desc: Use Penpot's asset libraries for reusable design elements! Learn to create
|
||||
<source src="/img/libraries/add-component.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<h4>Colors</h4>
|
||||
<p>Click the “+” to launch the color picker and add a color to the library. Learn more about <a href="/user-guide/styling/#fill">managing color</a></p>
|
||||
<h3>Colors</h3>
|
||||
<p>Click the “+” to launch the color picker and add a color to the library. Learn more about <a href="/user-guide/designing/color-stroke/#fill">managing color</a></p>
|
||||
<figure>
|
||||
<video title="Add color" muted="" playsinline="" controls="" width="auto" poster="/img/libraries/add-color.webp" height="auto">
|
||||
<source src="/img/libraries/add-color.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<h4>Typographies</h4>
|
||||
<h3>Typographies</h3>
|
||||
<p>All typography styles created from the text properties (at the right sidebar) are automatically stored at the library. You can also click the “+” to create a new typography style from scratch.</p>
|
||||
<figure>
|
||||
<video title="Add typography" muted="" playsinline="" controls="" width="auto" poster="/img/libraries/add-typography.webp" height="auto">
|
||||
@@ -46,7 +45,7 @@ desc: Use Penpot's asset libraries for reusable design elements! Learn to create
|
||||
</figure>
|
||||
<p><strong>Tip:</strong> If you select a text layer with certain properties (font family, size, line height...) and click the "+" at the Typographies section at the assets library (left sidebar), the created typography style will include the properties of the text layer.</p>
|
||||
|
||||
<h3 id="edit-assets">Edit assets</h3>
|
||||
<h2 id="edit-assets">Edit assets</h2>
|
||||
<p>Press left click over any asset of the library to show the options menu. Some options are available only for certain assets.</p>
|
||||
<ul>
|
||||
<li><strong>Components:</strong> Rename, duplicate, delete, group.</li>
|
||||
@@ -59,7 +58,7 @@ desc: Use Penpot's asset libraries for reusable design elements! Learn to create
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="use-assets">Use Assets</h3>
|
||||
<h2 id="use-assets">Use Assets</h2>
|
||||
<ul>
|
||||
<li><strong>Components:</strong> Drag the component directly from the library to the viewport.</li>
|
||||
<li><strong>Colors:</strong> With a shape or a text selected click a color from the library to apply it as a fill. If you press <kbd>Alt</kbd> (or <kbd>⌥</kbd> in macOS) while clicking, the color will be applied to the stroke.</li>
|
||||
@@ -71,10 +70,10 @@ desc: Use Penpot's asset libraries for reusable design elements! Learn to create
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="organize-assets">Organize assets</h3>
|
||||
<h2 id="organize-assets">Organize assets</h2>
|
||||
<p>Learn how to better view and organize your assets:</p>
|
||||
|
||||
<h4>Assets list views</h4>
|
||||
<h3>Assets list views</h3>
|
||||
<p>You can switch between list and grid views.</p>
|
||||
<ul>
|
||||
<li><strong>Grid view:</strong> With the thumbnails displayed is easier to visually identify the content of specific assets, specially components.</li>
|
||||
@@ -86,7 +85,7 @@ desc: Use Penpot's asset libraries for reusable design elements! Learn to create
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Sort assets</h4>
|
||||
<h3>Sort assets</h3>
|
||||
<p>Click the sort button to change the alphabetical order.</p>
|
||||
<figure>
|
||||
<video title="Asset sort" muted="" playsinline="" controls="" width="auto" poster="/img/libraries/asset-order.webp" height="auto">
|
||||
@@ -94,7 +93,7 @@ desc: Use Penpot's asset libraries for reusable design elements! Learn to create
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Search assets</h4>
|
||||
<h3>Search assets</h3>
|
||||
<p>Use the Search assets box to filter the assets with names that match what you write.</p>
|
||||
<figure>
|
||||
<video title="Asset search" muted="" playsinline="" controls="" width="auto" poster="/img/libraries/asset-search.webp" height="auto">
|
||||
@@ -102,17 +101,17 @@ desc: Use Penpot's asset libraries for reusable design elements! Learn to create
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Filter assets</h4>
|
||||
<h3>Filter assets</h3>
|
||||
<p>You can decide whether to show all asset types or only one of your choice (components, colors or typographies).</p>
|
||||
|
||||
<h4>Multiselect assets</h4>
|
||||
<h3>Multiselect assets</h3>
|
||||
<figure>
|
||||
<video title="Asset search" muted="" playsinline="" controls="" width="auto" poster="/img/libraries/asset-multiselect.webp" height="auto">
|
||||
<source src="/img/libraries/asset-multiselect.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Group assets</h4>
|
||||
<h3>Group assets</h3>
|
||||
<p>There are two ways to create groups in a library.</p>
|
||||
<ol>
|
||||
<li><strong>With slashes (/):</strong> Select an asset and rename it as follows: "FOLDER NAME/ASSET NAME". For example, "Buttons/Alert Button".</li>
|
||||
@@ -124,10 +123,10 @@ desc: Use Penpot's asset libraries for reusable design elements! Learn to create
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Ungroup assets</h4>
|
||||
<h3>Ungroup assets</h3>
|
||||
<p>You can ungroup the assets the same ways you can group them, via the menu option ("Ungroup" in this case) or renaming them.</p>
|
||||
|
||||
<h4>Drag assets to groups</h4>
|
||||
<h3>Drag assets to groups</h3>
|
||||
<p>One very direct way to move assets between groups at the libraries is by dragging them.</p>
|
||||
<figure>
|
||||
<video title="Drag components" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-drag.webp" height="auto">
|
||||
@@ -136,68 +135,3 @@ desc: Use Penpot's asset libraries for reusable design elements! Learn to create
|
||||
</figure>
|
||||
|
||||
|
||||
<h2 id="libraries">Libraries</h2>
|
||||
|
||||
<h3 id="file-libraries">File libraries</h3>
|
||||
<p>Each file has its own file library which is where the assets that belong to this file are stored.</p>
|
||||
<p>You have two ways to access the file library from the file <a href="/user-guide/the-interface/#interface-workspace">workspace</a>:</p>
|
||||
<ul>
|
||||
<li>Click the assets tab icon at the left sidebar.</li>
|
||||
<li>Press <kbd>Alt/⌥</kbd> + <kbd>i</kbd>.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/libraries/assets-tab.webp" alt="Library assets tab">
|
||||
</figure>
|
||||
|
||||
<h3 id="shared-libraries">Shared libraries</h3>
|
||||
<h4>Publish as shared library</h4>
|
||||
<p>You can publish any regular file as a shared library. This means that the file library of this file will be available to be connected to other files that exist in the same team, so its library assets can be reused.</p>
|
||||
<p>There are two ways to publish a library:</p>
|
||||
<ul>
|
||||
<li>Using the file main menu.</li>
|
||||
<li>From the libraries panel, that you can launch by clicking on the "Libraries" button that is found at the assets tab.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-publish-menu.webp" alt="Publish library">
|
||||
<figcaption>Publishing a library from the main menu</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-publish-panel.webp" alt="Publish library">
|
||||
<figcaption>Publishing a library from the libraries panel</figcaption>
|
||||
</figure>
|
||||
|
||||
<h4>Unpublish a shared library</h4>
|
||||
<p>You can unpublish any library anytime the same way you can publish it, both from the file menu and the libraries panel.</p>
|
||||
<p>Unpublishing a library will disconnect it from the files where it was connected. The assets that have already been used in other files will remain, but no longer linked with the now unpublished library.</p>
|
||||
|
||||
<h4>Library updates</h4>
|
||||
<p></p>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-updates.webp" alt="Update libraries">
|
||||
</figure>
|
||||
|
||||
<h4>Connect shared libraries</h4>
|
||||
<p>To add a Shared Library from another file, launch the libraries panel, then search and select the available libraries. If you see the message "There are no Shared Libraries available", start by <a href="/user-guide/libraries/#shared-libraries">publishing other files as a shared library</a> or add from our <a href="https://penpot.app/libraries-templates">Libraries & templates</a>.</p>
|
||||
<figure>
|
||||
<video title="Connecting a shared library" muted="" playsinline="" controls="" width="100%" poster="/img/libraries/libraries-launch.webp" height="auto">
|
||||
<source src="/img/libraries/libraries-launch.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Disconnect shared library</h4>
|
||||
<p>You can disconnect any library anytime from the libraries panel just by clicking on the disconnect button.</p>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-disconnect.webp" alt="Disconnect libraries">
|
||||
</figure>
|
||||
|
||||
<h4>Use shared libraries</h4>
|
||||
<p>Shared libraries will be listed at the assets panel, at the workspace left sidebar. You can expand and collapse them to access the assets of each connected shared library.</p>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-sidebar.webp" alt="Connected libraries list">
|
||||
</figure>
|
||||
|
||||
<h4>Open shared library file</h4>
|
||||
<p>Click on the arrow icon at the right of a shared library name to go to the file where the library is and edit its contents.</p>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-open.webp" alt="Open libraries">
|
||||
</figure>
|
||||
172
docs/user-guide/design-systems/components.njk
Normal file
172
docs/user-guide/design-systems/components.njk
Normal file
@@ -0,0 +1,172 @@
|
||||
---
|
||||
title: Components
|
||||
order: 3
|
||||
desc: Streamline your design workflow with Penpot's Components guide! Learn to create, duplicate, group, and manage reusable components.
|
||||
---
|
||||
|
||||
<h1 id="components">Components</h1>
|
||||
<p class="main-paragraph">Speed your workflow with the reusable power of components.</p>
|
||||
<p>A component is a layer or group of layers that can be reused multiple times across files. This can help you maintain consistency across a group of designs.</p>
|
||||
<p>A component consists of two elements:</p>
|
||||
<ul>
|
||||
<li><strong>Main component</strong>: The original source of truth. It defines the core properties of the component.</li>
|
||||
<li><strong>Component copy</strong> (also known as instance): A duplicate of the main component that inherits its properties.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/components/components-main-copy.webp" alt="Components main and copy" />
|
||||
<figcaption>Mains and copies have different icons. Mains also have a title header at the viewport.</figcaption>
|
||||
</figure>
|
||||
<p>All component copies used in a file are linked in a way that updates made to the Main component can reflect in their component copies. You can override properties for component copies, so that you can manage singularities while maintaining properties in common.</p>
|
||||
|
||||
<h3 id="component-create">Create components</h3>
|
||||
<h4>Create a component</h4>
|
||||
<ol>
|
||||
<li>Select a layer or a group of them.</li>
|
||||
<li>Press <kbd>Ctrl</kbd> + <kbd>K</kbd> or right click and select the option “Create component” at the layer menu.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Creating a component" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-create.webp" height="auto">
|
||||
<source src="/img/components/components-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Duplicate a component</h4>
|
||||
<p>You can duplicate a component <a href="/user-guide/designing/layers/#duplicating-layers">the same way</a> you can duplicate any other layer. When duplicating a component, you are creating a component copy that will be linked to its main component.</p>
|
||||
|
||||
<h4>Duplicate as main component</h4>
|
||||
<p>You can duplicate a component as a new main component from the assets sidebar. Just select the component at the library, open the menu with right click and select the option "Duplicate main".</p>
|
||||
<figure>
|
||||
<video title="Duplicate main component" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-duplicate-main.webp" height="auto">
|
||||
<source src="/img/components/components-duplicate-main.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Delete a main component</h4>
|
||||
<p>You can delete main components and its copies anytime <a href="/user-guide/designing/layers/#delete-layers">the same way</a> you can delete any other layer.</p>
|
||||
<p>Deleting a main component at the viewport means deleting it at the assets library and viceversa, so be careful!</p>
|
||||
<figure>
|
||||
<video title="Deleting main components" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-delete.webp" height="auto">
|
||||
<source src="/img/components/components-delete.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Restore a main component</h4>
|
||||
<p>If a main component has been deleted and you have access to a copy of it, you can use the copy to restore its main. There are two ways to do it:</p>
|
||||
<ul>
|
||||
<li>From the <strong>viewport menu</strong>: Select the component copy of a deleted main component, right click and press the option "Restore main component".</li>
|
||||
<li>From the <strong>sidebar menu</strong>: Open the sidebar menu of the component copy and press the option "Restore main component".</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/components/components-restore.webp" alt="Components main and copy" />
|
||||
<figcaption>Mains and copies have different icons. Mains also have a title header at the viewport.</figcaption>
|
||||
</figure>
|
||||
|
||||
<h3 id="component-find">Find main components</h3>
|
||||
<p>Where's my component? There are ways to find main components at the assets panel and at the design viewport.</p>
|
||||
|
||||
<h4>Find a main component at the assets panel</h4>
|
||||
<p>Select a main component at the viewport and then press "Show in assets panel" at the options of the right sidebar.</p>
|
||||
<figure>
|
||||
<video title="Show main component in the assets library" muted="" playsinline="" controls="" width="100%" poster="/img/components/components-show-asset.webp" height="auto">
|
||||
<source src="/img/components/components-show-asset.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Find a main component at the viewport</h4>
|
||||
<p>Select a component copy and then press "Show main component" at the viewport menu or the right sidebar menu.</p>
|
||||
<figure>
|
||||
<video title="Show main component" muted="" playsinline="" controls="" width="100%" poster="/img/components/components-show-main.webp" height="auto">
|
||||
<source src="/img/components/components-show-main.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="component-main-components-page">Main components page</h3>
|
||||
<p>If you find a page at a file called "Main components" this will probably mean that the file had assets with the previous components system and has been migrated to the current components system. The previous system didn't have the components as layers at the design file, only at the assets library, so when migrating a file to the new version Penpot automatically creates a page where to place all the components, grouping them using the library groups structure.</p>
|
||||
<figure>
|
||||
<img src="/img/components/components-page-main.webp" alt="Main components page" />
|
||||
</figure>
|
||||
|
||||
<h3 id="component-group">Group components</h3>
|
||||
<p>At the Components section from the Assets library, there are two ways to create groups in a components library.</p>
|
||||
<ol>
|
||||
<li><strong>Using slashes (/):</strong> Select one component and rename it as follows: "<i>FOLDER NAME/COMPONENT NAME</i>". For example, "<i>Buttons/Alert Button</i>".</li>
|
||||
<li><strong>Using the "Group" option:</strong> Select one or more components at the Assets library, right click to show the menu and then select "Group".</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Grouping components" muted="" playsinline="" controls="" width="100%" poster="/img/components/components-group.webp" height="auto">
|
||||
<source src="/img/components/components-group.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Ungroup components</h4>
|
||||
<p>You can ungroup the components the same ways you can group them, via the menu option ("Ungroup" in this case) or renaming them.</p>
|
||||
|
||||
<h4>Drag components to groups</h4>
|
||||
<p>One very direct way to move components between groups at the assets library is by dragging them.</p>
|
||||
<figure>
|
||||
<video title="Drag components" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-drag.webp" height="auto">
|
||||
<source src="/img/components/components-drag.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="component-detach">Detach components</h3>
|
||||
<p>Detach a component copy to unlink it from its Main component and transform it into a group layer. Press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>K</kbd> or right click and select the option “Detach instance” at the component menu.</p>
|
||||
<p>You can also detach components in bulk by selecting several components and performing the same action.</p>
|
||||
|
||||
<h3 id="component-annotate">Annotate components</h3>
|
||||
<p>You can add text annotations to main components. The annotations are shown in every component copy. It is extremely useful to attach specifications that can be read at each component copy.</p>
|
||||
<figure>
|
||||
<video title="Annotating components at Penpot" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-annotation.webp" height="auto">
|
||||
<source src="/img/components/components-annotation.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<p>The annotations are also shown at the <a href="/user-guide/dev-tools/#inspect-design">Inspect tab</a>, as another option to improve communication between designers and developers.</p>
|
||||
<figure>
|
||||
<img src="/img/components/components-annotations-inspect.webp" alt="Annotations at inspect tab" />
|
||||
</figure>
|
||||
|
||||
<h3 id="component-overrides">Component overrides</h3>
|
||||
<p>Main components represent the more generic information of an element in a design system. You will usually need to change specific things (like a text, a color or an icon) in a component while maintaining the inheritance of the rest of it properties. Component overrides allows you to do that in Penpot.</p>
|
||||
<p>Overrides are modifications made in a specific copy that are not in its main component. With overrides you can keep changes at the component copies while maintaining synchronization with the Main component.</p>
|
||||
<figure>
|
||||
<img src="/img/components/components-overrides.webp" alt="Components overrides" />
|
||||
</figure>
|
||||
|
||||
<h4>Reset overrides</h4>
|
||||
<p>Right click and select the option “Reset overrides” at the component menu to get it to the state of the Main component.</p>
|
||||
<figure>
|
||||
<video title="Reset component overrides" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-reset-overrides.webp" height="auto">
|
||||
<source src="/img/components/components-reset-overrides.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="component-update">Update main from copies</h3>
|
||||
<p>You can push changes made at a component copy to a main component:</p>
|
||||
<ol>
|
||||
<li>Select a component copy that has changes that override one or more properties of its main component.</li>
|
||||
<li>Right click and select the option “Update main component” at the component menu. You can find this option at the viewport menu and at the sidebar menu.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<img src="/img/components/components-update.webp" alt="Updating a main component from a copy" />
|
||||
</figure>
|
||||
<p>If the component that is about to be updated is located in a different file which is connected to this file as a <a href="/user-guide/design-systems/libraries/#shared-libraries">shared library</a>, a notification will be shown offering the options to update or dismiss.</p>
|
||||
<figure>
|
||||
<img src="/img/components/components-update-shared.webp" alt="Prompt shown to update a main component that is in a shared library" />
|
||||
</figure>
|
||||
|
||||
<h3 id="component-swap">Swap components</h3>
|
||||
<p>Penpot allows you to easily substitute component copies with other component copies.</p>
|
||||
<ol>
|
||||
<li>Select a component <strong>copy</strong>. You can not swap main components.</li>
|
||||
<li>At the right sidebar, press the component name to launch the swap menu.</li>
|
||||
<li>Choose the component you want to swap with and click on it.</li>
|
||||
</ol>
|
||||
<p class="advice"><strong>Tip:</strong> The first options shown to swap a component are the ones at the same level inside the assets library, so group them properly.</p>
|
||||
<figure>
|
||||
<video title="Swapping components at Penpot" muted="" playsinline="" controls="" width="auto" poster="/img/components/components-swap.webp" height="auto">
|
||||
<source src="/img/components/components-swap.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
---
|
||||
title: 10· Design Tokens
|
||||
title: Design Tokens
|
||||
order: 5
|
||||
---
|
||||
|
||||
<h1 id="design-tokens">Design Tokens</h1>
|
||||
<p class="main-paragraph">Design tokens are the building blocks of all UI elements, the same tokens are used in designs, tools, and code. They include colors, typography, spacing, shadows, and any visual element that affects an object: all these properties collectively make up a design system or a visual inheritance.</p>
|
||||
<p class="main-paragraph">Design tokens are the building blocks of all UI elements, the same tokens are used in designs, tools, and code. They include colors, typography, spacing, shadows, and any visual element that affects a layer: all these properties collectively make up a design system or a visual inheritance.</p>
|
||||
|
||||
<figure>
|
||||
<img src="/img/design-tokens/01-tokens-cover.webp" alt="Tokens cover" />
|
||||
</figure>
|
||||
|
||||
<h3 id="design-tokens-why">Why Design Tokens?</h3>
|
||||
<h3>Why Design Tokens?</h3>
|
||||
<p>Design tokens act as a single source of truth, a common language that can be translated and used in any other tool or framework capable of reading the token format. With Design Tokens, you can create, manage, and synchronize these visual elements within Penpot and across other design tools, keeping your designs consistent and making your workflows faster and easier to maintain.</p>
|
||||
<p>You can also integrate Design Tokens with other core Penpot features, such as components and grid & flex layout, plus plugins will be able to access the tokens API (coming soon) making it even more powerful.</p>
|
||||
|
||||
<h3 id="design-tokens-format">W3C DTCG Format</h3>
|
||||
<h3>W3C DTCG Format</h3>
|
||||
<p>Penpot Design Tokens adhere to the <a href="https://design-tokens.github.io/community-group/format/" target="_blank">Design Tokens Format Module</a> and <a href="https://www.designtokens.org/glossary/" target="_blank">its definitions</a>, a draft by the <a href="https://www.w3.org/community/design-tokens/" target="_blank">W3C DTCG</a>. Penpot ensures compatibility across various disciplines, tools, and technologies by following the most standardized approach available for design tokens.</p>
|
||||
<p>Tokens can be exported from Penpot or integrated into other tools directly, without conversion. Additionally, the knowledge gained from using Design Tokens in Penpot remains valuable, regardless of whether you continue using Penpot or a different tool or technology.</p>
|
||||
|
||||
<h2 id="design-tokens-use">Using Tokens</h2>
|
||||
<h3 id="design-tokens-use-create">Creating a token</h3>
|
||||
<h2 id="design-tokens-use-create">Creating a token</h2>
|
||||
<p>You can create reusable and semantic tokens to be referenced in your designs at the <strong>Tokens</strong> panel. In this panel, you’ll find all the available types of tokens in Penpot arranged alphabetically, with existing tokens being shown at the top of the list.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/02-tokens-create.webp" alt="Tokens create" />
|
||||
@@ -31,7 +31,7 @@ title: 10· Design Tokens
|
||||
</ul>
|
||||
<p>Once you have named the token and assigned it a value, click <strong>Save</strong> to store the token and start referencing it.</p>
|
||||
|
||||
<h3 id="design-tokens-aliases">Referencing tokens into values (aliases)</h3>
|
||||
<h2 id="design-tokens-aliases">Referencing tokens into values (aliases)</h2>
|
||||
<p>When assigning a value to a token, you can reference existing tokens - these are called aliases at the <a href="https://www.designtokens.org/glossary/" target="_blank">DTCG Glossary</a>.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/03-tokens-aliases.webp" alt="Tokens aliases" />
|
||||
@@ -41,7 +41,7 @@ title: 10· Design Tokens
|
||||
<p>If the value of the referenced token changes, this will also change the value of the tokens where it is referenced.</p>
|
||||
<p class="advice">References to existing tokens are case sensitive.</p>
|
||||
|
||||
<h3 id="design-tokens-equations">Using equations</h3>
|
||||
<h2 id="design-tokens-equations">Using equations</h2>
|
||||
<p>Token types with numerical values also accept mathematical equations. If, for example, you create a <strong>spacing.small</strong> token with the value of <strong>2</strong>, and you then want to create a <strong>spacing.medium</strong> token that is twice as large, you could do so by writing <code class="language-js">{spacing.small} * 2</code> in its value. As a result, <strong>spacing.medium</strong> would have a value of <strong>4</strong>.</p>
|
||||
<p>Say you have a <strong>spacing.scale</strong> token with a value of <strong>2</strong>. You could also use this token in the equation to calculate the value of <strong>spacing.medium</strong> by writing <code class="language-js">{spacing.small} * {spacing.scale}</code> in its value.</p>
|
||||
<figure>
|
||||
@@ -55,21 +55,21 @@ title: 10· Design Tokens
|
||||
<li><code class="language-js">/</code> for division.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="design-tokens-edit">Editing a token</h3>
|
||||
<h2 id="design-tokens-edit">Editing a token</h2>
|
||||
<p>Tokens can be edited by right-clicking the token and selecting <strong>Edit token</strong>. This will allow you to change the tokens name, value and description. Once the changes are made, click <strong>Save</strong>.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/05-tokens-edit.webp" alt="Tokens edit" />
|
||||
</figure>
|
||||
<p class="advice">Renaming tokens will break any references to their old names. If a token is already applied somewhere, you'll need to reapply it after renaming. This can lead to extra work, so rename with caution. We're actively working on a solution to handle this automatically, ensuring renamed tokens stay linked to their properties without additional effort.</p>
|
||||
|
||||
<h3 id="design-tokens-duplicate">Duplicating a token</h3>
|
||||
<h2 id="design-tokens-duplicate">Duplicating a token</h2>
|
||||
<p>Tokens can be duplicated by right-clicking the token you wish to duplicate and selecting <strong>Duplicate token</strong>. This will create a copy of the selected token within the same set, with <code class="language-js">-copy</code> added to its name.</p>
|
||||
|
||||
<h3 id="design-tokens-delete">Deleting a token</h3>
|
||||
<h2 id="design-tokens-delete">Deleting a token</h2>
|
||||
<p>Tokens can be deleted by right-clicking the token you wish to delete and selecting <strong>Delete token</strong>.</p>
|
||||
|
||||
<h2 id="design-tokens-available">Available tokens</h2>
|
||||
<p>You can apply tokens to the properties of any <a href="/user-guide/objects/" target="_blank">object</a>. There are two ways to apply tokens to a selection:</p>
|
||||
<p>You can apply tokens to the properties of any <a href="/user-guide/designing/layers/" target="_blank">layer</a>. There are two ways to apply tokens to a selection:</p>
|
||||
<ul>
|
||||
<li>Right-click on tokens to specify a particular property that you want to apply.</li>
|
||||
<li>Left-click on tokens to apply the assumtion. Assumptions can vary across different token types. For example, for the <strong>color</strong> type the assumtion is that you want to apply the token as a <strong>fill</strong>.</li>
|
||||
@@ -322,7 +322,7 @@ Applying a Typography Token will detach any Typography Style previously applied,
|
||||
<h4 id="design-tokens-font-size">Font Size</h4>
|
||||
<p>Font size tokens define the vertical size of glyphs/characters as an individual property. In typography, the letter spacing and line height properties are related to the font size.</p>
|
||||
<p>Font sizes are typically defined using a proportional scale. You can use math operations with references in order to create dynamic typography scales that follow a particular multiplier, like Golden Ratio.</p>
|
||||
<p><strong>Tip:</strong> Use <a href="/user-guide/design-tokens/#design-tokens-number" target="_blank">Number Tokens</a> (unitless token) to create stunning typographic scales though design tokens:</p>
|
||||
<p><strong>Tip:</strong> Use <a href="#design-tokens-number" target="_blank">Number Tokens</a> (unitless token) to create stunning typographic scales though design tokens:</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/30-tokens-type-font-size-scale.webp" alt="Font size scale" />
|
||||
</figure>
|
||||
@@ -423,6 +423,40 @@ ExtraBold Italic
|
||||
<p>This token can be applied directly to a text element or be used as a reference in a Typography Composite Token.</p>
|
||||
|
||||
|
||||
<h3 id="typography-composite-tokens">Typography composite token</h3>
|
||||
<p><strong>Typography tokens</strong> are composite entities that group several text properties into a single token definition. They allow you to define and reuse complete text styles in a consistent way.</p>
|
||||
<p>Each property within a typography token can either reference an existing <a href="#design-tokens-typography">individual typography token</a> (for example, <em>font-size</em> or <em>font-family</em>) or use a hardcoded value. The behavior and syntax of individual typography tokens are described in the previous section of this guide.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/36-tokens-composite-typography.webp" alt="Typography composite token" />
|
||||
</figure>
|
||||
|
||||
<h4 id="reference-composite-token">Reference another Typography Composite Token</h4>
|
||||
<p>You can also reference another existing <strong>Typography Composite Token</strong> instead of defining each property manually. When doing so, Penpot resolves all individual properties from the referenced token.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/34-tokens-composite-typography-alias.webp" alt="Typography composite token" />
|
||||
</figure>
|
||||
|
||||
<h4 id="line-height-property">Line height property</h4>
|
||||
<p>The <strong>Typography Token</strong> includes a <em>line-height</em> property, which is not available as an individual token. This is because line-height depends on the font size to be calculated properly. Make sure the <em>font-size</em> property is defined before setting <em>line-height</em>.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/35-tokens-composite-typography-lineheight.webp" alt="Typography composite token" />
|
||||
</figure>
|
||||
|
||||
<p>Accepted values for the line-height input:</p>
|
||||
<ul>
|
||||
<li><strong>Unitless number:</strong> interpreted as a multiplier of the font size. This is Penpot’s default behavior.</li>
|
||||
<li><strong>Percentage (%):</strong> converted internally to a multiplier.</li>
|
||||
<li><strong>Pixel (px) or rem value:</strong> if using rem, Penpot calculates the proportion relative to the font size and converts it to a multiplier.</li>
|
||||
<li><strong>References:</strong> you can also reference <a href="#design-tokens-number">number</a> or <a href="#design-tokens-dimensions">dimension</a> tokens.</li>
|
||||
</ul>
|
||||
|
||||
<h4 id="apply-typography-token">Apply a Typography token</h4>
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
|
||||
<h2 id="design-tokens-sets">Token Sets</h2>
|
||||
<p>Token Sets allow you to split your tokens up into multiple files in order to create organized groups or collections of tokens. It enables efficient management and customization within design files. For example you can group all your color sets, sizing sets or platform-specific sets. The purpose of tokens sets is to organize them in a way that matches your needs.</p>
|
||||
<figure>
|
||||
@@ -433,7 +467,7 @@ ExtraBold Italic
|
||||
<p>When creating a token set, it’s recommended that you assign it a unique name to ensure clarity. Token set names are not included in individual token names by default so it is possible to have tokens with the same name belonging to different token sets.</p>
|
||||
<p>Token sets can be enabled or disabled. If a set is disabled, its tokens will be excluded from the token resolution process.</p>
|
||||
|
||||
<h3 id="design-tokens-sets-create">Creating Token Sets</h3>
|
||||
<h3>Creating Token Sets</h3>
|
||||
<p>There are two ways to create a token set at the <strong>Tokens</strong> tab:</p>
|
||||
<ol>
|
||||
<li>Click on the <strong>+</strong> next to <strong>Sets</strong>;</li>
|
||||
@@ -443,7 +477,7 @@ ExtraBold Italic
|
||||
<p>When a token set is selected, the tokens within the selected set are displayed on the panel below.</p>
|
||||
|
||||
|
||||
<h3 id="design-tokens-sets-edit">Editing Token Sets</h3>
|
||||
<h3>Editing Token Sets</h3>
|
||||
<p>Right-click a token set to perform these quick actions:</p>
|
||||
<ol>
|
||||
<li><strong>Rename</strong>: Give the set a new name and press Enter.</li>
|
||||
@@ -458,7 +492,7 @@ ExtraBold Italic
|
||||
<p>Once you have created a token set, you can start creating tokens within that token set. To do so, simply select the token set and create a new token.</p>
|
||||
<p class="advice">If a token with the same name already exists in another set, a new token can still be created in the current set.</p>
|
||||
|
||||
<h3 id="design-tokens-groups">Creating Token Set Folders</h3>
|
||||
<h3>Creating Token Set Folders</h3>
|
||||
<p>To group token sets just use folder-style names. For example, naming your sets <code class="language-js">Light/Global</code> and <code class="language-js">Light/Colors</code> will create a folder called <strong><i>Light</i></strong> with two sets inside it: <strong><i>Global</i></strong> and <strong><i>Colors</i></strong>.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/15-tokens-sets-group.webp" alt="Tokens sets folder" />
|
||||
@@ -484,14 +518,14 @@ ExtraBold Italic
|
||||
<p>When you have various themes inside a group, only one of the themes in this group can be active.</p>
|
||||
<p>Having your sets clubbed under groups makes it more accessible to switch from a matrix of themes.</p>
|
||||
|
||||
<h3 id="design-tokens-themes-create">Creating Token Themes</h3>
|
||||
<h3>Creating Token Themes</h3>
|
||||
<p>To create a new theme, click the <strong>Create one</strong> button in the Themes section. You can create a group (this is optional) or add an existing one, and then you then need to assign a name to your theme and click on <strong>Save Theme</strong>.</p>
|
||||
<p>Your new theme will now appear on the Theme lists. You’ll need to enable the tokens sets that you want to include in the theme, clicking on the button “no active sets”. Here you can also activate and deactivate it, as well as delete the theme.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/17-tokens-themes-create.webp" alt="Tokens themes create" />
|
||||
</figure>
|
||||
|
||||
<h3 id="design-tokens-themes-edit">Editing Themes</h3>
|
||||
<h3>Editing Themes</h3>
|
||||
<p>In the <strong>Themes</strong> section, you can find a dropdown to activate and deactivate themes. If there are no active themes, the dropdown shows a message of: “no theme active”.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/19-tokens-themes-edit.webp" alt="Tokens themes edit" />
|
||||
@@ -508,7 +542,7 @@ ExtraBold Italic
|
||||
<li>Creates a new theme.</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="design-tokens-themes-group">Grouping Themes</h3>
|
||||
<h3>Grouping Themes</h3>
|
||||
<p>You can categorize your themes into groups. This allows you to generate a matrix of potential combinations involving color themes, brands, modes, and more.</p>
|
||||
<figure>
|
||||
<img src="/img/design-tokens/20-tokens-themes-group.webp" alt="Tokens themes group" />
|
||||
@@ -535,7 +569,7 @@ ExtraBold Italic
|
||||
<li><strong>Export:</strong> Click <strong>Tools</strong>, then select <strong>Export</strong> to view export options.</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="design-tokens-import-options">Import Options</h3>
|
||||
<h3>Import Options</h3>
|
||||
|
||||
<h4>ZIP file</h4>
|
||||
<p>You can import tokens from a <strong>.zip</strong> file. This file can either contain a single JSON file or a folder structure with multiple files. The ZIP import option provides flexibility for organizing your tokens before importing them into Penpot.</p>
|
||||
@@ -604,7 +638,7 @@ ExtraBold Italic
|
||||
</pre>
|
||||
<figcaption>The main folder name won’t be used to build token set names, so in this example, <strong>folder</strong> will be ignored in the set names.</figcaption>
|
||||
|
||||
<h3 id="design-tokens-export-options">Export Options</h3>
|
||||
<h3>Export Options</h3>
|
||||
<p>Just like with importing, you can export tokens, themes and sets either in a single JSON file or in multiple files. There is no difference in the content being exported; the choice depends on your team's preferences for file organization: a single file with all the tokens, sets and themes, or a folder structure with separated JSON files organized by sets.</p>
|
||||
<p>In both cases you can preview the result of the export options:</p>
|
||||
|
||||
@@ -645,4 +679,4 @@ ExtraBold Italic
|
||||
</figure>
|
||||
<p>Here you can view and change the value.</p>
|
||||
<p>The input accepts values with px and unitless, which will be interpreted as pixels.</p>
|
||||
-->
|
||||
-->
|
||||
40
docs/user-guide/design-systems/index.njk
Normal file
40
docs/user-guide/design-systems/index.njk
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Design Systems
|
||||
order: 3
|
||||
desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorials. Learn the interface, layers, objects, styling, and more.
|
||||
---
|
||||
|
||||
<h1 id="section-3">Design Systems</h1>
|
||||
|
||||
<ul class="intro-sections">
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/assets">
|
||||
<h2>Assets →</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/libraries">
|
||||
<h2>Libraries →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/components">
|
||||
<h2>Components →</h2>
|
||||
<p>Speed your design workflow</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/variants">
|
||||
<h2>Variants →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/design-tokens">
|
||||
<h2>Design Tokens →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
72
docs/user-guide/design-systems/libraries.njk
Normal file
72
docs/user-guide/design-systems/libraries.njk
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: Libraries
|
||||
order: 2
|
||||
desc: Use Penpot's libraries for reusable design elements! Learn to create, manage, and share components, colors, and typography. Try Penpot - it's free!
|
||||
---
|
||||
<h1 id="libraries">Libraries</h1>
|
||||
|
||||
<p class="main-paragraph">Libraries may include components, graphics, colors and typographies. Learn how to create and manage them to better organize the pieces of your designs and speed your workflow.</p>
|
||||
|
||||
<h3 id="file-libraries">File libraries</h3>
|
||||
<p>Each file has its own file library which is where the assets that belong to this file are stored.</p>
|
||||
<p>You have two ways to access the file library from the file <a href="/user-guide/first-steps/the-interface/#interface-workspace">workspace</a>:</p>
|
||||
<ul>
|
||||
<li>Click the assets tab icon at the left sidebar.</li>
|
||||
<li>Press <kbd>Alt/⌥</kbd> + <kbd>i</kbd>.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/libraries/assets-tab.webp" alt="Library assets tab">
|
||||
</figure>
|
||||
|
||||
<h3 id="shared-libraries">Shared libraries</h3>
|
||||
<h4>Publish as shared library</h4>
|
||||
<p>You can publish any regular file as a shared library. This means that the file library of this file will be available to be connected to other files that exist in the same team, so its library assets can be reused.</p>
|
||||
<p>There are two ways to publish a library:</p>
|
||||
<ul>
|
||||
<li>Using the file main menu.</li>
|
||||
<li>From the libraries panel, that you can launch by clicking on the "Libraries" button that is found at the assets tab.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-publish-menu.webp" alt="Publish library">
|
||||
<figcaption>Publishing a library from the main menu</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-publish-panel.webp" alt="Publish library">
|
||||
<figcaption>Publishing a library from the libraries panel</figcaption>
|
||||
</figure>
|
||||
|
||||
<h4>Unpublish a shared library</h4>
|
||||
<p>You can unpublish any library anytime the same way you can publish it, both from the file menu and the libraries panel.</p>
|
||||
<p>Unpublishing a library will disconnect it from the files where it was connected. The assets that have already been used in other files will remain, but no longer linked with the now unpublished library.</p>
|
||||
|
||||
<h4>Library updates</h4>
|
||||
<p></p>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-updates.webp" alt="Update libraries">
|
||||
</figure>
|
||||
|
||||
<h4>Connect shared libraries</h4>
|
||||
<p>To add a Shared Library from another file, launch the libraries panel, then search and select the available libraries. If you see the message "There are no Shared Libraries available", start by <a href="/user-guide/design-systems/libraries/#shared-libraries">publishing other files as a shared library</a> or add from our <a href="https://penpot.app/libraries-templates">Libraries & templates</a>.</p>
|
||||
<figure>
|
||||
<video title="Connecting a shared library" muted="" playsinline="" controls="" width="100%" poster="/img/libraries/libraries-launch.webp" height="auto">
|
||||
<source src="/img/libraries/libraries-launch.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Disconnect shared library</h4>
|
||||
<p>You can disconnect any library anytime from the libraries panel just by clicking on the disconnect button.</p>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-disconnect.webp" alt="Disconnect libraries">
|
||||
</figure>
|
||||
|
||||
<h4>Use shared libraries</h4>
|
||||
<p>Shared libraries will be listed at the assets panel, at the workspace left sidebar. You can expand and collapse them to access the assets of each connected shared library.</p>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-sidebar.webp" alt="Connected libraries list">
|
||||
</figure>
|
||||
|
||||
<h4>Open shared library file</h4>
|
||||
<p>Click on the arrow icon at the right of a shared library name to go to the file where the library is and edit its contents.</p>
|
||||
<figure>
|
||||
<img src="/img/libraries/libraries-open.webp" alt="Open libraries">
|
||||
</figure>
|
||||
168
docs/user-guide/design-systems/variants.njk
Normal file
168
docs/user-guide/design-systems/variants.njk
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
title: Variants
|
||||
order: 4
|
||||
desc: Streamline your design workflow with Penpot's Components guide! Learn to create, duplicate, group, and manage reusable components.
|
||||
---
|
||||
|
||||
<h1 id="variants">Variants</h1>
|
||||
<p class="main-paragraph">Variants allow you to group similar components, such as buttons, icons, or toggles, into a single, customizable component. Rather than navigating through separate components for every possible state, size, or style, you can manage them all from one unified component using clearly defined properties.</p>
|
||||
<p class="main-paragraph">Imagine a single button component that can switch between primary and secondary styles, active and disabled states, and small to large sizes. Useful, right? That’s the power of Variants.</p>
|
||||
|
||||
<h3 id="component-variants-why-are-variants-important">Why are Variants Important?</h3>
|
||||
<ul>
|
||||
<li><strong>Cleaner libraries</strong><br>
|
||||
Keep related designs organized in the Design viewport, Layers panel, and swap menu. Variants streamline your components into tidy, manageable sets, allowing you to retain overrides when switching between them.
|
||||
</li>
|
||||
<li><strong>Faster design workflows</strong><br>
|
||||
Make it easier to find and select the right version by quickly switching between states or styles using simple property controls.
|
||||
</li>
|
||||
<li><strong>Better team collaboration</strong><br>
|
||||
With variants, you can match the way states are handled in code, helping designers and developers stay in sync, fostering better collaboration between design and development teams.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<figure>
|
||||
<iframe
|
||||
width="672px"
|
||||
height="378px"
|
||||
src="https://peertube.kaleidos.net/videos/embed/v9Yh79hom5otcBEnqondBY"
|
||||
title="Penpot Variants Demo"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
<figcaption>Penpot – Variants release</figcaption>
|
||||
</figure>
|
||||
|
||||
<h3 id="component-understanding-variants-properties-and-values">Understanding variants: properties and values</h3>
|
||||
<p>A component’s variants are organized by properties and their values.</p>
|
||||
<ul>
|
||||
<li><strong>Properties</strong> define the dimensions that distinguish your variants (for example: <em>Color</em>, <em>Size</em>, <em>State</em>).</li>
|
||||
<li><strong>Values</strong> are the specific options within a property (for example: <em>Primary/Secondary</em>, <em>Small/Large, Default/Hover/Pressed</em>)</li>
|
||||
</ul>
|
||||
<p>Each variant is simply one unique combination of values across all properties (for example, <code class="language-js">Color=Primary + Size=Small + State=Hover</code>).</p>
|
||||
<p>Variants must have at least one property, and property values should be kept consistent to make switching predictable and to preserve overrides across connected layers.</p>
|
||||
|
||||
<h3 id="component-create-and-modify-variants">Create and modify variants</h3>
|
||||
|
||||
<h4 id="component-create-variants">Create variants</h4>
|
||||
<p>You can create variants from an existing component or from another variant:</p>
|
||||
<ul>
|
||||
<li><strong>From a component:</strong> Press Ctrl + K or right-click and select the menu option <strong>Create variant</strong>.</li>
|
||||
<li><strong>From a variant:</strong> Select the variant and press Ctrl + K or right-click and select the menu option <strong>Create variant</strong>.</li>
|
||||
<li><strong>By dragging:</strong> Drag a main component into an existing component with variants to add it as a new variant.</li>
|
||||
<li><strong>From the Design tab</strong> (right sidebar): Select a component or a variant, open the context menu next to the component name and select the menu option <strong>Create variant</strong>.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/variants/01-variants-create.webp" alt="Variants creation button" />
|
||||
</figure>
|
||||
<p><strong>When a variant is created:</strong></p>
|
||||
<ul>
|
||||
<li>It appears next to the original in a dedicated variant area (by default in horizontal flex layout).</li>
|
||||
<li>Shared layers between variants are automatically connected (<a href="#component-use-variants">connection conditions</a>) so that overrides can be preserved.</li>
|
||||
<li>Variants are named using their property values (e.g., <em>Primary / Hover</em>).</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/variants/02-variants-created.webp" alt="Variant created" />
|
||||
</figure>
|
||||
|
||||
<h4 id="component-manage-variant-properties">Manage variant properties</h4>
|
||||
<p>Properties are key to defining and differentiating your variants. They appear in the Design tab when a variant or component with variants is selected.</p>
|
||||
<figure>
|
||||
<img src="/img/variants/03-variants-property-add.webp" alt="Add variant property" />
|
||||
</figure>
|
||||
<h5>Add new properties</h5>
|
||||
<ul>
|
||||
<li><strong>From the Design tab:</strong> When the component or one of its variants is selected, you can add a new property via a menu. This property will be added to all existing variants with a default value (e.g., <em>Value 1</em>).</li>
|
||||
<li><strong>From the Layers panel:</strong> using the formula <code class="language-js">[property_name]=[value]</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h5>Edit properties</h5>
|
||||
<ul>
|
||||
<li><strong>From the Design tab:</strong> Select a component or a variant, then click on the property name to edit its name and/or value.</li>
|
||||
<li><strong>From the Layers panel:</strong> using the formula <code class="language-js">[property_name]=[value]</code>.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/variants/04-variants-properties-edit.webp" alt="Edit variant property" />
|
||||
</figure>
|
||||
|
||||
<h5>Delete properties</h5>
|
||||
<ul>
|
||||
<li><strong>From the Design tab:</strong> Select the main component (not an individual variant) and press the minus button next to the property.</li>
|
||||
<li><strong>From the Layers panel:</strong> You can delete a property by editing the names of all variants so that none of them contain that property in their formula.</li>
|
||||
</ul>
|
||||
<p class="advice">Variants must have at least one property. You can’t delete the last one.</p>
|
||||
<p>When <strong>multiple variants are selected</strong>, the Design tab will show all their properties and values. If a property has different values across the selected variants, it will display “Mixed,” allowing you to override them collectively.</p>
|
||||
|
||||
<h4 id="component-delete-variants">Delete Variants</h4>
|
||||
<ul>
|
||||
<li>Select the variant, press right-click, and select the menu option <strong>Delete</strong>.</li>
|
||||
<li><strong>Dragging</strong> a variant outside its component turns it into an independent component instead of deleting it.</li>
|
||||
</ul>
|
||||
<p class="advice">If you delete the last variant, the entire component is removed.</p>
|
||||
|
||||
<h4 id="component-restore-variants">Restore Variants</h4>
|
||||
<p>If you have a copy of a variant whose original was deleted, you can restore it:</p>
|
||||
<ul>
|
||||
<li>Select the variant copy, press right-click, and select the menu option <strong>Restore variant</strong> (will show if the main component still exists).</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="component-use-variants">Use variants</h3>
|
||||
<p>Once you have created your variants, you can place a copy of a component with variants into your design and then switch between its different versions.</p>
|
||||
|
||||
<h4 id="component-from-the-assets-tab">From the Assets tab</h4>
|
||||
<p>Drag and drop a component with variants from the Assets tab onto the design viewport, just like you would with any other component. Once placed, you can then use its properties in the Design tab to switch to the desired variant.</p>
|
||||
|
||||
<h4 id="component-from-the-design-tab">From the Design tab</h4>
|
||||
<p>When a variant is selected:</p>
|
||||
<ul>
|
||||
<li>You’ll see its properties and values.</li>
|
||||
<li>Change one or more property values to switch to another variant.</li>
|
||||
<li>If your chosen combination doesn’t exist, Penpot will suggest the closest match.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/variants/05-variants-use.webp" alt="Using variants" />
|
||||
</figure>
|
||||
|
||||
<h4 id="component-understanding-overrides">Understanding overrides</h4>
|
||||
<p>A key benefit of variants is the ability to <strong>preserve overrides when you switch between them</strong>. An override is a specific change you make to a component instance that diverges from its original definition (e.g., changing text content or a specific color).</p>
|
||||
<p>Layers between variants are considered connected if they:</p>
|
||||
<ol>
|
||||
<li>Share the <strong>same name</strong>.</li>
|
||||
<li><strong>Are the same type</strong>. Rectangle, ellipse, paths, and boolean operations count as the same type.</li>
|
||||
<li><strong>Have the same hierarchy level.</strong> Groups, boards, and layouts are considered equivalent.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<img src="/img/variants/variants-connections-conditions.png" alt="Variants connections conditions" />
|
||||
</figure>
|
||||
<p><strong>Example:</strong> If <code class="language-js">Variant 1</code> has a text layer named <em>label</em> with red color, and you change its content to <em>Click here</em> in an instance, then switch to <code class="language-js">Variant 2</code> (which also has a <em>label</em> text layer), the <em>Click here</em> content will be preserved, and <code class="language-js">Variant 2</code>’s color will be applied.</p>
|
||||
<p><strong>Changing any of these</strong> (e.g., renaming or grouping a layer) breaks the connection, but reverting the change will restore it.</p>
|
||||
|
||||
<h4 id="component-bulk-converting-components-to-variants">Bulk converting components to variants</h4>
|
||||
<p>If you already have multiple related components, you can combine them into a single component with variants:</p>
|
||||
<ul>
|
||||
<li><strong>From Assets tab</strong>: Select components in the same group and right-click → <strong>Combine as variants</strong>.</li>
|
||||
<li><strong>From viewport</strong>: Select multiple components → Right-click → <strong>Combine as variants</strong>.</li>
|
||||
<li><strong>From Design tab</strong>: If conditions are met, a Combine as variants button appears on the component card.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/variants/06-variants-combine.webp" alt="Combining components as variants" />
|
||||
</figure>
|
||||
<p><strong>Conditions:</strong></p>
|
||||
<ul>
|
||||
<li>Components must be on the same page.</li>
|
||||
<li>Components that already have variants cannot be combined.</li>
|
||||
</ul>
|
||||
<p><strong>When combined:</strong></p>
|
||||
<ul>
|
||||
<li>A variant area is created containing all former components.</li>
|
||||
<li>Property names and values are generated from the component names.</li>
|
||||
</ul>
|
||||
|
||||
<h4 id="component-transforming-variants-back-into-components">Transforming Variants Back into Components</h4>
|
||||
<p>To turn a variant into an independent component:</p>
|
||||
<ul>
|
||||
<li>Drag it outside the variant area (Design viewport or Layers panel).</li>
|
||||
<li>Cut and paste it outside the variant area.</li>
|
||||
</ul>
|
||||
<p>The new component’s name includes the original component name and the variant’s property values.</p>
|
||||
@@ -1,24 +1,25 @@
|
||||
---
|
||||
title: 06· Styling
|
||||
title: Color and Strokes
|
||||
order: 3
|
||||
desc: Style your designs with Penpot's options! Learn about color fills, gradients, strokes, shadows, blur, opacity, blend modes, and property copying.
|
||||
---
|
||||
|
||||
<h1 id="styling">Styling</h1>
|
||||
<p class="main-paragraph">Penpot has a variety of styling options for each object. When selected, the styling options are displayed in the design panel on the right.</p>
|
||||
<h1 id="styling">Color and Strokes</h1>
|
||||
<p class="main-paragraph">Penpot has a variety of styling options for each layer. When selected, the styling options are displayed in the design panel on the right.</p>
|
||||
|
||||
<h2 id="fill">Color fills</h2>
|
||||
<p>Color fills can be added to boards, shapes, texts and groups of layers.</p>
|
||||
<p>You can add as fills:</p>
|
||||
<ul>
|
||||
<li>Custom colors (hex).</li>
|
||||
<li>Color <a href="/user-guide/libraries/#asset-types">assets</a>.</li>
|
||||
<li>Color <a href="/user-guide/design-systems/assets/#asset-types">assets</a>.</li>
|
||||
<li>Images.</li>
|
||||
</ul>
|
||||
<p>To apply a fill you can use: the color picker, the color palette or a color style.</p>
|
||||
<ul>
|
||||
<li>The <a href="/user-guide/styling/#color-picker">color picker</a>.</li>
|
||||
<li>The <a href="/user-guide/styling/#color-palette">color palette</a>.</li>
|
||||
<li>The <a href="/user-guide/libraries/">assets library</a>.</li>
|
||||
<li>The <a href="/user-guide/designing/color-stroke/#color-picker">color picker</a>.</li>
|
||||
<li>The <a href="/user-guide/designing/color-stroke/#color-palette">color palette</a>.</li>
|
||||
<li>The <a href="/user-guide/design-systems/assets/#asset-types">assets library</a>.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video muted="" playsinline="" controls="" width="100%" poster="/img/styling/color-library.webp" height="auto">
|
||||
@@ -39,7 +40,7 @@ desc: Style your designs with Penpot's options! Learn about color fills, gradien
|
||||
</figure>
|
||||
|
||||
<h3>Remove a fill</h3>
|
||||
<p>To <strong>remove a fill</strong> from a selected object, press the “-” button in the fill section.</p>
|
||||
<p>To <strong>remove a fill</strong> from a selected layer, press the “-” button in the fill section.</p>
|
||||
<figure>
|
||||
<img alt="Multiple fills" src="/img/styling/fill-remove.webp"/>
|
||||
</figure>
|
||||
@@ -50,7 +51,7 @@ desc: Style your designs with Penpot's options! Learn about color fills, gradien
|
||||
<img alt="Color picker" src="/img/styling/color-picker.webp"/>
|
||||
</figure>
|
||||
<ol>
|
||||
<li><strong>Eyedropper</strong> - Allows you to pick any color of the objects at the viewport.</li>
|
||||
<li><strong>Eyedropper</strong> - Allows you to pick any color of the layers at the viewport.</li>
|
||||
<li><strong>Color profiles</strong> - Select between RGB, the Harmony Wheel or HSV.</li>
|
||||
<li><strong>Color type</strong> - Solid, gradient, or image.</li>
|
||||
<li><strong>Sliders</strong> - Easily manage settings like brightness, saturation or opacity.</li>
|
||||
@@ -59,7 +60,7 @@ desc: Style your designs with Penpot's options! Learn about color fills, gradien
|
||||
<li><strong>Color palette</strong> - A quick launcher of the palette with the selected library.</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="color-picker-gradients">Gradients</h3>
|
||||
<h2 id="color-picker-gradients">Gradients</h2>
|
||||
<p>You can apply gradient fills to layers. To do that select the Gradient type at the color picker.</p>
|
||||
<figure>
|
||||
<img alt="Gradient" src="/img/styling/color-picker-gradient.webp"/>
|
||||
@@ -86,20 +87,20 @@ desc: Style your designs with Penpot's options! Learn about color fills, gradien
|
||||
<p>Use the <strong>menu</strong> to easily switch between libraries.</p>
|
||||
<p>Switch between big and small <strong>thumbnails sizes</strong>.</p>
|
||||
|
||||
<h3 id="color-palette">Applying color from the palette</h3>
|
||||
<h3>Applying color from the palette</h3>
|
||||
<ul>
|
||||
<li><strong>Apply color to fill:</strong> Just select the shape and click on the preferred bullet in the color palette.</li>
|
||||
<li><strong>Apply color to stroke:</strong> Press <kbd>Alt</kbd> (or <kbd>⌥</kbd> in mac OS) while clicking.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="selected-colors">Selected colors</h2>
|
||||
<p>All of the colors that are contained within a selection of objects are showcased at the sidebar so you can play with the colors of a group without the hassles of individual selection.</p>
|
||||
<p>All of the colors that are contained within a selection of layers are showcased at the sidebar so you can play with the colors of a group without the hassles of individual selection.</p>
|
||||
<figure>
|
||||
<img alt="Selected colors" src="/img/styling/color-selected.webp"/>
|
||||
</figure>
|
||||
|
||||
<h2 id="strokes">Strokes</h2>
|
||||
<p>Strokes can be added to most of the objects at Penpot (rectangles, ellipses, boards, curves and images).</p>
|
||||
<p>Strokes can be added to most of the layers at Penpot (rectangles, ellipses, boards, curves and images).</p>
|
||||
<figure>
|
||||
<video title="strokes" muted="" playsinline="" controls="" width="100%" poster="/img/styling/stroke.webp" height="auto">
|
||||
<source src="/img/styling/stroke.mp4" type="video/mp4">
|
||||
@@ -136,86 +137,4 @@ desc: Style your designs with Penpot's options! Learn about color fills, gradien
|
||||
<li><strong>Square</strong> - Adds a rectangular ending to the end of the path.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="radius">Border radius</h2>
|
||||
<p>You can customize the border radius of rectangles and images, with the option to customize each corner individually.</p>
|
||||
<figure>
|
||||
<video title="Border radius" muted="" playsinline="" controls="" width="100%" poster="/img/styling/corners.webp" height="auto">
|
||||
<source src="/img/styling/corners.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
|
||||
<h2 id="shadow">Shadow</h2>
|
||||
<p>Adding shadows is easy from the design panel. You can add as many as you want.</p>
|
||||
<figure>
|
||||
<img alt="Layer shadows" src="/img/styling/shadow.webp"/>
|
||||
</figure>
|
||||
<p>Shadow options are:</p>
|
||||
<ul>
|
||||
<li><strong>Type</strong> - Drop (outside the layer), inner (inside the layer)</li>
|
||||
<li><strong>Horizontal position</strong> (X)</li>
|
||||
<li><strong>Vertical position</strong> (Y)</li>
|
||||
<li><strong>Blur</strong></li>
|
||||
<li><strong>Spread</strong></li>
|
||||
<li><strong>Color and opacity</strong></li>
|
||||
</ul>
|
||||
|
||||
<h2 id="blur">Blur</h2>
|
||||
<p>You can set a blur for each and every object at Penpot.</p>
|
||||
<p><strong></strong>Applying a lot and/or big values for blurs can affect Penpot’s performance as it requires a lot from the browser.</p>
|
||||
<figure>
|
||||
<video title="Apply blur to a layer" muted="" playsinline="" controls="" width="100%" poster="/img/styling/blur.webp" height="auto">
|
||||
<source src="/img/styling/blur.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="blend">Opacity and blend</h2>
|
||||
<p>Set the overal opacity for layers and their blend mode.</p>
|
||||
<p>Blend allows you to control how a layer interacts with the layers beneath it, determining how pixels from the current layer are combined with pixels in the underlying layers. Use blend to achive various effects, such as shading, highlights, or creative visual styles.</p>
|
||||
<figure>
|
||||
<img alt="Layer blend and opacity" src="/img/styling/blend-opacity.webp"/>
|
||||
</figure>
|
||||
<p>Blend options available:</p>
|
||||
<ul>
|
||||
<li><strong>Normal</strong></li>
|
||||
<li><strong>Darken</strong></li>
|
||||
<li><strong>Multiply</strong></li>
|
||||
<li><strong>Color burn</strong></li>
|
||||
<li><strong>Lighten</strong></li>
|
||||
<li><strong>Screen</strong></li>
|
||||
<li><strong>Color dodge</strong></li>
|
||||
<li><strong>Overlay</strong></li>
|
||||
<li><strong>Soft light</strong></li>
|
||||
<li><strong>Hard light</strong></li>
|
||||
<li><strong>Difference</strong></li>
|
||||
<li><strong>Exclusion</strong></li>
|
||||
<li><strong>Hue</strong></li>
|
||||
<li><strong>Saturation</strong></li>
|
||||
<li><strong>Color</strong></li>
|
||||
<li><strong>Luminosity</strong></li>
|
||||
</ul>
|
||||
|
||||
<h2 id="copy-paste-properties">Copy/Paste properties</h2>
|
||||
<p>You can copy and apply properties, including fills, strokes, shadows, and others from one layer to another—or multiple layers with just a few clicks. You can do it using the layer's menu or shortcuts.</p>
|
||||
|
||||
<figure>
|
||||
<video title="Apply blur to a layer" muted="" playsinline="" controls="" width="100%" poster="/img/styling/copy-properties.webp" height="auto">
|
||||
<source src="/img/styling/copy-properties.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<p>Using the layer menu</p>
|
||||
<ol>
|
||||
<li>Select one layer.</li>
|
||||
<li>Right click to show the layer menu.</li>
|
||||
<li>Press <strong>Copy/Paste as... > Copy properties</strong>.</li>
|
||||
<li>Select one or more other layers.</li>
|
||||
<li>Right click to show the layer/s menu.</li>
|
||||
<li>Press <strong>Copy/Paste as... > Paste properties</strong>.</li>
|
||||
</ol>
|
||||
|
||||
<p>Using Shortcuts</p>
|
||||
<ul>
|
||||
<li><strong>Copy properties</strong>: <kbd>Ctrl/⌘</kbd> + <kbd>Alt/⌥</kbd> + <kbd>C</kbd></li>
|
||||
<li><strong>Paste properties</strong>: <kbd>Ctrl/⌘</kbd> + <kbd>Alt/⌥</kbd> + <kbd>V</kbd></li>
|
||||
</ul>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 08· Flexible Layouts
|
||||
title: Flexible Layouts
|
||||
order: 6
|
||||
desc: Master responsive web design with Penpot's flexible and grid layouts! Learn Flexbox and CSS Grid standards. Explore tutorials, properties, and more.
|
||||
---
|
||||
|
||||
@@ -14,7 +15,7 @@ desc: Master responsive web design with Penpot's flexible and grid layouts! Lear
|
||||
To help you learn the fundamentals of Flex Layout <a href="https://penpot.app/design/layout" target="_blank">here’s a dedicated website</a> where you will find a <strong>video tutorial</strong> and a <strong>playground template</strong>.
|
||||
</p>
|
||||
|
||||
<h3 id="layouts-flex-css">Flex Layout is based on Flexbox CSS standard</h3>
|
||||
<h3>Flex Layout is based on Flexbox CSS standard</h3>
|
||||
<p>Penpot's Flex Layout is built over Flexbox, a CSS module that provides a more efficient way to lay out, align and distribute space among items in a container. There are many comprehensive explanations about Flexbox, if you are interested we recommend you to read the one at <a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/" target="_blank">CSS Tricks</a>.</p>
|
||||
<figure>
|
||||
<p><img src="/img/csstricks-00-basic-terminology.svg" alt="Flex Layout" /></p>
|
||||
@@ -32,8 +33,8 @@ desc: Master responsive web design with Penpot's flexible and grid layouts! Lear
|
||||
<figure><img src="/img/flexible-layouts/layouts-add.webp" alt="Adding Layouts" /></figure>
|
||||
|
||||
|
||||
<h3 id="layouts-flex-arrange-reorder">Arrange and reorder objects to a Flex Layout</h3>
|
||||
<p>To add an object to a Flex Layout you can just drag it at the position of your choice. You can also create or paste elements like in any regular board.</p>
|
||||
<h3 id="layouts-flex-arrange-reorder">Arrange and reorder layers to a Flex Layout</h3>
|
||||
<p>To add a layer to a Flex Layout you can just drag it at the position of your choice. You can also create or paste elements like in any regular board.</p>
|
||||
<p>To reorder elements you can drag them or use the <kbd>UP/DOWN</kbd> keystrokes.</p>
|
||||
<figure>
|
||||
<video title="A video showing a layer being dragged to and moved through a flex flow" muted="" playsinline="" controls="" width="100%" poster="/img/flexible-layouts/layouts-flex-arrange.webp" height="auto">
|
||||
@@ -57,7 +58,7 @@ desc: Master responsive web design with Penpot's flexible and grid layouts! Lear
|
||||
<li><strong>Sizing:</strong> Fix/fit width, Fix/fit height.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="layouts-flex-elements">Positioning Flex elements</h3>
|
||||
<h3 id="layouts-flex-elements">Positioning flex elements</h3>
|
||||
<h4>Position static:</h4>
|
||||
<p>Static position is the default option for flex elements, meaning that they will be included in the flex flow, using flex properties.</p>
|
||||
<figure><img src="/img/flexible-layouts/flex-properties-element.webp" alt="Flex Layout properties" /></figure>
|
||||
@@ -94,7 +95,7 @@ desc: Master responsive web design with Penpot's flexible and grid layouts! Lear
|
||||
|
||||
|
||||
<h3 id="layouts-flex-code">Get code and specifications</h3>
|
||||
<p>Designing with Flex Layout generates <em>ready for production</em> code. Select the flex board or its inner elements and then open the <a href="/user-guide/inspect" target="_blank">Inspect tab</a> to obtain its properties, detailed info and raw code.</p>
|
||||
<p>Designing with Flex Layout generates <em>ready for production</em> code. Select the flex board or its inner elements and then open the <a href="/user-guide/dev-tools/#inspect-design" target="_blank">Inspect tab</a> to obtain its properties, detailed info and raw code.</p>
|
||||
<figure><img src="/img/flexible-layouts/layouts-flex-code.gif" alt="Inspecting code at Penpot" /></figure>
|
||||
|
||||
<h3 id="layouts-flex-examples">Flex Layout basic examples</h3>
|
||||
@@ -132,7 +133,7 @@ desc: Master responsive web design with Penpot's flexible and grid layouts! Lear
|
||||
<figcaption>Rearranging cells in Grid Layout</figcaption>
|
||||
</figure>
|
||||
|
||||
<h3 id="layouts-flex-css">Grid Layout is based on CSS Grid standard</h3>
|
||||
<h3>Grid Layout is based on CSS Grid standard</h3>
|
||||
<p>Penpot's Grid Layout is built over CSS Grid, a fairly new CSS module. If you are interested to know more about this CSS module we recommend checking out the comprehensive explanation <a href="https://css-tricks.com/snippets/css/complete-guide-grid/" target="_blank">Guide to CSS Grid</a> at CSS Tricks.</p>
|
||||
|
||||
|
||||
@@ -274,7 +275,7 @@ desc: Master responsive web design with Penpot's flexible and grid layouts! Lear
|
||||
<p>To turn areas back to regular cells, just select the "Auto" option at the grid cell properties (right sidebar).</p>
|
||||
|
||||
<h3 id="layouts-grid-code">Grid code and specifications</h3>
|
||||
<p>Grid layout at Penpot behaves just like CSS Grid because it is actually using the CSS Grid standard. This means that you can just switch to <a href="/user-guide/inspect" target="_blank">Inspect mode</a>, get the code and use it in real websites.</p>
|
||||
<p>Grid layout at Penpot behaves just like CSS Grid because it is actually using the CSS Grid standard. This means that you can just switch to <a href="/user-guide/dev-tools/#inspect-design" target="_blank">Inspect mode</a>, get the code and use it in real websites.</p>
|
||||
<figure><img src="/img/flexible-layouts/layouts-grid-code.gif" alt="Inspecting code at Penpot" /></figure>
|
||||
|
||||
|
||||
40
docs/user-guide/designing/index.njk
Normal file
40
docs/user-guide/designing/index.njk
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Designing
|
||||
order: 2
|
||||
desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorials. Learn the interface, layers, objects, styling, and more.
|
||||
---
|
||||
|
||||
<h1 id="section-2">Designing</h1>
|
||||
|
||||
<ul class="intro-sections">
|
||||
<li>
|
||||
<a href="/user-guide/designing/workspace-basics">
|
||||
<h2>Workspace basics →</h2>
|
||||
<p>Workspace basics</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/designing/layers">
|
||||
<h2>Layers →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/designing/color-stroke/">
|
||||
<h2>Color & Strokes→</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/designing/text-typo">
|
||||
<h2>Text & Typography→</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/designing/flexible-layouts">
|
||||
<h2>Flexible layouts →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
519
docs/user-guide/designing/layers.njk
Normal file
519
docs/user-guide/designing/layers.njk
Normal file
@@ -0,0 +1,519 @@
|
||||
---
|
||||
title: Layers
|
||||
order: 2
|
||||
desc: "Work with Penpot's layers: boards, shapes, text, paths, and graphics. Learn to create, select, rename, and customize boards for optimal workflow."
|
||||
---
|
||||
|
||||
<h1 id="layers">Layers</h1>
|
||||
<p class="main-paragraph">Layers are objects or items that you can place in the viewport. Boards, shapes, texts, paths and graphics are layers. The following describes the different layers that you have
|
||||
available in Penpot, and how to get the most of them.</p>
|
||||
|
||||
<h2 id="available-layers">Available layers</h2>
|
||||
|
||||
<h3 id="Boards">Boards</h3>
|
||||
<p>Boards are layers that serve as your high-level containers for content organization and layout. Boards are useful if you want to design for a specific screen or print size. Boards can contain other boards. First level boards
|
||||
are shown by default at the <a href="/user-guide/prototyping-testing/testing-view-mode/">View mode</a>, acting as screens of a design or pages of a document. Also, layers inside boards can be clipped. Boards are a powerful element at Penpot, opening up a ton of possibilities when creating and organizing your designs.</p>
|
||||
|
||||
<h4>Create boards</h4>
|
||||
<p>To create a board, use the board tool at the toolbar or the shortcut <kbd>B</kbd>.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-tool.webp" alt="Board tool">
|
||||
</figure>
|
||||
|
||||
<p>Then, with the board tool selected, you have two options:</p>
|
||||
<ul>
|
||||
<li><strong>Select a board size upfront</strong>. You can choose one of the provided presets with the most common resolution for devices and standard print sizes</li>
|
||||
<li><strong>Click-and-drag</strong> to draw a frame of approximate size, then immediately edit its width/height values to be precise.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Create board" muted="" playsinline="" controls="" width="100%" poster="/img/objects/board-create.webp" height="auto">
|
||||
<source src="/img/objects/board-creation.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<p><strong>TIP:</strong> Create a board around one or more selected layers using the option "Selection to board" at the menu or the shortcut <kbd>Ctrl/⌘</kbd> + <kbd>Alt</kbd> + <kbd>G</kbd>.</p>
|
||||
|
||||
<h4>Select boards</h4>
|
||||
<p>There are two different cases in terms of selecting boards:</p>
|
||||
<ul>
|
||||
<li>For first level boards that have at least one inside, click on the board name or <kbd>Ctrl/⌘</kbd> + click on the board area to select it and then drag</li>
|
||||
<li>For the rest (empty first level boards and inside boards) just click to select.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Select board" muted="" playsinline="" controls="" width="auto" poster="/img/objects/board-select.webp" height="auto">
|
||||
<source src="/img/objects/board-select.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Rename boards</h4>
|
||||
<p>There several ways to rename boards:</p>
|
||||
<ul>
|
||||
<li>Double click on the board name at the workspace viewport.</li>
|
||||
<li>Double click on the board name at the layers panel.</li>
|
||||
<li>Press <kbd>Alt/⌥</kbd> + <kbd>N</kbd> to rename the board at the layers panel.</li>
|
||||
<li>Right click to show the menu and select "Rename".</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Rename board" muted="" playsinline="" controls="" width="auto" poster="/img/objects/board-rename.webp" height="auto">
|
||||
<source src="/img/objects/board-rename.webm" type="video/webm">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Set board as thumbnail</h4>
|
||||
<p>At the workspace, select a specific board to be the file thumbnail that will be shown at the dashboard in the file card.</p>
|
||||
<p>To set a custom thumbnail:</p>
|
||||
<ol>
|
||||
<li>Select a board.</li>
|
||||
<li>Right click to show the menu and select "Set as thumbnail" or press <kbd>Shift</kbd> <kbd>T</kbd>.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Set board as thumbnail" muted="" playsinline="" controls="" width="auto" poster="/img/objects/board-thumbnail.webp" height="auto">
|
||||
<source src="/img/objects/board-thumbnail.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Copy link to board</h4>
|
||||
<p>You can get the link to each individual board, making it easy to share them with team members or include direct links in documentation.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-copy-link.webp" alt="copy link to board">
|
||||
</figure>
|
||||
<p>There are two ways to copy a direct link to a board:</p>
|
||||
<ul>
|
||||
<li>Using the menu: Select the board, right click and select the "Copy link" option.</li>
|
||||
<li>Using the shortcut: Select the board and press <kbd>Shift/⇧</kbd> + <kbd>Alt/⌥</kbd> + <kbd>C</kbd>.</li>
|
||||
</ul>
|
||||
|
||||
<h4>Clip content</h4>
|
||||
<p>Boards offer the option to clip its content (or not).</p>
|
||||
<figure>
|
||||
<video title="Clip board" muted="" playsinline="" controls="" width="100%" poster="/img/objects/board-clip.webp" height="auto">
|
||||
<source src="/img/objects/board-clip.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Show in View mode</h4>
|
||||
<p>Boards offer the option to be shown as a separate board/screen in the <a href="/user-guide/first-steps/the-interface/#interface-viewmode">View mode</a>. Use this setting to decide what boards should be shown as individual items in your presentations.</p>
|
||||
<p><strong>Defaults</strong></p>
|
||||
<p>As it is very likely that the first level boards will be used as a screen and the interiors will not, there are different defaults for newly created boards.</p>
|
||||
<ul>
|
||||
<li>Boards created at first level (<a href="/user-guide/designing/workspace-basics/#viewport">the viewport</a>): shown by default.</li>
|
||||
<li>Boards created inside other boards: not shown by default.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/objects/board-show.webp" alt="board show in view mode">
|
||||
</figure>
|
||||
|
||||
<h4>Show fill in exports</h4>
|
||||
<p> Sometimes you don’t need the artboards to be part of your designs, but only their support to work on them.
|
||||
Penpot allows you to decide if the fill of an artboard will be shown in exports, you just have to check/uncheck the "Show in exports" option which is below the fill setting.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-fill.webp" alt="show board fill in exports">
|
||||
</figure>
|
||||
|
||||
<h4>Resize board to fit to content</h4>
|
||||
<p>You can adjust the board size to fit its content by clicking the icon in the design sidebar.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-fit.webp" alt="Resize board to fit to content button">
|
||||
</figure>
|
||||
|
||||
<h4>Board guides</h4>
|
||||
<p>You can set guides on boards that will assist with aligning layers.</p>
|
||||
<p>Read more about <a href="/user-guide/designing/workspace-basics/#guides">guides</a>.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-guides.webp" alt="board guides">
|
||||
</figure>
|
||||
|
||||
<h4>Prototyping boards</h4>
|
||||
<p>You can connect boards with other boards to create rich interactions.</p>
|
||||
<p>Read more about <a href="/user-guide/prototyping-testing/prototyping/">prototyping</a>.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-prototyping.webp" alt="prototyping with boards">
|
||||
</figure>
|
||||
|
||||
<h3 id="rectangles-ellipses">Rectangles and ellipses</h3>
|
||||
<p>Rectangle and ellipses are two basic “primitive” geometric shapes that are useful when starting
|
||||
a design.</p>
|
||||
<p>The shortcut keys are <kbd>E</kbd> for ellipses and <kbd>R</kbd> for rectangles.</p>
|
||||
<p>To find out more about how to edit and modify these shapes go to <a href="/user-guide/designing/layers/#layer-actions">Layer basics</a>.</p>
|
||||
<figure>
|
||||
<video title="Rectangles and ellipses" muted="" playsinline="" controls="" width="auto" poster="/img/objects/rectangles-ellipses.webp" height="auto">
|
||||
<source src="/img/objects/rectangles-ellipses.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="text">Text</h3>
|
||||
<p> (NOTA: El grosso de este contenido está en su propia sección. Aquí vendría un texto introductorio y un link a la <a href="/user-guide/designing/text-typo/">sección en cuestión</a>. )</p>
|
||||
|
||||
<h3 id="curves">Curves (freehand)</h3>
|
||||
<p>The curve tool allows a path to be created directly in a freehand mode.
|
||||
Select the curve tool by clicking on the icon at the toolbar or pressing <kbd>Shift/⇧</kbd> + <kbd>c</kbd>.
|
||||
<p>The path created will contain a lot of points, but it is edited the same way as any other curve.</p>
|
||||
|
||||
<h3 id="paths">Paths (bezier)</h3>
|
||||
<p>A path is composed of two or more nodes and the line segments between them, which may also be curved. To draw a new path you have to select the path tool by clicking on the icon at the toolbar or pressing <kbd>P</kbd>. Then you have two ways to create the path:</p>
|
||||
<ol>
|
||||
<li><strong>Click</strong> to create a new corner node.</li>
|
||||
<li><strong>Click and drag</strong> to create a curved node.</li>
|
||||
</ol>
|
||||
<p>To finish the path:</p>
|
||||
<ol>
|
||||
<li><strong>Close it</strong> clicking over the starting node.</li>
|
||||
<li><strong>Leave it open</strong> pressing <kbd>Esc</kbd> or <kbd>Enter</kbd> to stop editing. Then press <kbd>Esc</kbd> to exit the edit mode.</li>
|
||||
</ol>
|
||||
<p><strong>Tip:</strong> If you hold <kbd>Shift/⇧</kbd> while adding nodes the angle between the current and the next will change in 45 degree increments.</p>
|
||||
<figure>
|
||||
<video title="Paths" muted="" playsinline="" controls="" width="100%" poster="/img/objects/path-create.webp" height="auto">
|
||||
<source src="/img/objects/path-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Edit nodes</h4>
|
||||
<p>To edit a node double click on a path or select and press <kbd>Enter</kbd>.
|
||||
You can choose to edit individual nodes or create new ones. Press <kbd>Esc</kbd> to exit node edition. </p>
|
||||
<figure>
|
||||
<video title="Edit nodes" muted="" playsinline="" controls="" width="100%" poster="/img/objects/nodes-edit.webp" height="auto">
|
||||
<source src="/img/objects/nodes-edit.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure><h4>Node types</h4>
|
||||
<p>There are two types of nodes: curve or corner (straight). The type of a selected node can be changed at the bezier menu. Curved nodes have bezier handles that allow the curvature of a path to be modified.</p>
|
||||
<figure>
|
||||
<video title="Node types" muted="" playsinline="" controls="" width="100%" poster="/img/objects/node-types.webp" height="auto">
|
||||
<source src="/img/objects/node-types.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="images">Images</h3>
|
||||
<h4>Insert images</h4>
|
||||
<p>There are several options for inserting an image into a Penpot file:</p>
|
||||
<ul>
|
||||
<li>Use the <strong>image tool</strong> at the toolbar or press <kbd>K</kbd> to insert images in your file system.</li>
|
||||
<li><strong>Drag</strong> an image from your computer to the viewport.</li>
|
||||
<li>Copy an image & paste it or drag it right from a <strong>browser</strong>.</li>
|
||||
<li>Drag an image from a Penpot <strong>library</strong>.</li>
|
||||
</ul>
|
||||
|
||||
<h4>Images aspect ratio</h4>
|
||||
<p>Images fill the layer backgrounds by default, so they take up the entire layer while maintaining the aspect ratio. This is great for flexible designs because the images can adapt to different sizes.</p>
|
||||
<p>However, if you don't want an image to keep its aspect ratio when resizing, you just have to uncheck the option in the image settings.</p>
|
||||
<figure>
|
||||
<video title="Changing the aspect ratio of an image" muted="" playsinline="" controls="" width="100%" poster="/img/objects/image-ratio.webp" height="auto">
|
||||
<source src="/img/objects/image-ratio.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="layer-actions">Layer actions</h2>
|
||||
|
||||
<h3 id="creating-layers">Create</h3>
|
||||
<p>To create a layer you have to select the type of layer by clicking the selected tool (board, rectangle, ellipse, text, image, path or curve) at the toolbar. Then you usually have to click and drag your mouse on the viewport. </p>
|
||||
<p>Hold <kbd>Shift/⇧</kbd> while creating an ellipse or a rectangle to maintain equal width and height.</p>
|
||||
<figure>
|
||||
<video title="Layers create" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-create.webp" height="auto">
|
||||
<source src="/img/layers/layers-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="duplicating-layers">Duplicate</h3>
|
||||
<p>There are several ways to duplicate a layer:</p>
|
||||
<ol>
|
||||
<li>You can press <kbd>Ctrl/⌘</kbd> + <kbd>D</kbd> to duplicate a layer right over a selected layer. </li>
|
||||
<li>If you press right click over a selected layer at the viewport or at the layers panel you can use the option at the layer menu. </li>
|
||||
<li>You can also select a layer and drag while pressing <kbd>Alt/⌥</kbd> so you can simultaneously duplicate and drag the new layer.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Duplicate layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-duplicate.webp" height="auto">
|
||||
<source src="/img/layers/layers-duplicate.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="delete-layers">Delete</h3>
|
||||
<p>There are a couple ways to delete a layer. </p>
|
||||
<ol>
|
||||
<li>You can press <kbd>Supr/⌫</kbd> to delete a selected layer. </li>
|
||||
<li>If you press right click over a selected layer at the viewport or at the layers panel you can use the option at the layer menu.</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="move-layers">Move</h3>
|
||||
<p>To move one or more layers on the viewport you have to select them first and then click and drag the selection where you want to place them. You can also use the design panel to set a precise position relative to the viewport or the board.</p>
|
||||
<figure>
|
||||
<video title="Move layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-move.webp" height="auto">
|
||||
<source src="/img/layers/layers-move.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="select-layers">Select</h3>
|
||||
<p>The simplest way to select a layer is to click on it. Make sure that you have the “move” pointer selected at the toolbar. </p>
|
||||
<p>To select multiple layers you can click and drag around the layers you want to select. You can also click more than one layer while pressing <kbd>Shift/⇧</kbd>. If you hold <kbd>Shift/⇧</kbd> and click you can deselect layers individually.</p>
|
||||
<figure>
|
||||
<video title="Select layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-select.webp" height="auto">
|
||||
<source src="/img/layers/layers-select.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h4>Selecting layers at the layers panel</h4>
|
||||
<ol>
|
||||
<li>Click a layer to do a single selection.</li>
|
||||
<li>Press <kbd>Ctrl/⌘</kbd> while clicking two or more layers to do a multiple selection.</li>
|
||||
<li>If you press <kbd>Shift/⇧</kbd> while selecting two or more layers all the layers within the selection area will be selected.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Select layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-multiselect.webp" height="auto">
|
||||
<source src="/img/layers/layers-multiselect.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<h4>Select layers ignoring groups (deep selection)</h4>
|
||||
<p>If you want to select an element that is difficult to reach because it is under a group of elements, hold <kbd>Ctrl/⌘</kbd> to make the selection ignore group areas and treat all the layers as being at the same level.</p>
|
||||
<figure>
|
||||
<video title="Select layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-deepselect.webp" height="auto">
|
||||
<source src="/img/layers/layers-deepselect.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<h4>Select layers inside groups</h4>
|
||||
<p>To <strong>select a layer inside a group</strong> you do double click. First click selects the group, second click selects a layer.</p>
|
||||
|
||||
<h4>Select layer menu</h4>
|
||||
<p>At the dropdown menu (right click on a layer to show it) there's the option "Select layer" that allows the user to select one layer among the ones that are under the cursor's location.</p>
|
||||
<p><img src="/img/layers-select-menu.gif" alt="layers select" /></p>
|
||||
|
||||
<h3 id="hide-lock">Hide and lock layers</h3>
|
||||
<h4>Hide and show layers</h4>
|
||||
<p>You can control the visibility of any layer by clicking the eye icon next to it in the Layers panel. When a layer is hidden, it will not appear on the canvas, but you can still select it in the Layers panel, move its order, or modify its properties. The eye icon always indicates whether a layer is visible or hidden, making it easy to manage complex designs.</p>
|
||||
|
||||
<h4>Lock and unlock</h4>
|
||||
<p>Locking a layer helps prevent accidental changes or movement on the canvas. When a layer is locked, it cannot be moved or edited directly in the canvas area. However, you can still select a locked layer in the Layers panel and adjust its properties, such as color, effects, or name. The lock icon next to the layer’s name shows its locked status, helping you keep your design organized and protected.</p>
|
||||
|
||||
<figure>
|
||||
<video title="Layers hide and lock" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-hide-lock.webp" height="auto">
|
||||
<source src="/img/layers/layers-hide-lock.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="group-layers">Group</h3>
|
||||
<p>Grouped layers can be moved, transformed or styled at the same time. </p>
|
||||
<ul>
|
||||
<li><strong>Group:</strong> To group two or more layers, select them and then press <kbd>Ctrl/⌘</kbd> + <kbd>G</kbd>. You can also use the option at the layers menu that you can open with right click.</li>
|
||||
<li><strong>Ungroup:</strong> Press <kbd>Shift/⇧</kbd> + <kbd>G</kbd> or use the option at the layers menu that you can open with right click over the selected group.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Group layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-group.webp" height="auto">
|
||||
<source src="/img/layers/layers-group.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="mask-layers">Mask</h3>
|
||||
<p>A mask is a layer that does a clipping and only shows parts of a layer or multiple layers that fall within its shape. </p>
|
||||
<ul>
|
||||
<li><strong>Mask layers:</strong> Select more than one layer or a group of them. Then you can apply the masking using the option at the layers menu or by pressing <kbd>Ctrl/⌘</kbd> + <kbd>M</kbd>. The shape that is at the lowest level at the layer list will be used as a mask. </li>
|
||||
<li><strong>Unmask layers:</strong> Select a mask and then press <kbd>Shift/⇧</kbd> + <kbd>Ctrl/⌘</kbd> + <kbd>M</kbd> or use the option at the layers menu.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Mask layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-mask.webp" height="auto">
|
||||
<source src="/img/layers/layers-mask.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="resize-layers">Resize</h3>
|
||||
<p>To resize a selected layer you can use the handles at the edges of the selection box. Make sure the cursor is in resizing mode. You can also use the design panel where you can link width and height.</p>
|
||||
<ul>
|
||||
<li>Hold <kbd>Shift/⇧</kbd> while resizing the layer to preserve its aspect ratio.</li>
|
||||
<li>Hold <kbd>Alt/⌥</kbd> while resizing the layer to do it from the center and resize simultaneously two opposite sides.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Resize layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-resize.webp" height="auto">
|
||||
<source src="/img/layers/layers-resize.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="scale-elements">Scale elements, texts and properties</h3>
|
||||
<p>Activate the scale tool by pressing <kbd>K</kbd> or from the main file menu to scale elements while maintaining their visual aspect. Once it is activated you can resize texts, layers and groups and preserve their aspect ratio while scaling their properties proportionally, including strokes, shadows, blurs and corners.
|
||||
<figure>
|
||||
<video title="Scale layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-scale.webp" height="auto">
|
||||
<source src="/img/layers/layers-scale.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="rotate-layers">Rotate</h3>
|
||||
<p>To rotate selected layers you can use the handles at the edges of the selection box. Make sure the cursor is in rotation mode. If you hold <kbd>Ctrl/⌘</kbd> while rotation the angle will change in 45 degree increments. You can also find this option at the design panel.</p>
|
||||
<figure>
|
||||
<video title="Rotate layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-rotate.webp" height="auto">
|
||||
<source src="/img/layers/layers-rotate.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="flip-layers">Flip</h3>
|
||||
<p>You can find the options to flip layers in their contextual menu (select the layer and right click). You also have shortcuts to do this:</p>
|
||||
<ul>
|
||||
<li><strong>Flip layers horizontally:</strong> Select the layer and press <kbd>Shift/⇧</kbd> + <kbd>H</kbd></li>
|
||||
<li><strong>Flip layers vertically:</strong> Select the layer and then press <kbd>Shift/⇧</kbd> + <kbd>V</kbd>.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Flip layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-flip.webp" height="auto">
|
||||
<source src="/img/layers/layers-flip.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="aling-distribute-layers">Align and distribute</h3>
|
||||
<p>Aligning and distributing layers can be found at the top of the Design panel. </p>
|
||||
<h4>Align layers</h4>
|
||||
<p>Aligning will move all the selected layers to a position relative to one of them. For instance, aligning top will align the elements with the edge of the top-most element.</p>
|
||||
<figure>
|
||||
<video title="Align layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-align.webp" height="auto">
|
||||
<source src="/img/layers/layers-align.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<h4>Distribute layers</h4>
|
||||
<p>Distributing layers to position them vertically and horizontally with equal distances between them.</p>
|
||||
<figure>
|
||||
<video title="Distribute layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-distribute.webp" height="auto">
|
||||
<source src="/img/layers/layers-distribute.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="boolean-operators">Boolean operators</h3>
|
||||
<p>It is possible to combine shapes in a group in different ways to create more complex layers by using
|
||||
"boolean" operators. Boolean operators are non destructive and the original shapes remain grouped and available for more editing. There are five boolean operations available: union, difference, intersection, exclusion and flatten. Using boolean operations allows many graphic options and possibilities for your designs.</p>
|
||||
<figure>
|
||||
<video title="Boolean operators" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-boolean.webp" height="auto">
|
||||
<source src="/img/layers/layers-boolean.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<ul>
|
||||
<li><strong>Union:</strong> the resulting combination is the sum of the shapes.</li>
|
||||
<li><strong>Difference:</strong> the opposite of union, the resulting layer has the area of the shape on top has been cut out from the shape at the bottom.</li>
|
||||
<li><strong>Intersection:</strong> creates a group whose shape is the overlapping area between the shapes.</li>
|
||||
<li><strong>Exclusion:</strong> the opposite of intersection, the resulting shape is the area that does not overlap between the shapes.</li>
|
||||
<li><strong>Flatten:</strong> Tecnically not a boolean operator, this is the way to permanently combine the paths of a group of shapes in a single one.</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="constraints">Resizing constraints</h3>
|
||||
<p>Constraints allow you to decide how layers will behave when resizing its container.</p>
|
||||
|
||||
<h4>Apply constraints</h4>
|
||||
<p>Constraints allow you to decide how layers will behave when resizing its parent container. You can apply horizontal and vertical constraints for every layer.</p>
|
||||
<p>To apply constraints select a layer and use the constraints map or the constraints selectors at the design panel.</p>
|
||||
<figure>
|
||||
<video title="Constraints" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-constraints.webp" height="auto">
|
||||
<source src="/img/layers/layers-constraints.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<p>Constraints are set to “Scale” by default, but you have other options.</p>
|
||||
|
||||
<h5>Horizontal constraints</h5>
|
||||
<ul>
|
||||
<li><strong>Left</strong>: The layer maintains its size and position relative to the left side of its parent container.</li>
|
||||
<li><strong>Right</strong>: The layer maintains its size and position relative to the right side of its parent container.</li>
|
||||
<li><strong>Left & right</strong>: The layer resizes while maintaining its distance to both horizontal sides of its parent container.</li>
|
||||
<li><strong>Center</strong>: The layer maintains its size and position relative to the horizontal center of its parent container.</li>
|
||||
<li><strong>Scale</strong>: The layer will horizontally resize proportionally to its parent container size.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/layers/layers-constraints-h.webp" alt="Horizontal constraints">
|
||||
</figure>
|
||||
|
||||
<h5>Vertical constraints</h5>
|
||||
<ul>
|
||||
<li><strong>Top</strong>: The layer maintains its size and position relative to the top side of its parent container.</li>
|
||||
<li><strong>Bottom</strong>: The layer maintains its size and position relative to the bottom side of its parent container.</li>
|
||||
<li><strong>Top & bottom</strong>: The layer resizes while maintaining its distance to both vertical sides of its parent container.</li>
|
||||
<li><strong>Center</strong>: The layer maintains its size and position relative to the vertical center of its parent container.</li>
|
||||
<li><strong>Scale</strong>: The layer will vertically resize proportionally to its parent container size.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/layers/layers-constraints-v.webp" alt="Vettical constraints">
|
||||
</figure>
|
||||
|
||||
|
||||
<h2 id="styling-layers">Styling layers</h2>
|
||||
|
||||
<p>Penpot has a variety of properties for each layer. When selected, the options are displayed in the design panel on the right.</p>
|
||||
|
||||
<h3 id="radius">Border radius</h3>
|
||||
<p>You can customize the border radius of rectangles and images, with the option to customize each corner individually.</p>
|
||||
<figure>
|
||||
<video title="Border radius" muted="" playsinline="" controls="" width="100%" poster="/img/styling/corners.webp" height="auto">
|
||||
<source src="/img/styling/corners.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
|
||||
<h3 id="shadow">Shadow</h3>
|
||||
<p>Adding shadows is easy from the design panel. You can add as many as you want.</p>
|
||||
<figure>
|
||||
<img alt="Layer shadows" src="/img/styling/shadow.webp"/>
|
||||
</figure>
|
||||
<p>Shadow options are:</p>
|
||||
<ul>
|
||||
<li><strong>Type</strong> - Drop (outside the layer), inner (inside the layer)</li>
|
||||
<li><strong>Horizontal position</strong> (X)</li>
|
||||
<li><strong>Vertical position</strong> (Y)</li>
|
||||
<li><strong>Blur</strong></li>
|
||||
<li><strong>Spread</strong></li>
|
||||
<li><strong>Color and opacity</strong></li>
|
||||
</ul>
|
||||
|
||||
<h3 id="blur">Blur</h3>
|
||||
<p>You can set a blur for each and every layer at Penpot.</p>
|
||||
<p><strong></strong>Applying a lot and/or big values for blurs can affect Penpot’s performance as it requires a lot from the browser.</p>
|
||||
<figure>
|
||||
<video title="Apply blur to a layer" muted="" playsinline="" controls="" width="100%" poster="/img/styling/blur.webp" height="auto">
|
||||
<source src="/img/styling/blur.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3 id="blend">Opacity and blend</h3>
|
||||
<p>Set the overal opacity for layers and their blend mode.</p>
|
||||
<p>Blend allows you to control how a layer interacts with the layers beneath it, determining how pixels from the current layer are combined with pixels in the underlying layers. Use blend to achive various effects, such as shading, highlights, or creative visual styles.</p>
|
||||
<figure>
|
||||
<img alt="Layer blend and opacity" src="/img/styling/blend-opacity.webp"/>
|
||||
</figure>
|
||||
<p>Blend options available:</p>
|
||||
<ul>
|
||||
<li><strong>Normal</strong></li>
|
||||
<li><strong>Darken</strong></li>
|
||||
<li><strong>Multiply</strong></li>
|
||||
<li><strong>Color burn</strong></li>
|
||||
<li><strong>Lighten</strong></li>
|
||||
<li><strong>Screen</strong></li>
|
||||
<li><strong>Color dodge</strong></li>
|
||||
<li><strong>Overlay</strong></li>
|
||||
<li><strong>Soft light</strong></li>
|
||||
<li><strong>Hard light</strong></li>
|
||||
<li><strong>Difference</strong></li>
|
||||
<li><strong>Exclusion</strong></li>
|
||||
<li><strong>Hue</strong></li>
|
||||
<li><strong>Saturation</strong></li>
|
||||
<li><strong>Color</strong></li>
|
||||
<li><strong>Luminosity</strong></li>
|
||||
</ul>
|
||||
|
||||
<h3 id="copy-paste-properties">Copy/Paste properties</h3>
|
||||
<p>You can copy and apply properties, including fills, strokes, shadows, and others from one layer to another—or multiple layers with just a few clicks. You can do it using the layer's menu or shortcuts.</p>
|
||||
|
||||
<figure>
|
||||
<video title="Apply blur to a layer" muted="" playsinline="" controls="" width="100%" poster="/img/styling/copy-properties.webp" height="auto">
|
||||
<source src="/img/styling/copy-properties.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<p>Using the layer menu</p>
|
||||
<ol>
|
||||
<li>Select one layer.</li>
|
||||
<li>Right click to show the layer menu.</li>
|
||||
<li>Press <strong>Copy/Paste as... > Copy properties</strong>.</li>
|
||||
<li>Select one or more other layers.</li>
|
||||
<li>Right click to show the layer/s menu.</li>
|
||||
<li>Press <strong>Copy/Paste as... > Paste properties</strong>.</li>
|
||||
</ol>
|
||||
|
||||
<p>Using Shortcuts</p>
|
||||
<ul>
|
||||
<li><strong>Copy properties</strong>: <kbd>Ctrl/⌘</kbd> + <kbd>Alt/⌥</kbd> + <kbd>C</kbd></li>
|
||||
<li><strong>Paste properties</strong>: <kbd>Ctrl/⌘</kbd> + <kbd>Alt/⌥</kbd> + <kbd>V</kbd></li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
89
docs/user-guide/designing/text-typo.njk
Normal file
89
docs/user-guide/designing/text-typo.njk
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Text & Typography
|
||||
order: 5
|
||||
desc: Penpot's guide on custom fonts! Upload, manage, and use custom fonts in Penpot! Enhance your designs with personalised typography.
|
||||
---
|
||||
|
||||
<h1 id="text-typo">Text & Typography</h1>
|
||||
|
||||
<h2 id="text">Text</h2>
|
||||
<p>To insert text you have to activate the text tool by first clicking on the icon at the toolbar or pressing <kbd>T</kbd>. Then you have two ways to create a text layer:</p>
|
||||
<ol>
|
||||
<li><strong>Click</strong> to create a textbox without any specific dimensions.</li>
|
||||
<li><strong>Drag</strong> to create a textbox with a fixed size.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Create text" muted="" playsinline="" controls="" width="auto" poster="/img/objects/text-create.webp" height="auto">
|
||||
<source src="/img/objects/text-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<p><strong>Tips for resizing</strong></p>
|
||||
<ul>
|
||||
<li>Double-click on the right side of the bounding box to set the resize setting to auto-width.</li>
|
||||
<li>Double-click on the bottom side of the bounding box to set the resize setting to auto-height.</li>
|
||||
</ul>
|
||||
<h4>Edit and style text content</h4>
|
||||
<p>Press <kbd>Enter</kbd> with a text layer selected to start editing the text content. You can style parts of the text content as rich text.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/text-edit.webp" alt="editing text">
|
||||
</figure>
|
||||
<h3>Text options</h3>
|
||||
<figure>
|
||||
<img src="/img/objects/text-options.webp" alt="text options">
|
||||
</figure>
|
||||
<ol>
|
||||
<li><strong>Font family.</strong> Penpot includes by default the <a href="https://fonts.google.com/" target=”_blank”>Google Fonts</a> cataloge. You can also <a href="/user-guide/designing/text-typo/#custom-fonts">install your own fonts</a>.</li>
|
||||
<li><strong>Font size.</strong></li>
|
||||
<li><strong>Font type.</strong></li>
|
||||
<li><strong>Line height</strong> (in pixels).</li>
|
||||
<li><strong>Letter spacing</strong> (in pixels).</li>
|
||||
<li><strong>Text case:</strong> none, uppercase, lowercase, titlecase.</li>
|
||||
<li><strong>Horizontal alignment:</strong> left, center, right, justify.</li>
|
||||
<li><strong>Sizing:</strong> auto height, auto width, fixed size.</li>
|
||||
<li><strong>Vertical alignment:</strong> top, center, bottom.</li>
|
||||
<li><strong>Decoration:</strong> none, underline, strikethrough.</li>
|
||||
<li><strong>Direction:</strong> LTR (left to right), RTL (right to left).</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="rtl-support">RTL support</h2>
|
||||
<p>Diversity and inclusion is a major Penpot concern and that's why we love to give support to RTL languages, unlike most design tools.</p>
|
||||
<p>If you write in arabic, hebrew or other RTL language text direction will be automatically detected in text layers.</p>
|
||||
<figure>
|
||||
<img src="/img/layers/layers-rtl.webp" alt="RTL support">
|
||||
</figure>
|
||||
|
||||
<h2 id="custom-fonts">Custom fonts</h2>
|
||||
<p>If you have purchased, personal or libre fonts that are not included in the catalog provided by Penpot, you can upload them from your computer and use them across the files of a team. <p>
|
||||
|
||||
<h3 id="customfonts-upload">Upload local fonts</h3>
|
||||
<p>To use a font that you have on your local machine, first you need to upload it to the Penpot team where you want to use it.</p>
|
||||
<p>You can find the “Fonts” section in the dashboard menu, at the left sidebar.</p>
|
||||
<p><a href="/img/customfonts.png" target="_blank"><img src="/img/customfonts.png" alt="local fonts" /></a></p>
|
||||
|
||||
<h4>To upload a local font:</h4>
|
||||
<ol>
|
||||
<li>Press “Add custom font”.</li>
|
||||
<li>Inspect your local files to select one or more fonts that you want to upload. <strong>You can upload fonts with
|
||||
the following formats: TTF, OTF and WOFF</strong>. Only one format will be needed.</li>
|
||||
<li>Change the font name if needed. The font name is the name that will be shown in the font list at the workspace.
|
||||
It is also what Penpot uses to group fonts in families. You can always edit it later.</li>
|
||||
<li>Once ready, press upload. That's it. The font will be available at the font list of this team’s files.</li>
|
||||
</ol>
|
||||
<p><a href="/img/customfonts-upload.png" target="_blank"><img src="/img/customfonts-upload.png" alt="local fonts" /></a></p>
|
||||
|
||||
<h3 id="customfonts-families">Group fonts in font families</h3>
|
||||
<p>Fonts with the same font family name will be grouped as a single font family. That means that at the font list that you will use at the files they will be shown as only one font with different variants available. </p>
|
||||
<p>If you want to add a font variant (eg: Light) to a font family (eg: Helvetica) you only need to ensure during the upload process that it has the same font family name.</p>
|
||||
<p><a href="/img/customfonts-families.png" target="_blank"><img src="/img/customfonts-families.png" alt="local fonts" /></a></p>
|
||||
|
||||
<h3 id="customfonts-edit">Edit custom fonts</h3>
|
||||
<p>At the right side of a font family of the custom fonts list you can find a menu that allows you to edit the name of a font family and delete it.</p>
|
||||
|
||||
<h3 id="customfonts-using">Using custom fonts</h3>
|
||||
<p>Custom fonts are added to the fonts catalog of a team and can be used at the workspace from the font list at the design sidebar.</p>
|
||||
<p><img src="/img/customfonts-use.gif" alt="local fonts" /></p>
|
||||
|
||||
<h3>Fonts Licensing and Usage</h3>
|
||||
<p>You should only upload fonts you own or have license to use in Penpot. Find out more in the Content rights section of <a href="https://penpot.app/terms" target="_blank">Penpot's Terms of Service</a>. You also might want to read about <a href="https://www.typography.com/faq" target="_blank">font licensing</a>.</p>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: 03· Workspace basics
|
||||
title: Workspace Basics
|
||||
order: 1
|
||||
desc: Master Penpot's workspace basics! Learn interface navigation, zoom tools, dynamic alignment, rulers, guides, and shortcuts.
|
||||
---
|
||||
|
||||
@@ -29,15 +30,81 @@ desc: Master Penpot's workspace basics! Learn interface navigation, zoom tools,
|
||||
<img src="/img/workspace-basics/main-menu.webp" alt="Main menu" />
|
||||
</figure>
|
||||
|
||||
<h2 id="layer-basics">Layers panel</h2>
|
||||
<p>Every layer you create in Penpot’s <a href="/user-guide/designing/workspace-basics/#viewport">viewport</a> is a layer. Rectangles, ellipses, boards or text boxes are layers that you can use to build your design.</p>
|
||||
|
||||
<h3>Pages</h3>
|
||||
<p>Pages allow you to organize layers into separate sections inside a file, and are shown in separate tabs. Subdividing a file into pages gives you the ability to split your file into logically different sections so that you can organise your work. For instance, you can use pages to separate stages of the design process but keep them in the same document. Different screen sizes, features or atomic design categories are other common ways to use pages. </p>
|
||||
|
||||
<p>You can add, remove or rename pages to suit your needs.</p>
|
||||
<figure>
|
||||
<video title="Creating pages" muted="" playsinline="" controls="" width="auto" poster="/img/layers/pages-create.webp" height="auto">
|
||||
<source src="/img/layers/pages-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3>Layers</h3>
|
||||
<p><strong>Layers:</strong> Layers are the different objects that you can place at the design viewport. At the layers panel you can see all the layers of a file page. Drag the layers to arrange them to different positions.</p>
|
||||
<figure>
|
||||
<video title="Layers panel" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-panel.webp" height="auto">
|
||||
<source src="/img/layers/layers-panel.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<h4>Navigate layers using the keyboard</h4>
|
||||
<ul>
|
||||
<li>Select a layer and press top/bottom arrows while pressing <kbd>Ctrl/⌘</kbd> to move them in the layers list.</li>
|
||||
<li>Press <kbd>tab</kbd> to change the layer selection to the next layer.</li>
|
||||
<li>Press <kbd>tab</kbd> + <kbd>Shift/⇧</kbd> to change the layer selection to the previous layer.</li>
|
||||
<li>If the layer contains other layers, press <kbd>Enter</kbd> to select the first layer inside the group and <kbd>Enter</kbd>+ <kbd>Shift/⇧</kbd> to move a level up.</li>
|
||||
</ul>
|
||||
|
||||
<p>Layers are displayed from the bottom to the top of the layer stack, with layers above on the stack being shown on top in the image.</p>
|
||||
|
||||
|
||||
<h3>Search and filter layers</h3>
|
||||
<p>Reach specific layers with a simple search. You can also filter the layers list per layer type (board, group, mask, component, text, image and shape).</p>
|
||||
<figure>
|
||||
<video title="Search and filter layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-search.webp" height="auto">
|
||||
<source src="/img/layers/layers-search.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3>Collapse groups and boards</h3>
|
||||
<p>Groups and boards can have their contents expanded and collapsed. Click on the arrow at the
|
||||
right side to toggle the visibility of their contents. </p>
|
||||
<p>To collapse all the layers, and just display the boards,
|
||||
press <kbd>Shift/⇧</kbd> + left click over the right arrow of a group or a board to collapse them all.</p>
|
||||
<figure>
|
||||
<video title="Collapse layer groups" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-collapse.webp" height="auto">
|
||||
<source src="/img/layers/layers-collapse.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="focus-mode">Focus mode</h2>
|
||||
<p>Focus mode zooms into the elements of a page you want to work with in a specific moment, and hides the rest so that they don’t get in the way. When the page has many elements, focus mode can also improve performance.</p>
|
||||
<p>To activate focus mode:</p>
|
||||
<ol>
|
||||
<li>Select one or more elements.</li>
|
||||
<li>Right click on the selection to show the menu and select the option “Focus on” or press <kbd>F</kbd>.</li>
|
||||
</ol>
|
||||
<p>Notice that the layer panel will now only show the focused layers. A focus mode status line will also appear at the top.</p>
|
||||
<p>To exit focus mode and return to the original viewport and selection, right click anywhere and select “Focus off” or just press <kbd>F</kbd> again. You can also click anywhere on the focus mode status line at the top of the layer panel.
|
||||
</p>
|
||||
<figure>
|
||||
<video title="Focus mode" muted="" playsinline="" controls="" width="100%" poster="/img/layers/layers-focus.webp" height="auto">
|
||||
<source src="/img/layers/layers-focus.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="zoom">Zoom</h2>
|
||||
<h4>Zoom menu</h4>
|
||||
<h3>Zoom menu</h3>
|
||||
<p>To zoom in and out hold <kbd>Ctrl</kbd> (or <kbd>⌘</kbd> if using macOS) and use the scroll wheel on your mouse. You also have a bunch of useful shortcuts for the most common zoom levels that you can find at the zoom menu in the navigation bar.</p>
|
||||
<p><a href="/user-guide/introduction/shortcuts/#zoom" target="_blank">All zoom shortcuts →</a></p>
|
||||
<p><a href="/user-guide/first-steps/shortcuts/#zoom-workspace" target="_blank">All zoom shortcuts →</a></p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/workspace-zoom.webp" alt="Zoom options" />
|
||||
</figure>
|
||||
|
||||
<h4>Zoom lense</h4>
|
||||
<h3>Zoom lense</h3>
|
||||
<p>Press left click while pressing <kbd>Z</kbd> to zoom in to a specific point and <kbd>Alt/⌥</kbd> + <kbd>Z</kbd> to zoom out.</p>
|
||||
<p><img src="/img/zoom-lense.gif" alt="zoom lense" /></p>
|
||||
<!--figure>
|
||||
@@ -46,7 +113,7 @@ desc: Master Penpot's workspace basics! Learn interface navigation, zoom tools,
|
||||
</video>
|
||||
</figure-->
|
||||
|
||||
<h4>Zooming from the layers panel</h4>
|
||||
<h3>Zooming from the layers panel</h3>
|
||||
<p>Double click over a layer icon to zoom to the layer.</p>
|
||||
<figure>
|
||||
<video title="Zoom layer" muted="" playsinline="" controls="" width="auto" poster="/img/workspace-basics/workspace-zoom-layer.webp" height="auto">
|
||||
@@ -54,14 +121,112 @@ desc: Master Penpot's workspace basics! Learn interface navigation, zoom tools,
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="comments">Comments</h2>
|
||||
<p>Comments allow the team to have one priceless conversation getting and providing feedback right over the designs and prototypes.<p>
|
||||
|
||||
<h3>Adding comments</h3>
|
||||
<figure>
|
||||
<video title="Create comments" muted="" playsinline="" controls="" width="auto" poster="/img/workspace-basics/comments-create.webp" height="auto">
|
||||
<source src="/img/workspace-basics/comments-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<ol>
|
||||
<li>At the workspace, activate the comment tool by clicking the comment icon in the navbar or pressing the <kbd>C</kbd> key.</li>
|
||||
<li>Click on a location within the viewport to leave a comment. If you want the comment to appear in the board view, add the comment to the board.</li>
|
||||
<li>Write your comment in the text box.</li>
|
||||
<li>Press Post to leave the comment or Cancel to not do it.</li>
|
||||
</ol>
|
||||
<h3>How to reply a comment</h3>
|
||||
<ol>
|
||||
<li>Open a comment by clicking at its bubble (a circled number).</li>
|
||||
<li>Write your comment at the text box at the end of the comment popup.</li>
|
||||
<li>Press Post to leave the comment or Cancel to not do it.</li>
|
||||
</ol>
|
||||
<h3>Mark threads as read</h3>
|
||||
<p>Mark a thread as read using the checkbox at the comment box to make it disappear from the comments notifications at the dashboard.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/comments-mark.webp" alt="Marking comments as read" />
|
||||
</figure>
|
||||
|
||||
<h3>Edit and remove comments</h3>
|
||||
<p>At the top right of the comment popup you can find options to edit or delete comments.</p>
|
||||
|
||||
<h3>Dashboard notifications</h3>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/comments-dashboard.webp" alt="Comments notifications" />
|
||||
</figure>
|
||||
<p>At your projects Dashboard you will be able to see if you have unread comments inside the files of the team.</p>
|
||||
|
||||
|
||||
<h2 id="history">File history versions</h2>
|
||||
<p>The history panel keeps track of the latest changes on an opened file as well as the different versions of the file, making it easier to track changes, revert to previous states and collaborate.</p>
|
||||
|
||||
<h4>View history</h4>
|
||||
<p>To view the recent history of a file at the workspace click the history icon on the navbar at the left:</p>
|
||||
<ul>
|
||||
<li>To see the history of file versions go to the <strong>History</strong> tab.</li>
|
||||
<li>To see the history of item changes go to the <strong>Actions</strong> tab.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-view.webp" alt="History versions button" />
|
||||
</figure>
|
||||
|
||||
<h3>History panel</h3>
|
||||
<p>At the History panel, you can save the current version of your file, as well as access previous versions for up to 7 days.</p>
|
||||
|
||||
<h4>Restore versions</h4>
|
||||
<p>All saved versions of the file—whether manually saved, autosaved, or pinned—can be restored, reverting the file back to its state at the selected time.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-restore.webp" alt="Restore versions" />
|
||||
</figure>
|
||||
|
||||
<h4>Saved versions</h4>
|
||||
<p>You can save the current version of your file by clicking the pin icon at the History tab. This will allow the version to be named and it will add it to your list of versions.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-save.webp" alt="Saved versions" />
|
||||
</figure>
|
||||
|
||||
<h4>Autosaved versions</h4>
|
||||
<p>When you start working on a file, Penpot will start to automatically save versions of that file across time so that you can later restore them as needed.</p>
|
||||
<p>In the History tab, if you click on the autosaved versions, you’ll see a list of the exact date and time when the version was automatically saved.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-autosaved.webp" alt="Autosaved versions" />
|
||||
</figure>
|
||||
|
||||
<h4>Pinned versions</h4>
|
||||
<p>File versions can also be pinned. Pinning a file version will allow you to name it, making it easier to access at the History tab. Pinned file versions will be saved forever and can be renamed, restored or deleted at any time.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-pin.webp" alt="Pin versions" />
|
||||
</figure>
|
||||
|
||||
<h3>Actions panel</h3>
|
||||
<p>At the Actions panel, you have the layer type (rectangle, text, image...) and type of change (New, Modified, Deleted...). If you press the item, it will be reverted to its state before that specific action was performed.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-actions.webp" alt="Actions panel" />
|
||||
</figure>
|
||||
<p class="advice">The Actions panel shows only a limited list of changes at a current browser tab session. Refreshing the browser means refreshing the history of actions as well.</p>
|
||||
|
||||
<h4>Navigate actions</h4>
|
||||
<p>To navigate through the actions press <kbd>Ctrl/⌘</kbd> + <kbd>Z</kbd> to go backwards and <kbd>Ctrl/⌘</kbd> + <kbd>Shift/⇧</kbd> + <kbd>Z</kbd> to go forward.</p>
|
||||
<p>You can also press any item of the actions list to get to this specific state.</p>
|
||||
|
||||
<h2 id="shortcuts-panel">Shortcuts panel</h2>
|
||||
<p>Shortcuts boost your productivity but they are not easy to learn. A handy panel at your workspace will help you with that.</p>
|
||||
<p>Display the shortcuts panel at the workspace using the shortcut <kbd>?</kbd> or through the option at the <a href="/user-guide/designing/workspace-basics/#workspace-menu">main menu</a>.</p>
|
||||
<p>The categories and a filter will help you to find the shortcut you need.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/shortcuts.webp" alt="Shortcuts panel" />
|
||||
</figure>
|
||||
<p><a href="/user-guide/first-steps/shortcuts/" target="_blank">Full list of shortcuts →</a></p>
|
||||
|
||||
<h2 id="dynamic-alignment">Dynamic alignment</h2>
|
||||
<p>While moving objects at the viewport Penpot will show alignment guides for the edges and the center of the layers at sight. Dynamic alignment also snaps the object that is being moved to those guides to help you align to the center of the edges of other objects.</p>
|
||||
<p>While moving layers at the viewport Penpot will show alignment guides for the edges and the center of the layers at sight. Dynamic alignment also snaps the layer that is being moved to those guides to help you align to the center of the edges of other layers.</p>
|
||||
<figure>
|
||||
<video title="dynamic alignment" muted="" playsinline="" controls="" width="auto" poster="/img/workspace-basics/dynamic-alignment.webp" height="auto">
|
||||
<source src="/img/workspace-basics/dynamic-alignment.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<p>If there are more than two objects nearby and you drag one of them Penpot will show their distance to help you distribute them equally. </p>
|
||||
<p>If there are more than two layers nearby and you drag one of them Penpot will show their distance to help you distribute them equally. </p>
|
||||
<figure>
|
||||
<video title="dynamic alignment" muted="" playsinline="" controls="" width="auto" poster="/img/workspace-basics/dynamic-alignment-measurement.webp" height="auto">
|
||||
<source src="/img/workspace-basics/dynamic-alignment-measurement.mp4" type="video/mp4">
|
||||
@@ -177,13 +342,13 @@ geometric structure. In Penpot there are three types of guides:
|
||||
|
||||
|
||||
<h2 id="snap-to-pixel">Snap to pixel</h2>
|
||||
<p>Objects automatically snap to the pixel grid. If you need a different kind of precision like working at subpixel level using measures with decimals you can disable this option anytime from the main menu.</p>
|
||||
<p>Layers automatically snap to the pixel grid. If you need a different kind of precision like working at subpixel level using measures with decimals you can disable this option anytime from the main menu.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/workspace-snap.webp" alt="Snap to pixel setting" />
|
||||
</figure>
|
||||
|
||||
<h2 id="nudge-amount">Nudge amount</h2>
|
||||
<p>Set your chosen distance to move objects using the keyboard. This is a must if you’re working with guides (if you’re not, you should ;)). Being able to adjust the movement to your baseline grid (8px? 5px?) is a huge timesaver that will improve your quality of life while designing.</p>
|
||||
<p>Set your chosen distance to move layers using the keyboard. This is a must if you’re working with guides (if you’re not, you should ;)). Being able to adjust the movement to your baseline grid (8px? 5px?) is a huge timesaver that will improve your quality of life while designing.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/nudge.webp" alt="Nudge amount prompt" />
|
||||
</figure>
|
||||
@@ -192,98 +357,4 @@ geometric structure. In Penpot there are three types of guides:
|
||||
<img src="/img/workspace-basics/nudge-menu.webp" alt="Nudge amount prompt" />
|
||||
</figure>
|
||||
|
||||
<h2 id="shortcuts-panel">Shortcuts panel</h2>
|
||||
<p>Shortcuts boost your productivity but they are not easy to learn. A handy panel at your workspace will help you with that.</p>
|
||||
<p>Display the shortcuts panel at the workspace using the shortcut <kbd>?</kbd> or through the option at the <a href="/user-guide/workspace-basics/#workspace-menu">main menu</a>.</p>
|
||||
<p>The categories and a filter will help you to find the shortcut you need.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/shortcuts.webp" alt="Shortcuts panel" />
|
||||
</figure>
|
||||
|
||||
<h2 id="history">File history versions</h2>
|
||||
<p>The history panel keeps track of the latest changes on an opened file as well as the different versions of the file, making it easier to track changes, revert to previous states and collaborate.</p>
|
||||
|
||||
<h3>View history</h3>
|
||||
<p>To view the recent history of a file at the workspace click the history icon on the navbar at the left:</p>
|
||||
<ul>
|
||||
<li>To see the history of file versions go to the <strong>History</strong> tab.</li>
|
||||
<li>To see the history of item changes go to the <strong>Actions</strong> tab.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-view.webp" alt="History versions button" />
|
||||
</figure>
|
||||
|
||||
<h3>History panel</h3>
|
||||
<p>At the History panel, you can save the current version of your file, as well as access previous versions for up to 7 days.</p>
|
||||
|
||||
<h4>Restore versions</h4>
|
||||
<p>All saved versions of the file—whether manually saved, autosaved, or pinned—can be restored, reverting the file back to its state at the selected time.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-restore.webp" alt="Restore versions" />
|
||||
</figure>
|
||||
|
||||
<h4>Saved versions</h4>
|
||||
<p>You can save the current version of your file by clicking the pin icon at the History tab. This will allow the version to be named and it will add it to your list of versions.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-save.webp" alt="Saved versions" />
|
||||
</figure>
|
||||
|
||||
<h4>Autosaved versions</h4>
|
||||
<p>When you start working on a file, Penpot will start to automatically save versions of that file across time so that you can later restore them as needed.</p>
|
||||
<p>In the History tab, if you click on the autosaved versions, you’ll see a list of the exact date and time when the version was automatically saved.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-autosaved.webp" alt="Autosaved versions" />
|
||||
</figure>
|
||||
|
||||
<h4>Pinned versions</h4>
|
||||
<p>File versions can also be pinned. Pinning a file version will allow you to name it, making it easier to access at the History tab. Pinned file versions will be saved forever and can be renamed, restored or deleted at any time.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-pin.webp" alt="Pin versions" />
|
||||
</figure>
|
||||
|
||||
<h3>Actions panel</h3>
|
||||
<p>At the Actions panel, you have the object type (rectangle, text, image...) and type of change (New, Modified, Deleted...). If you press the item, it will be reverted to its state before that specific action was performed.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-actions.webp" alt="Actions panel" />
|
||||
</figure>
|
||||
<p class="advice">The Actions panel shows only a limited list of changes at a current browser tab session. Refreshing the browser means refreshing the history of actions as well.</p>
|
||||
|
||||
<h4>Navigate actions</h4>
|
||||
<p>To navigate through the actions press <kbd>Ctrl/⌘</kbd> + <kbd>Z</kbd> to go backwards and <kbd>Ctrl/⌘</kbd> + <kbd>Shift/⇧</kbd> + <kbd>Z</kbd> to go forward.</p>
|
||||
<p>You can also press any item of the actions list to get to this specific state.</p>
|
||||
|
||||
<h2 id="comments">Comments</h2>
|
||||
<p>Comments allow the team to have one priceless conversation getting and providing feedback right over the designs and prototypes.<p>
|
||||
|
||||
<h4>Adding comments</h4>
|
||||
<figure>
|
||||
<video title="Create comments" muted="" playsinline="" controls="" width="auto" poster="/img/workspace-basics/comments-create.webp" height="auto">
|
||||
<source src="/img/workspace-basics/comments-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<ol>
|
||||
<li>At the workspace, activate the comment tool by clicking the comment icon in the navbar or pressing the <kbd>C</kbd> key.</li>
|
||||
<li>Click on a location within the viewport to leave a comment. If you want the comment to appear in the board view, add the comment to the board.</li>
|
||||
<li>Write your comment in the text box.</li>
|
||||
<li>Press Post to leave the comment or Cancel to not do it.</li>
|
||||
</ol>
|
||||
<h4>How to reply a comment</h4>
|
||||
<ol>
|
||||
<li>Open a comment by clicking at its bubble (a circled number).</li>
|
||||
<li>Write your comment at the text box at the end of the comment popup.</li>
|
||||
<li>Press Post to leave the comment or Cancel to not do it.</li>
|
||||
</ol>
|
||||
<h4>Mark threads as read</h4>
|
||||
<p>Mark a thread as read using the checkbox at the comment box to make it disappear from the comments notifications at the dashboard.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/comments-mark.webp" alt="Marking comments as read" />
|
||||
</figure>
|
||||
|
||||
<h4>Edit and remove comments</h4>
|
||||
<p>At the top right of the comment popup you can find options to edit or delete comments.</p>
|
||||
|
||||
<h4>Dashboard notifications</h4>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/comments-dashboard.webp" alt="Comments notifications" />
|
||||
</figure>
|
||||
<p>At your projects Dashboard you will be able to see if you have unread comments inside the files of the team.</p>
|
||||
@@ -1,23 +1,24 @@
|
||||
---
|
||||
title: 14· Inspect designs
|
||||
title: Dev tools
|
||||
order: 5
|
||||
desc: Learn how to inspect designs in Penpot! This guide covers distances, properties, code snippets (CSS, SVG, HTML), & exporting assets for seamless collaboration.
|
||||
---
|
||||
|
||||
<h1 id="inspect">Inspect designs</h1>
|
||||
<h1 id="dev-tools">Dev tools</h1>
|
||||
<p class="main-paragraph">At Penpot, you can inspect designs to get measures, view properties, export assets and get production-ready code. <p>
|
||||
|
||||
<h2 id="inspect-activate">How to inspect designs</h2>
|
||||
<p>You can activate the Inspect mode both at the <a href="/user-guide/the-interface/#interface-viewmode">View mode</a> and at the <a href="/user-guide/the-interface/#interface-workspace">Workspace</a>.</p>
|
||||
<h2 id="inspect-design">Inspect design</h2>
|
||||
<p>You can activate the Inspect mode both at the <a href="/user-guide/first-steps/the-interface/#interface-viewmode">View mode</a> and at the <a href="/user-guide/first-steps/the-interface/#interface-workspace">Workspace</a>.</p>
|
||||
|
||||
<h3 id="inspect-viewmode">At the View mode</h3>
|
||||
<h3>At the View mode</h3>
|
||||
<figure>
|
||||
<video title="A video showing how to activate Inspect at the View mode" muted="" playsinline="" controls="" width="100%" poster="/img/inspect/inspect-viewmode.webp" height="auto">
|
||||
<source src="/img/inspect/inspect-viewmode.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<p>Go to the <a href="/user-guide/view-mode/#viewmode-inspect">Inspect designs at the View mode section</a> to know how to activate inspect mode at the View mode.</p>
|
||||
<p>Go to the <a href="/user-guide/prototyping-testing/testing-view-mode/#viewmode-inspect">Inspect designs at the View mode section</a> to know how to activate inspect mode at the View mode.</p>
|
||||
|
||||
<h3 id="inspect-workspace">At the Workspace</h3>
|
||||
<h3>At the Workspace</h3>
|
||||
<figure>
|
||||
<video title="A video showing how to activate Inspect at the Workspace" muted="" playsinline="" controls="" width="100%" poster="/img/inspect/inspect-workspace.webp" height="auto">
|
||||
<source src="/img/inspect/inspect-workspace.mp4" type="video/mp4">
|
||||
@@ -26,13 +27,13 @@ desc: Learn how to inspect designs in Penpot! This guide covers distances, prope
|
||||
<p>At the Workspace, select the Inspect tab at the right sidebar to enter inspect mode.</p>
|
||||
<p>Inspect mode provides a safer <strong>view-only</strong> mode so developers can work at the Workspace without the fear of breaking things ;)</p>
|
||||
|
||||
<h2 id="inspect-measure">How to get distances and measurements</h2>
|
||||
<p>You can easily get measurements and distances between an object and other objects or board edges.</p>
|
||||
<h2 id="inspect-measure">Get distances and measurements</h2>
|
||||
<p>You can easily get measurements and distances between a layer and other layers or board edges.</p>
|
||||
<p>To get distances:</p>
|
||||
<ul>
|
||||
<li>Click on an object or select it at the layers panel.</li>
|
||||
<li>Hover over other objects to see the distances between them and the selected one.</li>
|
||||
<li>Hover over a free space on the board or the area around it to see the distances from the object to the board edges.</li>
|
||||
<li>Click on a layer or select it at the layers panel.</li>
|
||||
<li>Hover over other layers to see the distances between them and the selected one.</li>
|
||||
<li>Hover over a free space on the board or the area around it to see the distances from the layer to the board edges.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="A video showing how to get Inspect measures" muted="" playsinline="" controls="" width="100%" poster="/img/inspect/inspect-measures.webp" height="auto">
|
||||
@@ -40,15 +41,15 @@ desc: Learn how to inspect designs in Penpot! This guide covers distances, prope
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="inspect-info">How to get properties info</h2>
|
||||
<p>At the Info panel you can see specifications about style and content of an object. Different types of objects can have different sets of properties.</p>
|
||||
<h2 id="inspect-info">Get properties info</h2>
|
||||
<p>At the Info panel you can see specifications about style and content of a layer. Different types of layers can have different sets of properties.</p>
|
||||
<figure>
|
||||
<video title="A video showing how to get Inspect properties" muted="" playsinline="" controls="" width="100%" poster="/img/inspect/inspect-properties.webp" height="auto">
|
||||
<source src="/img/inspect/inspect-properties.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="inspect-copy">How to copy properties info</h2>
|
||||
<h2 id="inspect-copy">Copy properties info</h2>
|
||||
<p>You can copy the value of one property or full sections of properties pressing the copy buttons that are shown at the right when hovering. For example you could copy all the layout properties or only the width.</p>
|
||||
<figure>
|
||||
<video title="A video showing how to copy properties" muted="" playsinline="" controls="" width="100%" poster="/img/inspect/inspect-copy.webp" height="auto">
|
||||
@@ -56,13 +57,27 @@ desc: Learn how to inspect designs in Penpot! This guide covers distances, prope
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="inspect-code">How to get code</h2>
|
||||
<p>Press the code tab to get actual code snippets. Select an object to get ready to use code for markup (SVG and HTML) and styles (currently CSS only but more are coming).</p>
|
||||
<h2 id="copy-css-properties">Copy CSS properties from layers</h2>
|
||||
<p>To copy CSS properties from layers:</p>
|
||||
<ol>
|
||||
<li>Select one or more layers.</li>
|
||||
<li>Right click to show the layer menu.</li>
|
||||
<li>Press <strong>Copy/Paste as... > Copy as CSS</strong> in case you only want to get the CSS properties from the selected layer/s.</li>
|
||||
<li>Press <strong>Copy/Paste as... > Copy as CSS (nested layers)</strong> in case you only want to get the CSS properties from the selected layer/s and all the contained layers.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<img alt="Copy CSS properties" src="/img/layers/copy-css.webp"/>
|
||||
</figure>
|
||||
|
||||
<h2 id="inspect-code">Get code</h2>
|
||||
<p>Press the code tab to get actual code snippets. Select a layer to get ready to use code for markup (SVG and HTML) and styles (currently CSS only but more are coming).</p>
|
||||
<figure>
|
||||
<video title="A video showing how to get code" muted="" playsinline="" controls="" width="100%" poster="/img/inspect/inspect-code.webp" height="auto">
|
||||
<source src="/img/inspect/inspect-code.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="inspect-export">How to export assets</h2>
|
||||
<p>Export option is available at the bottom of the Info panel. The same export presets that have been set at the workspace will be available at the View mode inspect. New export presets can be added at the Code mode but will not persist. Read more about <a href="/user-guide/exporting/">exporting assets</a>.</p>
|
||||
<h2 id="inspect-export">Export assets</h2>
|
||||
<p>Export option is available at the bottom of the Info panel. The same export presets that have been set at the workspace will be available at the View mode inspect. New export presets can be added at the Code mode but will not persist. Read more about <a href="/user-guide/export-import/exporting-layers/">exporting assets</a>.</p>
|
||||
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
---
|
||||
title: 15· Import/export files
|
||||
title: Export/Import Penpot files
|
||||
order: 1
|
||||
desc: Learn how to import and export files in Penpot, the free, open-source design tool. Discover file formats, backups, sharing, and library management.
|
||||
---
|
||||
|
||||
<h1 id="import-export">Import and export files</h1>
|
||||
<h1 id="export-import-files">Export and import Penpot files</h1>
|
||||
<p class="main-paragraph">You can export Penpot files to your computer and import them from your computer to your projects.</p>
|
||||
|
||||
<h2 id="files-export">Export Penpot files</h2>
|
||||
<p>Exporting files is useful for many reasons. Sometimes you want to have a backup of your files and sometimes it is useful to share Penpot files with a user that does not belong to one of your teams, or you want to have a backup of your files outside Penpot, both SaaS (design.penpot.app) or at a self-hosted instance.</p>
|
||||
|
||||
<h3 id="export-penpot-files">How to export Penpot files</h3>
|
||||
<h3>How to export Penpot files</h3>
|
||||
<h4>Export a single file</h4>
|
||||
<p>You can download (export) files from the workspace and from the dashboard.</p>
|
||||
<p>
|
||||
<strong>From the <a href="/user-guide/the-interface/#interface-dashboard">dashboard</a></strong>: Select the download option at the file card menu.
|
||||
<strong>From the <a href="/user-guide/first-steps/the-interface/#interface-dashboard">dashboard</a></strong>: Select the download option at the file card menu.
|
||||
<figure><img src="/img/import-export/export-card.webp" alt="Export penpot file" /></figure>
|
||||
</p>
|
||||
<p>
|
||||
<strong>From the <a href="/user-guide/the-interface/#interface-workspace">workspace</a></strong>: Select the download option at the main menu.
|
||||
<strong>From the <a href="/user-guide/first-steps/the-interface/#interface-workspace">workspace</a></strong>: Select the download option at the main menu.
|
||||
<figure><img src="/img/import-export/export-menu.webp" alt="Export penpot file" /></figure>
|
||||
</p>
|
||||
|
||||
@@ -34,7 +35,7 @@ desc: Learn how to import and export files in Penpot, the free, open-source desi
|
||||
<ul>
|
||||
<li><strong>Export shared libraries</strong>: Files with shared libraries will be included in the export, maintaining their linkage.</li>
|
||||
<li><strong>Include shared library assets in file libraries</strong>: Files will be exported with all external assets merged into the file library.</li>
|
||||
<li><strong>Treat shared library assets as basic objects</strong>: Shared libraries will not be included in the export and no assets will be added to the library.</li>
|
||||
<li><strong>Treat shared library assets as basic layers</strong>: Shared libraries will not be included in the export and no assets will be added to the library.</li>
|
||||
</ul>
|
||||
<figure><img src="/img/import-export/export-libraries.webp" alt="Export penpot file" /></figure>
|
||||
|
||||
@@ -67,4 +68,6 @@ desc: Learn how to import and export files in Penpot, the free, open-source desi
|
||||
<li>✅ Allows some automations and integrations.</li>
|
||||
<li>✅ Is a transparent, existing, open standard format.</li>
|
||||
<li>❌ Highly inefficient in terms of memory and transfer time when exporting and importing (this is because SVG).</li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
---
|
||||
title: 07· Exporting objects
|
||||
desc: Learn how to export objects in Penpot, the free, open-source design collaboration tool. This guide covers export presets, file formats, and more!
|
||||
title: Exporting layers
|
||||
order: 2
|
||||
desc: Learn how to export layers in Penpot, the free, open-source design collaboration tool. This guide covers export presets, file formats, and more!
|
||||
---
|
||||
|
||||
<h1 id="export">Exporting objects</h1>
|
||||
<h1 id="export">Exporting layers</h1>
|
||||
<p class="main-paragraph">In Penpot you can setup export presets for different file formats and scales.</p>
|
||||
|
||||
<h2 id="export-howto">How to export</h2>
|
||||
<p>You can set up different export configurations to suit your needs. Each export configuration is called "export preset".</p>
|
||||
|
||||
<h3>Create export preset</h3>
|
||||
<p>To export an object you need to select it and at the Design panel add an export preset pressing the “+” button of the Export section.</p>
|
||||
<p>Export presets can be also found at the <strong><a href="/user-guide/the-interface/#interface-viewmode" target="_blank">View mode</a></strong> with the code tab activated.</p>
|
||||
<p>To export a layer you need to select it and at the Design panel add an export preset pressing the “+” button of the Export section.</p>
|
||||
<p>Export presets can be also found at the <strong><a href="/user-guide/first-steps/the-interface/#interface-viewmode" target="_blank">View mode</a></strong> with the code tab activated.</p>
|
||||
<figure>
|
||||
<video title="Export presets" muted="" playsinline="" controls="" width="100%" poster="/img/export/export-presets.webp" height="auto">
|
||||
<source src="/img/export/export-presets.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<p>You can set as many export presets as you need for the same object. Set multiple exports to get the same object in different scales and/or formats with just one click.</p>
|
||||
<p>You can set as many export presets as you need for the same layer. Set multiple exports to get the same layer in different scales and/or formats with just one click.</p>
|
||||
|
||||
|
||||
<h3>Remove export preset</h3>
|
||||
<p>To <strong>remove an export preset</strong> you have to select the object and then press the “-” button at the export preset you want to remove.</p>
|
||||
<p>To <strong>remove an export preset</strong> you have to select the layer and then press the “-” button at the export preset you want to remove.</p>
|
||||
|
||||
<h2 id="export-options">Export options</h2>
|
||||
<p>The options of an export:</p>
|
||||
@@ -57,7 +58,7 @@ desc: Learn how to export objects in Penpot, the free, open-source design collab
|
||||
<h2 id="export-artboards-pdf">Export boards to PDF</h2>
|
||||
<p>If you have a presentation made at Penpot you might want to create a document that can be shared with anyone, regardless of having a Penpot account, or just to be able to use your presentation offline (essential for talks and classes). You can easily export all the artboards of a page to a single PDF file from the file menu.</p>
|
||||
|
||||
<p><a href="#pdf-note">Technical note</a> about the PDF format.</p>
|
||||
<p><a href="#export-technical">Technical note</a> about the PDF format.</p>
|
||||
|
||||
<figure>
|
||||
<img src="/img/export/export-pdf.webp" alt="Export PDF">
|
||||
@@ -65,10 +66,10 @@ desc: Learn how to export objects in Penpot, the free, open-source design collab
|
||||
|
||||
<h2 id="export-technical">Technical notes about exports</h2>
|
||||
<p class="advice">
|
||||
Exported PDF files try to leverage the capabilities of PDF vectorial format (unpixelated zoom, select & copy texts, etc.), but cannot guarantee that 100% of SVG features will be converted perfectly to PDF. You may see differences between an object displayed inside Penpot and in the exported file. If you need an exact match, a workaround is to export the object into PNG and convert it to PDF with some of the many tools that exist for it.<br /><br />
|
||||
Exported PDF files try to leverage the capabilities of PDF vectorial format (unpixelated zoom, select & copy texts, etc.), but cannot guarantee that 100% of SVG features will be converted perfectly to PDF. You may see differences between a layer displayed inside Penpot and in the exported file. If you need an exact match, a workaround is to export the layer into PNG and convert it to PDF with some of the many tools that exist for it.<br /><br />
|
||||
</p>
|
||||
<p class="advice">
|
||||
<a name="pdf-note"></a>
|
||||
<strong>Currently known issue:</strong>
|
||||
When exporting objects with masks, the mask does not work when opening the PDF file with some open source tools (e.g. evince or inkscape). This is not Penpot's fault, but <a href="https://gitlab.freedesktop.org/poppler/poppler/-/issues/1210" target="_blank">a bug in poppler</a>, a library used by many of the open source tools. If you open the file with an official Adobe viewer, or a tool like okular, or in a browser like Chrome or Firefox, you can see it properly.
|
||||
When exporting layers with masks, the mask does not work when opening the PDF file with some open source tools (e.g. evince or inkscape). This is not Penpot's fault, but <a href="https://gitlab.freedesktop.org/poppler/poppler/-/issues/1210" target="_blank">a bug in poppler</a>, a library used by many of the open source tools. If you open the file with an official Adobe viewer, or a tool like okular, or in a browser like Chrome or Firefox, you can see it properly.
|
||||
</p>
|
||||
22
docs/user-guide/export-import/index.njk
Normal file
22
docs/user-guide/export-import/index.njk
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Export & Import
|
||||
order: 7
|
||||
desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorials. Learn the interface, layers, objects, styling, and more.
|
||||
---
|
||||
|
||||
<h1 id="export-import">Export & Import</h1>
|
||||
|
||||
<ul class="intro-sections">
|
||||
<li>
|
||||
<a href="/user-guide/export-import/export-import-files/">
|
||||
<h2>Export/Import Penpot files →</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/export-import/exporting-layers/">
|
||||
<h2>Exporting layers →</h2>
|
||||
<p>Exporting layers</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: Quickstart
|
||||
title: Cloud or Self-host
|
||||
order: 1
|
||||
desc: Start instantly with Penpot, the open-source design and prototyping platform! Access our free user guide to learn now.
|
||||
---
|
||||
|
||||
<h1 id="section-1-1">Quickstart</h1>
|
||||
<h1 id="section-1-1">Cloud or Self-host</h1>
|
||||
|
||||
<p class="main-paragraph">You can start using Penpot right in your browser or installing it in a server of your own.</p>
|
||||
|
||||
34
docs/user-guide/first-steps/index.njk
Normal file
34
docs/user-guide/first-steps/index.njk
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: First Steps
|
||||
order: 1
|
||||
desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorials. Learn the interface, layers, objects, styling, and more.
|
||||
---
|
||||
|
||||
<h1 id="section-1">First Steps</h1>
|
||||
|
||||
<ul class="intro-sections">
|
||||
<li>
|
||||
<a href="/user-guide/first-steps/cloud-selfhost">
|
||||
<h2>Cloud or Selfhost →</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/first-steps/the-interface">
|
||||
<h2>Interface tour →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/first-steps/shortcuts">
|
||||
<h2>Shortcuts →</h2>
|
||||
<p>Speed your design workflow</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/first-steps/info">
|
||||
<h2>Tutorials & info →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Tutorials & info
|
||||
order: 5
|
||||
desc: Begin with Penpot's comprehensive user guide! Get tutorials, learn interface basics, and master design features. Discover FAQs and more.
|
||||
---
|
||||
|
||||
@@ -7,16 +8,16 @@ desc: Begin with Penpot's comprehensive user guide! Get tutorials, learn interfa
|
||||
|
||||
<p class="main-paragraph">Some useful links to better understand Penpot through answers and tutorials.</p>
|
||||
|
||||
<h2 id="dev-diaries">Dev Diaries</h2>
|
||||
<h2>Dev Diaries</h2>
|
||||
<p>The Dev Diaries are our release notes, where we jot down every new feature, enhancement and fix included in every release. There are also kudos to community contributors <3</p>
|
||||
|
||||
<p>Wanna know what's new? take a look at our <a href="https://penpot.app/dev-diaries" target="_blank">Dev Diaries</a>.</p>
|
||||
|
||||
<h2 id="video-tutorials">Video tutorials</h2>
|
||||
<h2>Video tutorials</h2>
|
||||
<p>Suscribe to the <a href="https://www.youtube.com/channel/UCAqS8G72uv9P5HG1IfgnQ9g" target="_blank">Penpot Youtube channel</a> to get updates when we upload new Penpot tutorials, demos of features and talks.</p>
|
||||
|
||||
<h2 id="faqs">Frequently asked questions</h2>
|
||||
<h2>Frequently asked questions</h2>
|
||||
<p>If you have questions about the "Whys" or the "Hows" of Penpot we have collected and answered a bunch of the most common questions that we've been asked so far at our <a href="https://community.penpot.app/c/faq/17">Frequently Asked Questions</a>.</p>
|
||||
|
||||
<h2 id="faqs">Community space</h2>
|
||||
<h2>Community space</h2>
|
||||
<p>We launched a community space to allow for everyone to be part of the conversation. <a href="https://community.penpot.app/" target="_blank">Join the community here</a>.</p>
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
title: Shortcuts
|
||||
order: 3
|
||||
desc: Get quickstart tips, shortcuts, and tutorials for Penpot! Learn interface basics and more with this free, open-source design tool.
|
||||
---
|
||||
|
||||
@@ -12,7 +13,7 @@ desc: Get quickstart tips, shortcuts, and tutorials for Penpot! Learn interface
|
||||
<p>You can also check the most updated list of shortcuts at the <a href="https://github.com/penpot/penpot/blob/develop/frontend/src/app/main/data/workspace/shortcuts.cljs" target="_blank">GitHub file</a>.</p>
|
||||
|
||||
<h2 id="workspace-section"> Workspace </h2>
|
||||
<p>The Workspace is where the designs are actually created. <a href="/user-guide/the-interface/#interface-workspace">More about the Workspace</a>.</p>
|
||||
<p>The Workspace is where the designs are actually created. <a href="/user-guide/first-steps/the-interface/#interface-workspace">More about the Workspace</a>.</p>
|
||||
<h3 id="alignment">Item Alignment</h3>
|
||||
<table cellspacing="0" cellpadding="1" border="1" width="100%">
|
||||
<thead>
|
||||
@@ -738,19 +739,19 @@ desc: Get quickstart tips, shortcuts, and tutorials for Penpot! Learn interface
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Increase font size</td>
|
||||
<td style="text-align: center;"><kbd>Ctrl</kbd> <kbd>Shift</kbd> <kbd>RIGHT</kbd></td>
|
||||
<td style="text-align: center;"><kbd>⌘</kbd> <kbd>⇧</kbd> <kbd>RIGHT</kbd></td>
|
||||
<td style="text-align: center;"><kbd>Ctrl</kbd> <kbd>Shift</kbd> <kbd>.</kbd></td>
|
||||
<td style="text-align: center;"><kbd>⌘</kbd> <kbd>⇧</kbd> <kbd>.</kbd></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Decrease font size</td>
|
||||
<td style="text-align: center;"><kbd>Ctrl</kbd> <kbd>Shift</kbd> <kbd>LEFT</kbd></td>
|
||||
<td style="text-align: center;"><kbd>⌘</kbd> <kbd>⇧</kbd> <kbd>LEFT</kbd></td>
|
||||
<td style="text-align: center;"><kbd>Ctrl</kbd> <kbd>Shift</kbd> <kbd>,</kbd></td>
|
||||
<td style="text-align: center;"><kbd>⌘</kbd> <kbd>⇧</kbd> <kbd>,</kbd></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="dashboard-section"> Dashboard </h2>
|
||||
<p>The Dashboard is the place where you will be able to organize your files, libraries, projects and teams. <a href="/user-guide/the-interface/#interface-dashboard">More about the Dashboard</a>.</p>
|
||||
<p>The Dashboard is the place where you will be able to organize your files, libraries, projects and teams. <a href="/user-guide/first-steps/the-interface/#interface-dashboard">More about the Dashboard</a>.</p>
|
||||
|
||||
<h3 id="generic-dashboard">Generic</h3>
|
||||
<table cellspacing="0" cellpadding="1" border="1" width="100%">
|
||||
@@ -809,7 +810,7 @@ desc: Get quickstart tips, shortcuts, and tutorials for Penpot! Learn interface
|
||||
</table>
|
||||
|
||||
<h2 id="viewer-section"> View mode </h2>
|
||||
<p>The View mode is the area to present and share designs and play the prototype interactions. <a href="/user-guide/the-interface/#interface-viewmode">More about the View mode</a>.</p>
|
||||
<p>The View mode is the area to present and share designs and play the prototype interactions. <a href="/user-guide/first-steps/the-interface/#interface-viewmode">More about the View mode</a>.</p>
|
||||
|
||||
<h3 id="generic-viewer">Generic</h3>
|
||||
<table cellspacing="0" cellpadding="1" border="1" width="100%">
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: 02· The interface
|
||||
title: Interface tour
|
||||
order: 2
|
||||
desc: Discover Penpot's free user guide! Learn the interface, workspace basics, flexible layouts, and prototyping. Master Penpot today.
|
||||
---
|
||||
|
||||
<h1 id="the-interface">The interface</h1>
|
||||
<h1 id="the-interface">Interface tour</h1>
|
||||
<p class="main-paragraph">The Penpot interface has three main areas: Dashboard, Workspace and View mode. Lets take a look at their composition and main features.</p>
|
||||
|
||||
<h2 id="interface-workspace">Workspace</h2>
|
||||
@@ -40,29 +41,29 @@ desc: Discover Penpot's free user guide! Learn the interface, workspace basics,
|
||||
|
||||
<ol>
|
||||
<li><strong>Viewport:</strong> An infinite canvas where you can design without limits.</li>
|
||||
<li><strong>Toolbar:</strong> This is where you’ll find all the tools to quickly and easily create different types of objects: board, rectangle, ellipse, text, graphic, path, and free drawing. <a href="/user-guide/objects/">Learn more about objects.</a></li>
|
||||
<li><strong>Toolbar: </strong> This is where you’ll find all the tools to quickly and easily create different types of layers: board, rectangle, ellipse, text, graphic, path, and free drawing. <a href="/user-guide/designing/layers/">Learn more about layers.</a></li>
|
||||
<li><strong>Main menu:</strong> From the main menu, you can customize your workspace. Manage the visibility of grids, rulers, and panels. Enable or disable snapping and dynamic alignment. Add or remove the file as a Shared Library. You’ll also find help resources here.</li>
|
||||
<li><strong>Pages:</strong> A file can contain as many pages as you need. Each page has its own viewport (the almost infinite area where you design) and its own layers. You can create, delete, or reorder pages as needed.</li>
|
||||
<li><strong>Layers:</strong> Layers are the different objects you can place in the design viewport. <a href="/user-guide/layer-basics/">Learn more about layers.</a></li>
|
||||
<li><strong>Layers:</strong> Layers are the different objects you can place in the design viewport. <a href="/user-guide/designing/workspace-basics/#layer-basics">More about Layers panel.</a></li>
|
||||
<li><strong>Rulers:</strong> Rulers provide coordinates to help you design. You can also drag guides from them.</li>
|
||||
<li><strong>Color palette:</strong> The color palette gives you quick access to a visible library of colors. Use the menu to easily switch between libraries. <a href="/user-guide/styling/#color-palette">Learn more about the color palette.</a></li>
|
||||
<li><strong>Color palette:</strong> The color palette gives you quick access to a visible library of colors. Use the menu to easily switch between libraries. <a href="/user-guide/designing/color-stroke/#color-palette">Learn more about the color palette.</a>.</li>
|
||||
<li><strong>Typography palette:</strong> The typography palette keeps text styles always at hand. Use the menu to switch between libraries.</li>
|
||||
<li><strong>Design properties:</strong> The Design Properties sidebar lets you view and edit the attributes of a selected layer. The list changes depending on the element type: some properties are always present (size, position), while others are optional (stroke, shadow, blur…). <a href="/user-guide/styling/">Learn more about styling.</a></li>
|
||||
<li><strong>Prototype mode:</strong> Penpot lets you prototype interactions by connecting boards. Activate prototype mode from the right sidebar, then select an element and drag a connection to another board. You can preview the interactive prototype in View Mode (access it by clicking the play button in the top-right corner). <a href="/user-guide/prototyping/">Learn more about prototyping.</a></li>
|
||||
<li><strong>Inspect mode:</strong> Use Inspect Mode to get measurements, properties, and production-ready code from your designs. It also provides a safer, view-only mode with additional improvements. <a href="/user-guide/inspect/">Learn more about inspecting designs.</a></li>
|
||||
<li><strong>View mode:</strong> The View Mode button launches a presentation of your boards. Explore more features in the <a href="/user-guide/view-mode/#viewmode-features">View Mode guide.</a></li>
|
||||
<li><strong>Share / Invite:</strong> Invite team members to give them access to this file and all team files.</li>
|
||||
<li><strong>History:</strong> The History panel records recent changes in an open file. Use undo/redo to step through changes, and expand each entry for detailed information. <a href="/user-guide/workspace-basics/#history">Learn more about the history panel.</a></li>
|
||||
<li><strong>Comments:</strong> Switch to Comments Mode to view all comments on the file and add new ones.</li>
|
||||
<li><strong>Zoom:</strong> Options to zoom in, zoom out, or fit the canvas.</li>
|
||||
<li><strong>Design properties:</strong> The Design Properties sidebar lets you view and edit the attributes of a selected layer. The list changes depending on the element type: some properties are always present (size, position), while others are optional (stroke, shadow, blur…). <a href="/user-guide/designing/layers/#layer-actions">Learn more about styling.</a></li>
|
||||
<li><strong>Prototype mode:</strong> Penpot lets you prototype interactions by connecting boards. Activate prototype mode from the right sidebar, then select an element and drag a connection to another board. You can preview the interactive prototype in View Mode (access it by clicking the play button in the top-right corner). <a href="/user-guide/prototyping-testing/prototyping/">Leran more about prototyping.</a></li>
|
||||
<li><strong>Inspect mode:</strong> Use Inspect Mode to get measurements, properties, and production-ready code from your designs. It also provides a safer, view-only mode with additional improvements. <a href="/user-guide/dev-tools/#inspect-design">Learn more about inspecting designs.</a></li>
|
||||
<li><strong>View mode:</strong> The View Mode button launches a presentation of your boards. Explore more features in the <a href="/user-guide/prototyping-testing/testing-view-mode/">View Mode guide.</a> </li>
|
||||
<li><strong>Share / Invite:</strong> Invite team members to give them access to this file and all team files.</li>
|
||||
<li><strong>History:</strong> The history panel keeps track of the latest changes on an opened file. You can undo/redo to walk the changes and expand each one of them to get detailed information. <a href="/user-guide/designing/workspace-basics/#history">Learn more about the history panel.</a></li>
|
||||
<li><strong>Comments:</strong> Switch to Comments Mode to view all comments on the file and add new ones. <a href="/user-guide/designing/workspace-basics/#comments">Learn more about workspace comments.</a></li>
|
||||
<li><strong>Zoom:</strong> Options to zoom in, zoom out, or fit the canvas. <a href="/user-guide/designing/workspace-basics/#zoom">Learn more about zoom.</a></li>
|
||||
<li><strong>Users:</strong> See how many users currently have the file open.</li>
|
||||
<li><strong>Assets:</strong> Each file includes a default File Library where you can store reusable elements and styles, such as components, colors, and text styles. From this panel, you'll be also able to include and use shared libraries.</li>
|
||||
<li><strong>Design tokens:</strong> Design tokens are the building blocks of UI elements, expressed in a format usable across design, tools, and code. They include colors, typography, spacing, shadows, and more.</li>
|
||||
<li><strong>Assets:</strong> Each file includes a default File Library where you can store reusable elements and styles, such as components, colors, and text styles. From this panel, you'll be also able to include and use shared libraries. <a href="/user-guide/design-systems/assets/">Learn more about assets.</a></li>
|
||||
<li><strong>Design tokens:</strong> Design tokens are the building blocks of UI elements, expressed in a format usable across design, tools, and code. They include colors, typography, spacing, shadows, and more. <a href="/user-guide/design-systems/design-tokens/">Learn more about Design Tokens.</a></li>
|
||||
<li><strong>File status:</strong> Displays the file’s saving state, showing whether your latest changes are saved or if there’s a problem.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="interface-viewmode">View mode</h2>
|
||||
<p>Launch the view mode to present and share your designs, comment on them and play with the interactions set at the workspace. You also have an Inspect mode where you can get properties specifications and code snippets. <a href="/user-guide/view-mode/">More about the View mode.</a></p>
|
||||
<p>Launch the view mode to present and share your designs, comment on them and play with the interactions set at the workspace. You also have an Inspect mode where you can get properties specifications and code snippets. <a href="/user-guide/prototyping-testing/testing-view-mode/">More about the View mode.</a></p>
|
||||
|
||||
<figure>
|
||||
<a href="/img/interface/viewmode-dark.webp" target="_blank">
|
||||
@@ -91,8 +92,8 @@ desc: Discover Penpot's free user guide! Learn the interface, workspace basics,
|
||||
<li><strong>Boards selector:</strong> Boards selector displays a list of the boards with thumbnails that provide a graphic overview of the file.</li>
|
||||
<li><strong>Play mode:</strong> Default presentation mode where you can also play with interactions.</li>
|
||||
<li><strong>Comments mode:</strong> Comments mode shows comments inside Boards and allows users to reply or add new ones.</li>
|
||||
<li><strong>Inspect mode:</strong> At the Inspect mode you can get design specifications such as object properties, measurements and distances. You can also get code snippets for CSS styles, HTML and SVG. <a href="/user-guide/view-mode/#viewmode-inspect">More about Inspect.</a></li>
|
||||
<li><strong>Interactions settings:</strong> Select whether to highlight interactions active areas on click, all the time or not at all. <a href="/user-guide/view-mode/#viewmode-features">More about interaction settings.</a></li>
|
||||
<li><strong>Inspect mode:</strong> At the Inspect mode you can get design specifications such as layer properties, measurements and distances. You can also get code snippets for CSS styles, HTML and SVG. <a href="/user-guide/dev-tools/#inspect-design">More about Inspect.</a></li>
|
||||
<li><strong>Interactions settings:</strong> Select whether to highlight interactions active areas on click, all the time or not at all. <a href="/user-guide/prototyping-testing/testing-view-mode/#viewmode-features">More about interaction settings.</a></li>
|
||||
<li><strong>Zoom:</strong> Zoom options and info about their shortcuts.</li>
|
||||
<li><strong>Edit file:</strong> Opens file edition at the design workspace.</li>
|
||||
<li><strong>Fullscreen mode:</strong> Activate or deactivate browser fullscreen mode.</li>
|
||||
@@ -126,82 +127,18 @@ desc: Discover Penpot's free user guide! Learn the interface, workspace basics,
|
||||
</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>Teams:</strong> A team allows you to collaborate with other Penpot users. Team members are allowed to work with any project or file within the team depending on their permissions. Members with admin permissions can also invite other members. <a href="/user-guide/teams/#teams-management">Create or join as many teams as you need</a> with different groups of people.</li>
|
||||
<li><strong>Teams:</strong> A team allows you to collaborate with other Penpot users. Team members are allowed to work with any project or file within the team depending on their permissions. Members with admin permissions can also invite other members. <a href="/user-guide/account-teams/teams/#teams-management">Create or join as many teams as you need</a> with different groups of people.</li>
|
||||
<li><strong>Search:</strong> If you are looking for a specific file just type its title at the search box.</li>
|
||||
<li><strong>Projects:</strong> A project allows you to group design files. It works pretty much like a folder in a file system. You can create as many projects as you need. If you are going to work with more people in a project, you should create it inside a team.</li>
|
||||
<li><strong>Drafts:</strong> The drafts section is where you can find the design files that are not inside any project.</li>
|
||||
<li><strong>Shared Libraries:</strong> In this section you will find all the design files that have been added as shared libraries. That way you will be able to better control the files that are sharing their assets. </li>
|
||||
<li><strong>Custom fonts:</strong> If you have purchased or own personal fonts that are not included in the catalog provided by Penpot, you can upload them from your computer and use them across the files of a team.</li>
|
||||
<li><strong>Pinned projects:</strong> If you want to keep some projects handy (for instance because you’re currently working on them) you can pin them to make them quickly available at the sidebar.</li>
|
||||
<li><strong>User area:</strong> This must be you! Access your profile settings, Penpot tutorials, the Penpot Community and more. You can also find here a way to leave us feedback. We’d love to read your thoughts :)</li>
|
||||
<li><strong>User area:</strong> This must be you! Access your <a href="/user-guide/account-teams/your-account"> profile settings</a>, Penpot tutorials, the Penpot Community and more. You can also find here a way to leave us feedback. We’d love to read your thoughts :). </li>
|
||||
<li><strong>Comments notifications:</strong> Here you will be able to see if you have unread comments inside the files of the team. There's also a button to mark all notifications as read.</li>
|
||||
<li><strong>Create project:</strong> Create as many projects as you need to organize your designs.</li>
|
||||
<li><strong>File card:</strong> Basic information about a file at plain sight. A preview, update info or if it’s added as a Shared Library. From there you can perform several actions over the file (rename, duplicate, move, download, delete).</li>
|
||||
<li><strong>Libraries & Templates module:</strong> A curated selection of Libraries & Templates files ready to import.</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="your-account">Your account</h3>
|
||||
<p>Your account settings can be changed at the user area, in <b>Your account</b>. Here you can make changes to your profile, password or account language, as well as generate personal access tokens and access release notes.</p>
|
||||
|
||||
<h4 id="your-account-profile">Profile
|
||||
<a class="direct-link" href="#your-account-profile">#</a>
|
||||
</h3>
|
||||
<p>If you want to change the email address associated to your account or remove your account entirely, this can be done in the <b>Profile</b> section.</p>
|
||||
<figure>
|
||||
<img src="/img/interface/youraccount-profile.webp" alt="Penpot's profile" />
|
||||
</figure>
|
||||
|
||||
<h4 id="your-account-password">Password
|
||||
<a class="direct-link" href="#your-account-password">#</a>
|
||||
</h3>
|
||||
<p>If you want to change your password to a new one, this can be done in the <b>Password</b> section.</p>
|
||||
<figure>
|
||||
<img src="/img/interface/youraccount-password.webp" alt="Penpot's password" />
|
||||
</figure>
|
||||
|
||||
<h4 id="your-account-notifications">Notifications
|
||||
<a class="direct-link" href="#your-account-notifications">#</a>
|
||||
</h3>
|
||||
<p>At the <strong>Notifications</strong> section you can configure the email and dashboard notifications.</p>
|
||||
<figure>
|
||||
<img src="/img/interface/youraccount-notifications.webp" alt="Penpot's notifications" />
|
||||
</figure>
|
||||
|
||||
<h4 id="your-account-settings">Settings
|
||||
<a class="direct-link" href="#your-account-settings">#</a>
|
||||
</h3>
|
||||
<p>At the <strong>Settings</strong> section you can change the language and the UI color theme.</p>
|
||||
<figure>
|
||||
<img src="/img/interface/youraccount-settings.webp" alt="Penpot's settings" />
|
||||
</figure>
|
||||
|
||||
<h4 id="your-account-accesstokens">Access tokens
|
||||
<a class="direct-link" href="#your-account-accesstokens">#</a>
|
||||
</h3>
|
||||
<p>At the <strong>Asset tokens</strong> section you can manage your access tokens. <a href="https://help.penpot.app/technical-guide/integration/#access-tokens" target="_blank">Read more about access tokens here</a>.</p>
|
||||
|
||||
<h2 id="interface-ui-theme">UI Theme</h2>
|
||||
<p>Penpot's default interface is dark but you can switch anytime to a light option. You have 2 ways to change the theme:</p>
|
||||
<ul>
|
||||
<li>From "Your account" > "Settings".</li>
|
||||
<li>Using the shortcut <kbd>Alt/⌥</kbd> + <kbd>M</kbd>.</li>
|
||||
</ul>
|
||||
|
||||
<figure>
|
||||
<a href="/img/interface/dashboard-light.webp" target="_blank">
|
||||
<img src="/img/interface/dashboard-light.webp" alt="Penpot's dashboard" />
|
||||
</a>
|
||||
<figcaption>Penpot's dashboard in light mode</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<a href="/img/interface/workspace-light.webp" target="_blank">
|
||||
<img src="/img/interface/workspace-light.webp" alt="Penpot's workspace" />
|
||||
</a>
|
||||
<figcaption>Penpot's workspace in light mode</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<a href="/img/interface/viewmode-light.webp" target="_blank">
|
||||
<img src="/img/interface/viewmode-light.webp" alt="Penpot's view mode" />
|
||||
</a>
|
||||
<figcaption>Penpot's view mode in light mode</figcaption>
|
||||
</figure>
|
||||
@@ -16,17 +16,55 @@ eleventyNavigation:
|
||||
|
||||
<p class="advice">This documentation is a work in progress that will be updated frequently. If you have a suggestion, see something is missing or find anything that needs correcting, please write to us at <a href="mailto:support@penpot.app" target="_blank">support@penpot.app</a>.</p>
|
||||
|
||||
<p class="main-paragraph">Explore our featured topics:</p>
|
||||
|
||||
<ul class="intro-sections">
|
||||
<li>
|
||||
<a href="/user-guide/introduction/quickstart">
|
||||
<h2>Quickstart</h2>
|
||||
<a href="/user-guide/designing/layers/">
|
||||
<h2>Layers</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/the-interface/">
|
||||
<h2>The interface</h2>
|
||||
<a href="/user-guide/designing/flexible-layouts/">
|
||||
<h2>Flexible layouts</h2>
|
||||
<p>Create designs that adapt automatically.</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/components/">
|
||||
<h2>Components</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/variants/">
|
||||
<h2>Variants</h2>
|
||||
<p>Penpot's main areas and features</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/design-tokens/">
|
||||
<h2>Design Tokens</h2>
|
||||
<p>Penpot's main areas and features</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/dev-tools/#inspect-design">
|
||||
<h2>Inspect design</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/prototyping-testing/prototyping/">
|
||||
<h2>Prototyping</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/design-systems/libraries/">
|
||||
<h2>Libraries</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
---
|
||||
title: 01· Introduction
|
||||
desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorials. Learn the interface, layers, objects, styling, and more.
|
||||
---
|
||||
|
||||
<h1 id="section-1">Introduction</h1>
|
||||
|
||||
<ul class="intro-sections">
|
||||
<li>
|
||||
<a href="/user-guide/introduction/quickstart">
|
||||
<h2>Quickstart →</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/introduction/shortcuts">
|
||||
<h2>Shortcuts →</h2>
|
||||
<p>Speed your design workflow</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/introduction/info">
|
||||
<h2>Info & tutorials →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1,311 +0,0 @@
|
||||
---
|
||||
title: 04· Layer basics
|
||||
desc: Master layer basics with Penpot's user guide! Learn to create, manipulate, and organize layers for stunning designs. Try Penpot, it's free!
|
||||
---
|
||||
|
||||
<h1 id="layer-basics">Layer basics</h1>
|
||||
<p class="main-paragraph">Every object you create in Penpot’s <a href="/user-guide/workspace-basics/#viewport">viewport</a> is a layer. Rectangles, ellipses, boards or text boxes are layers that you can use to build your design.</p>
|
||||
|
||||
<h2 id="pages">Pages</h2>
|
||||
<p>Pages allow you to organize layers into separate sections inside a file, and are shown in separate tabs. Subdividing a file into pages gives you the ability to split your file into logically different sections so that you can organise your work. For instance, you can use pages to separate stages of the design process but keep them in the same document. Different screen sizes, features or atomic design categories are other common ways to use pages. </p>
|
||||
|
||||
<p>You can add, remove or rename pages to suit your needs.</p>
|
||||
<figure>
|
||||
<video title="Creating pages" muted="" playsinline="" controls="" width="auto" poster="/img/layers/pages-create.webp" height="auto">
|
||||
<source src="/img/layers/pages-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="layers-panel">Layers panel</h2>
|
||||
<p><strong>Layers:</strong> Layers are the different objects that you can place at the design viewport. At the layers panel you can see all the layers of a file page. Drag the layers to arrange them to different positions.</p>
|
||||
<figure>
|
||||
<video title="Layers panel" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-panel.webp" height="auto">
|
||||
<source src="/img/layers/layers-panel.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<h3>Navigate layers using the keyboard</h3>
|
||||
<ul>
|
||||
<li>Select a layer and press top/bottom arrows while pressing <kbd>Ctrl/⌘</kbd> to move them in the layers list.</li>
|
||||
<li>Press <kbd>tab</kbd> to change the layer selection to the next layer.</li>
|
||||
<li>Press <kbd>tab</kbd> + <kbd>Shift/⇧</kbd> to change the layer selection to the previous layer.</li>
|
||||
<li>If the layer contains other layers, press <kbd>Enter</kbd> to select the first layer inside the group and <kbd>Enter</kbd>+ <kbd>Shift/⇧</kbd> to move a level up.</li>
|
||||
</ul>
|
||||
|
||||
<p>Layers are displayed from the bottom to the top of the layer stack, with layers above on the stack being shown on top in the image.</p>
|
||||
|
||||
<h2 id="hide-lock">Hide and lock layers</h2>
|
||||
|
||||
<h3>Hide and show layers</h3>
|
||||
<p>You can control the visibility of any layer by clicking the eye icon next to it in the Layers panel. When a layer is hidden, it will not appear on the canvas, but you can still select it in the Layers panel, move its order, or modify its properties. The eye icon always indicates whether a layer is visible or hidden, making it easy to manage complex designs.</p>
|
||||
|
||||
<h3>Lock and unlock layers</h3>
|
||||
<p>Locking a layer helps prevent accidental changes or movement on the canvas. When a layer is locked, it cannot be moved or edited directly in the canvas area. However, you can still select a locked layer in the Layers panel and adjust its properties, such as color, effects, or name. The lock icon next to the layer’s name shows its locked status, helping you keep your design organized and protected.</p>
|
||||
|
||||
<figure>
|
||||
<video title="Layers hide and lock" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-hide-lock.webp" height="auto">
|
||||
<source src="/img/layers/layers-hide-lock.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="creating-layers">Create layers</h2>
|
||||
<p>To create a layer you have to select the type of layer by clicking the selected tool (board, rectangle, ellipse, text, image, path or curve) at the toolbar. Then you usually have to click and drag your mouse on the viewport. </p>
|
||||
<p>Hold <kbd>Shift/⇧</kbd> while creating an ellipse or a rectangle to maintain equal width and height.</p>
|
||||
<figure>
|
||||
<video title="Layers create" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-create.webp" height="auto">
|
||||
<source src="/img/layers/layers-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="duplicating-layers">Duplicate layers</h2>
|
||||
<p>There are several ways to duplicate a layer:</p>
|
||||
<ol>
|
||||
<li>You can press <kbd>Ctrl/⌘</kbd> + <kbd>D</kbd> to duplicate a layer right over a selected layer. </li>
|
||||
<li>If you press right click over a selected layer at the viewport or at the layers panel you can use the option at the layer menu. </li>
|
||||
<li>You can also select a layer and drag while pressing <kbd>Alt/⌥</kbd> so you can simultaneously duplicate and drag the new layer.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Duplicate layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-duplicate.webp" height="auto">
|
||||
<source src="/img/layers/layers-duplicate.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="delete-layers">Delete layers</h2>
|
||||
<p>There are a couple ways to delete a layer. </p>
|
||||
<ol>
|
||||
<li>You can press <kbd>Supr/⌫</kbd> to delete a selected layer. </li>
|
||||
<li>If you press right click over a selected layer at the viewport or at the layers panel you can use the option at the layer menu.</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="select-layers">Select layers</h2>
|
||||
<p>The simplest way to select a layer is to click on it. Make sure that you have the “move” pointer selected at the toolbar. </p>
|
||||
<p>To select multiple layers you can click and drag around the layers you want to select. You can also click more than one layer while pressing <kbd>Shift/⇧</kbd>. If you hold <kbd>Shift/⇧</kbd> and click you can deselect layers individually.</p>
|
||||
<figure>
|
||||
<video title="Select layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-select.webp" height="auto">
|
||||
<source src="/img/layers/layers-select.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3>Selecting layers at the layers panel</h3>
|
||||
<ol>
|
||||
<li>Click a layer to do a single selection.</li>
|
||||
<li>Press <kbd>Ctrl/⌘</kbd> while clicking two or more layers to do a multiple selection.</li>
|
||||
<li>If you press <kbd>Shift/⇧</kbd> while selecting two or more layers all the layers within the selection area will be selected.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Select layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-multiselect.webp" height="auto">
|
||||
<source src="/img/layers/layers-multiselect.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<h3>Select layers ignoring groups (deep selection)</h3>
|
||||
<p>If you want to select an element that is difficult to reach because it is under a group of elements, hold <kbd>Ctrl/⌘</kbd> to make the selection ignore group areas and treat all the objects as being at the same level.</p>
|
||||
<figure>
|
||||
<video title="Select layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-deepselect.webp" height="auto">
|
||||
<source src="/img/layers/layers-deepselect.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<h3>Select layers inside groups</h3>
|
||||
<p>To <strong>select a layer inside a group</strong> you do double click. First click selects the group, second click selects a layer.</p>
|
||||
|
||||
<h3>Select layer menu</h3>
|
||||
<p>At the dropdown menu (right click on a layer to show it) there's the option "Select layer" that allows the user to select one layer among the ones that are under the cursor's location.</p>
|
||||
<p><img src="/img/layers-select-menu.gif" alt="layers select" /></p>
|
||||
|
||||
<h2 id="group-layers">Group layers</h2>
|
||||
<p>Grouped layers can be moved, transformed or styled at the same time. </p>
|
||||
<ul>
|
||||
<li><strong>Group:</strong> To group two or more layers, select them and then press <kbd>Ctrl/⌘</kbd> + <kbd>G</kbd>. You can also use the option at the layers menu that you can open with right click.</li>
|
||||
<li><strong>Ungroup:</strong> Press <kbd>Shift/⇧</kbd> + <kbd>G</kbd> or use the option at the layers menu that you can open with right click over the selected group.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Group layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-group.webp" height="auto">
|
||||
<source src="/img/layers/layers-group.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="mask-layers">Mask layers</h2>
|
||||
<p>A mask is a layer that does a clipping and only shows parts of a layer or multiple layers that fall within its shape. </p>
|
||||
<ul>
|
||||
<li><strong>Mask layers:</strong> Select more than one layer or a group of them. Then you can apply the masking using the option at the layers menu or by pressing <kbd>Ctrl/⌘</kbd> + <kbd>M</kbd>. The shape that is at the lowest level at the layer list will be used as a mask. </li>
|
||||
<li><strong>Unmask layers:</strong> Select a mask and then press <kbd>Shift/⇧</kbd> + <kbd>Ctrl/⌘</kbd> + <kbd>M</kbd> or use the option at the layers menu.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Mask layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-mask.webp" height="auto">
|
||||
<source src="/img/layers/layers-mask.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="move-layers">Move layers</h2>
|
||||
<p>To move one or more layers on the viewport you have to select them first and then click and drag the selection where you want to place them. You can also use the design panel to set a precise position relative to the viewport or the board.</p>
|
||||
<figure>
|
||||
<video title="Move layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-move.webp" height="auto">
|
||||
<source src="/img/layers/layers-move.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="resize-layers">Resize layers</h2>
|
||||
<p>To resize a selected layer you can use the handles at the edges of the selection box. Make sure the cursor is in resizing mode. You can also use the design panel where you can link width and height.</p>
|
||||
<ul>
|
||||
<li>Hold <kbd>Shift/⇧</kbd> while resizing the object to preserve its aspect ratio.</li>
|
||||
<li>Hold <kbd>Alt/⌥</kbd> while resizing the object to do it from the center and resize simultaneously two opposite sides.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Resize layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-resize.webp" height="auto">
|
||||
<source src="/img/layers/layers-resize.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="rotate-layers">Rotate layers</h2>
|
||||
<p>To rotate selected layers you can use the handles at the edges of the selection box. Make sure the cursor is in rotation mode. If you hold <kbd>Ctrl/⌘</kbd> while rotation the angle will change in 45 degree increments. You can also find this option at the design panel.</p>
|
||||
<figure>
|
||||
<video title="Rotate layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-rotate.webp" height="auto">
|
||||
<source src="/img/layers/layers-rotate.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="flip-layers">Flip layers</h2>
|
||||
<p>You can find the options to flip layers in their contextual menu (select the layer and right click). You also have shortcuts to do this:</p>
|
||||
<ul>
|
||||
<li><strong>Flip layers horizontally:</strong> Select the layer and press <kbd>Shift/⇧</kbd> + <kbd>H</kbd></li>
|
||||
<li><strong>Flip layers vertically:</strong> Select the layer and then press <kbd>Shift/⇧</kbd> + <kbd>V</kbd>.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Flip layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-flip.webp" height="auto">
|
||||
<source src="/img/layers/layers-flip.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
|
||||
<h2 id="scale-elements">Scale elements, texts and properties</h2>
|
||||
<p>Activate the scale tool by pressing <kbd>K</kbd> or from the main file menu to scale elements while maintaining their visual aspect. Once it is activated you can resize texts, layers and groups and preserve their aspect ratio while scaling their properties proportionally, including strokes, shadows, blurs and corners.
|
||||
<figure>
|
||||
<video title="Scale layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-scale.webp" height="auto">
|
||||
<source src="/img/layers/layers-scale.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="aling-distribute-layers">Align and distribute layers</h2>
|
||||
<p>Aligning and distributing layers can be found at the top of the Design panel. </p>
|
||||
<h3>Align layers</h3>
|
||||
<p>Aligning will move all the selected layers to a position relative to one of them. For instance, aligning top will align the elements with the edge of the top-most element.</p>
|
||||
<figure>
|
||||
<video title="Align layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-align.webp" height="auto">
|
||||
<source src="/img/layers/layers-align.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<h3>Distribute layers</h3>
|
||||
<p>Distributing objects to position them vertically and horizontally with equal distances between them.</p>
|
||||
<figure>
|
||||
<video title="Distribute layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-distribute.webp" height="auto">
|
||||
<source src="/img/layers/layers-distribute.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="layers-search">Search and filter layers</h2>
|
||||
<p>Reach specific layers with a simple search. You can also filter the layers list per layer type (board, group, mask, component, text, image and shape).</p>
|
||||
<figure>
|
||||
<video title="Search and filter layers" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-search.webp" height="auto">
|
||||
<source src="/img/layers/layers-search.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="scale-elements">Copy CSS properties</h2>
|
||||
<p>To copy CSS properties from layers:</p>
|
||||
<ol>
|
||||
<li>Select one or more layers.</li>
|
||||
<li>Right click to show the layer menu.</li>
|
||||
<li>Press <strong>Copy/Paste as... > Copy as CSS</strong> in case you only want to get the CSS properties from the selected layer/s.</li>
|
||||
<li>Press <strong>Copy/Paste as... > Copy as CSS (nested layers)</strong> in case you only want to get the CSS properties from the selected layer/s and all the contained layers.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<img alt="Copy CSS properties" src="/img/layers/copy-css.webp"/>
|
||||
</figure>
|
||||
|
||||
<h2 id="collapse-groups">Collapse groups and boards</h2>
|
||||
<p>Groups and boards can have their contents expanded and collapsed. Click on the arrow at the
|
||||
right side to toggle the visibility of their contents. </p>
|
||||
<p>To collapse all the layers, and just display the boards,
|
||||
press <kbd>Shift/⇧</kbd> + left click over the right arrow of a group or a board to collapse them all.</p>
|
||||
<figure>
|
||||
<video title="Collapse layer groups" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-collapse.webp" height="auto">
|
||||
<source src="/img/layers/layers-collapse.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="boolean-operators">Boolean operators</h2>
|
||||
<p>It is possible to combine shapes in a group in different ways to create more complex objects by using
|
||||
"boolean" operators. Boolean operators are non destructive and the original shapes remain grouped and available for more editing. There are five boolean operations available: union, difference, intersection, exclusion and flatten. Using boolean operations allows many graphic options and possibilities for your designs.</p>
|
||||
<figure>
|
||||
<video title="Boolean operators" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-boolean.webp" height="auto">
|
||||
<source src="/img/layers/layers-boolean.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<ul>
|
||||
<li><strong>Union:</strong> the resulting combination is the sum of the shapes.</li>
|
||||
<li><strong>Difference:</strong> the opposite of union, the resulting object has the area of the shape on top has been cut out from the shape at the bottom.</li>
|
||||
<li><strong>Intersection:</strong> creates a group whose shape is the overlapping area between the shapes.</li>
|
||||
<li><strong>Exclusion:</strong> the opposite of intersection, the resulting shape is the area that does not overlap between the shapes.</li>
|
||||
<li><strong>Flatten:</strong> Tecnically not a boolean operator, this is the way to permanently combine the paths of a group of shapes in a single one.</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<h2 id="constraints">Resizing constraints</h2>
|
||||
<p>Constraints allow you to decide how layers will behave when resizing its container.</p>
|
||||
|
||||
<h3>Apply constraints</h3>
|
||||
<p>Constraints allow you to decide how layers will behave when resizing its parent container. You can apply horizontal and vertical constraints for every layer.</p>
|
||||
<p>To apply constraints select a layer and use the constraints map or the constraints selectors at the design panel.</p>
|
||||
<figure>
|
||||
<video title="Constraints" muted="" playsinline="" controls="" width="auto" poster="/img/layers/layers-constraints.webp" height="auto">
|
||||
<source src="/img/layers/layers-constraints.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<p>Constraints are set to “Scale” by default, but you have other options.</p>
|
||||
|
||||
<h3>Horizontal constraints</h3>
|
||||
<ul>
|
||||
<li><strong>Left</strong>: The object maintains its size and position relative to the left side of its parent container.</li>
|
||||
<li><strong>Right</strong>: The object maintains its size and position relative to the right side of its parent container.</li>
|
||||
<li><strong>Left & right</strong>: The object resizes while maintaining its distance to both horizontal sides of its parent container.</li>
|
||||
<li><strong>Center</strong>: The object maintains its size and position relative to the horizontal center of its parent container.</li>
|
||||
<li><strong>Scale</strong>: The object will horizontally resize proportionally to its parent container size.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/layers/layers-constraints-h.webp" alt="Horizontal constraints">
|
||||
</figure>
|
||||
|
||||
<h3>Vertical constraints</h3>
|
||||
<ul>
|
||||
<li><strong>Top</strong>: The object maintains its size and position relative to the top side of its parent container.</li>
|
||||
<li><strong>Bottom</strong>: The object maintains its size and position relative to the bottom side of its parent container.</li>
|
||||
<li><strong>Top & bottom</strong>: The object resizes while maintaining its distance to both vertical sides of its parent container.</li>
|
||||
<li><strong>Center</strong>: The object maintains its size and position relative to the vertical center of its parent container.</li>
|
||||
<li><strong>Scale</strong>: The object will vertically resize proportionally to its parent container size.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/layers/layers-constraints-v.webp" alt="Vettical constraints">
|
||||
</figure>
|
||||
|
||||
|
||||
<h2 id="focus-mode">Focus mode</h2>
|
||||
<p>Focus mode zooms into the elements of a page you want to work with in a specific moment, and hides the rest so that they don’t get in the way. When the page has many elements, focus mode can also improve performance.</p>
|
||||
<p>To activate focus mode:</p>
|
||||
<ol>
|
||||
<li>Select one or more elements.</li>
|
||||
<li>Right click on the selection to show the menu and select the option “Focus on” or press <kbd>F</kbd>.</li>
|
||||
</ol>
|
||||
<p>Notice that the layer panel will now only show the focused layers. A focus mode status line will also appear at the top.</p>
|
||||
<p>To exit focus mode and return to the original viewport and selection, right click anywhere and select “Focus off” or just press <kbd>F</kbd> again. You can also click anywhere on the focus mode status line at the top of the layer panel.
|
||||
</p>
|
||||
<figure>
|
||||
<video title="Focus mode" muted="" playsinline="" controls="" width="100%" poster="/img/layers/layers-focus.webp" height="auto">
|
||||
<source src="/img/layers/layers-focus.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
|
||||
<h2 id="rtl-support">RTL support</h2>
|
||||
<p>Diversity and inclusion is a major Penpot concern and that's why we love to give support to RTL languages, unlike most design tools.</p>
|
||||
<p>If you write in arabic, hebrew or other RTL language text direction will be automatically detected in text layers.</p>
|
||||
<figure>
|
||||
<img src="/img/layers/layers-rtl.webp" alt="RTL support">
|
||||
</figure>
|
||||
@@ -1,236 +0,0 @@
|
||||
---
|
||||
title: 05· Objects
|
||||
desc: "Work with Penpot's objects: boards, shapes, text, paths, and graphics. Learn to create, select, rename, and customize boards for optimal workflow."
|
||||
---
|
||||
|
||||
<h1 id="objects">Objects</h1>
|
||||
<p class="main-paragraph">Objects are items that you can place in the viewport. Boards, shapes, texts, paths and graphics are objects. The following describes the different objects that you have
|
||||
available in Penpot, and how to get the most of them.</p>
|
||||
|
||||
<h2 id="Boards">Boards</h2>
|
||||
<p>Boards are layers that serve as your high-level containers for content organization and layout. Boards are useful if you want to design for a specific screen or print size. Boards can contain other boards. First level boards
|
||||
are shown by default at the <a href="/user-guide/view-mode">View mode</a>, acting as screens of a design or pages of a document. Also, objects inside boards can be clipped. Boards are a powerful element at Penpot, opening up a ton of possibilities when creating and organizing your designs.</p>
|
||||
|
||||
<h3>Create boards</h3>
|
||||
<p>To create a board, use the board tool at the toolbar or the shortcut <kbd>B</kbd>.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-tool.webp" alt="Board tool">
|
||||
</figure>
|
||||
|
||||
<p>Then, with the board tool selected, you have two options:</p>
|
||||
<ul>
|
||||
<li><strong>Select a board size upfront</strong>. You can choose one of the provided presets with the most common resolution for devices and standard print sizes</li>
|
||||
<li><strong>Click-and-drag</strong> to draw a frame of approximate size, then immediately edit its width/height values to be precise.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Create board" muted="" playsinline="" controls="" width="100%" poster="/img/objects/board-create.webp" height="auto">
|
||||
<source src="/img/objects/board-creation.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<p><strong>TIP:</strong> Create a board around one or more selected objects using the option "Selection to board" at the menu or the shortcut <kbd>Ctrl/⌘</kbd> + <kbd>Alt</kbd> + <kbd>G</kbd>.</p>
|
||||
|
||||
<h3>Select boards</h3>
|
||||
<p>There are two different cases in terms of selecting boards:</p>
|
||||
<ul>
|
||||
<li>For first level boards that have at least one inside, click on the board name or <kbd>Ctrl/⌘</kbd> + click on the board area to select it and then drag</li>
|
||||
<li>For the rest (empty first level boards and inside boards) just click to select.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Select board" muted="" playsinline="" controls="" width="auto" poster="/img/objects/board-select.webp" height="auto">
|
||||
<source src="/img/objects/board-select.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3>Rename boards</h3>
|
||||
<p>There several ways to rename boards:</p>
|
||||
<ul>
|
||||
<li>Double click on the board name at the workspace viewport.</li>
|
||||
<li>Double click on the board name at the layers panel.</li>
|
||||
<li>Press <kbd>Alt/⌥</kbd> + <kbd>N</kbd> to rename the board at the layers panel.</li>
|
||||
<li>Right click to show the menu and select "Rename".</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<video title="Rename board" muted="" playsinline="" controls="" width="auto" poster="/img/objects/board-rename.webp" height="auto">
|
||||
<source src="/img/objects/board-rename.webm" type="video/webm">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3>Set board as thumbnail</h3>
|
||||
<p>Select a specific board to be the file thumbnail that will be shown at <a href="/user-guide/the-interface/#dashboard-interface" target="_blank">the dashboard</a> in the file card.</p>
|
||||
<p>To set a custom thumbnail:</p>
|
||||
<ol>
|
||||
<li>Select a board.</li>
|
||||
<li>Right click to show the menu and select "Set as thumbnail" or press <kbd>Shift</kbd> <kbd>T</kbd>.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Set board as thumbnail" muted="" playsinline="" controls="" width="auto" poster="/img/objects/board-thumbnail.webp" height="auto">
|
||||
<source src="/img/objects/board-thumbnail.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3>Copy link to board</h3>
|
||||
<p>You can get the link to each individual board, making it easy to share them with team members or include direct links in documentation.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-copy-link.webp" alt="copy link to board">
|
||||
</figure>
|
||||
<p>There are two ways to copy a direct link to a board:</p>
|
||||
<ul>
|
||||
<li>Using the menu: Select the board, right click and select the "Copy link" option.</li>
|
||||
<li>Using the shortcut: Select the board and press <kbd>Shift/⇧</kbd> + <kbd>Alt/⌥</kbd> + <kbd>C</kbd>.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Clip content</h3>
|
||||
<p>Boards offer the option to clip its content (or not).</p>
|
||||
<figure>
|
||||
<video title="Clip board" muted="" playsinline="" controls="" width="100%" poster="/img/objects/board-clip.webp" height="auto">
|
||||
<source src="/img/objects/board-clip.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3>Show in View mode</h3>
|
||||
<p>Boards offer the option to be shown as a separate board/screen in the <a href="/user-guide/the-interface/#interface-viewmode">View mode</a>. Use this setting to decide what boards should be shown as individual items in your presentations.</p>
|
||||
<p><strong>Defaults</strong></p>
|
||||
<p>As it is very likely that the first level boards will be used as a screen and the interiors will not, there are different defaults for newly created boards.</p>
|
||||
<ul>
|
||||
<li>Boards created at first level (<a href="/user-guide/workspace-basics/#viewport">the viewport</a>): shown by default.</li>
|
||||
<li>Boards created inside other boards: not shown by default.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/objects/board-show.webp" alt="board show in view mode">
|
||||
</figure>
|
||||
|
||||
<h3>Show fill in exports</h3>
|
||||
<p> Sometimes you don’t need the artboards to be part of your designs, but only their support to work on them.
|
||||
Penpot allows you to decide if the fill of an artboard will be shown in exports, you just have to check/uncheck the "Show in exports" option which is below the fill setting.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-fill.webp" alt="show board fill in exports">
|
||||
</figure>
|
||||
|
||||
<h3>Resize board to fit to content</h3>
|
||||
<p>You can adjust the board size to fit its content by clicking the icon in the design sidebar.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-fit.webp" alt="Resize board to fit to content button">
|
||||
</figure>
|
||||
|
||||
<h3>Board guides</h3>
|
||||
<p>You can set guides on boards that will assist with aligning objects.</p>
|
||||
<p>Read more about <a href="/user-guide/workspace-basics/#guides">guides</a>.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-guides.webp" alt="board guides">
|
||||
</figure>
|
||||
|
||||
<h3>Prototyping boards</h3>
|
||||
<p>You can connect boards with other boards to create rich interactions.</p>
|
||||
<p>Read more about <a href="/user-guide/prototyping/">prototyping</a>.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/board-prototyping.webp" alt="prototyping with boards">
|
||||
</figure>
|
||||
|
||||
<h2 id="rectangles-ellipses">Rectangles and ellipses</h2>
|
||||
<p>Rectangle and ellipses are two basic “primitive” geometric shapes that are useful when starting
|
||||
a design.</p>
|
||||
<p>The shortcut keys are <kbd>E</kbd> for ellipses and <kbd>R</kbd> for rectangles.</p>
|
||||
<p>To find out more about how to edit and modify these shapes go to <a href="/user-guide/layer-basics/">Layer basics</a>.</p>
|
||||
<figure>
|
||||
<video title="Rectangles and ellipses" muted="" playsinline="" controls="" width="auto" poster="/img/objects/rectangles-ellipses.webp" height="auto">
|
||||
<source src="/img/objects/rectangles-ellipses.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
|
||||
<h2 id="text">Text</h2>
|
||||
<p>To insert text you have to activate the text tool by first clicking on the icon at the toolbar or pressing <kbd>T</kbd>. Then you have two ways to create a text layer:</p>
|
||||
<ol>
|
||||
<li><strong>Click</strong> to create a textbox without any specific dimensions.</li>
|
||||
<li><strong>Drag</strong> to create a textbox with a fixed size.</li>
|
||||
</ol>
|
||||
<figure>
|
||||
<video title="Create text" muted="" playsinline="" controls="" width="auto" poster="/img/objects/text-create.webp" height="auto">
|
||||
<source src="/img/objects/text-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
<p><strong>Tips for resizing</strong></p>
|
||||
<ul>
|
||||
<li>Double-click on the right side of the bounding box to set the resize setting to auto-width.</li>
|
||||
<li>Double-click on the bottom side of the bounding box to set the resize setting to auto-height.</li>
|
||||
</ul>
|
||||
<h3>Edit and style text content</h3>
|
||||
<p>Press <kbd>Enter</kbd> with a text layer selected to start editing the text content. You can style parts of the text content as rich text.</p>
|
||||
<figure>
|
||||
<img src="/img/objects/text-edit.webp" alt="editing text">
|
||||
</figure>
|
||||
<h3>Text options</h3>
|
||||
<figure>
|
||||
<img src="/img/objects/text-options.webp" alt="text options">
|
||||
</figure>
|
||||
<ol>
|
||||
<li><strong>Font family.</strong> Penpot includes by default the <a href="https://fonts.google.com/" target=”_blank”>Google Fonts</a> cataloge. You can also <a href="/user-guide/custom-fonts">install your own fonts</a>.</li>
|
||||
<li><strong>Font size.</strong></li>
|
||||
<li><strong>Font type.</strong></li>
|
||||
<li><strong>Line height</strong> (in pixels).</li>
|
||||
<li><strong>Letter spacing</strong> (in pixels).</li>
|
||||
<li><strong>Text case:</strong> none, uppercase, lowercase, titlecase.</li>
|
||||
<li><strong>Horizontal alignment:</strong> left, center, right, justify.</li>
|
||||
<li><strong>Sizing:</strong> auto height, auto width, fixed size.</li>
|
||||
<li><strong>Vertical alignment:</strong> top, center, bottom.</li>
|
||||
<li><strong>Decoration:</strong> none, underline, strikethrough.</li>
|
||||
<li><strong>Direction:</strong> LTR (left to right), RTL (right to left).</li>
|
||||
</ol>
|
||||
|
||||
<h2 id="curves">Curves (freehand)</h2>
|
||||
<p>The curve tool allows a path to be created directly in a freehand mode.
|
||||
Select the curve tool by clicking on the icon at the toolbar or pressing <kbd>Shift/⇧</kbd> + <kbd>c</kbd>.
|
||||
<p>The path created will contain a lot of points, but it is edited the same way as any other curve.</p>
|
||||
|
||||
<h2 id="paths">Paths (bezier)</h2>
|
||||
<p>A path is composed of two or more nodes and the line segments between them, which may also be curved. To draw a new path you have to select the path tool by clicking on the icon at the toolbar or pressing <kbd>P</kbd>. Then you have two ways to create the path:</p>
|
||||
<ol>
|
||||
<li><strong>Click</strong> to create a new corner node.</li>
|
||||
<li><strong>Click and drag</strong> to create a curved node.</li>
|
||||
</ol>
|
||||
<p>To finish the path:</p>
|
||||
<ol>
|
||||
<li><strong>Close it</strong> clicking over the starting node.</li>
|
||||
<li><strong>Leave it open</strong> pressing <kbd>Esc</kbd> or <kbd>Enter</kbd> to stop editing. Then press <kbd>Esc</kbd> to exit the edit mode.</li>
|
||||
</ol>
|
||||
<p><strong>Tip:</strong> If you hold <kbd>Shift/⇧</kbd> while adding nodes the angle between the current and the next will change in 45 degree increments.</p>
|
||||
<figure>
|
||||
<video title="Paths" muted="" playsinline="" controls="" width="100%" poster="/img/objects/path-create.webp" height="auto">
|
||||
<source src="/img/objects/path-create.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h3>Edit nodes</h3>
|
||||
<p>To edit a node double click on a path or select and press <kbd>Enter</kbd>.
|
||||
You can choose to edit individual nodes or create new ones. Press <kbd>Esc</kbd> to exit node edition. </p>
|
||||
<figure>
|
||||
<video title="Edit nodes" muted="" playsinline="" controls="" width="100%" poster="/img/objects/nodes-edit.webp" height="auto">
|
||||
<source src="/img/objects/nodes-edit.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure><h4>Node types</h4>
|
||||
<p>There are two types of nodes: curve or corner (straight). The type of a selected node can be changed at the bezier menu. Curved nodes have bezier handles that allow the curvature of a path to be modified.</p>
|
||||
<figure>
|
||||
<video title="Node types" muted="" playsinline="" controls="" width="100%" poster="/img/objects/node-types.webp" height="auto">
|
||||
<source src="/img/objects/node-types.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
|
||||
<h2 id="images">Images</h2>
|
||||
<h3>Insert images</h3>
|
||||
<p>There are several options for inserting an image into a Penpot file:</p>
|
||||
<ul>
|
||||
<li>Use the <strong>image tool</strong> at the toolbar or press <kbd>K</kbd> to insert images in your file system.</li>
|
||||
<li><strong>Drag</strong> an image from your computer to the viewport.</li>
|
||||
<li>Copy an image & paste it or drag it right from a <strong>browser</strong>.</li>
|
||||
<li>Drag an image from a Penpot <strong>library</strong>.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Images aspect ratio</h3>
|
||||
<p>Images fill the layer backgrounds by default, so they take up the entire object while maintaining the aspect ratio. This is great for flexible designs because the images can adapt to different sizes.</p>
|
||||
<p>However, if you don't want an image to keep its aspect ratio when resizing, you just have to uncheck the option in the image settings.</p>
|
||||
<figure>
|
||||
<video title="Changing the aspect ratio of an image" muted="" playsinline="" controls="" width="100%" poster="/img/objects/image-ratio.webp" height="auto">
|
||||
<source src="/img/objects/image-ratio.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
@@ -1,21 +1,22 @@
|
||||
---
|
||||
title: 18· Plugins
|
||||
title: Plugins & Integrations
|
||||
order: 6
|
||||
desc: Extend Penpot's functionality with plugins! Install from PenpotHub or via URL. Learn to install, use, and create your own plugins.
|
||||
---
|
||||
|
||||
<h1 id="penpot-plugins">Penpot Plugins</h1>
|
||||
<h1 id="penpot-plugins">Plugins & Integrations</h1>
|
||||
|
||||
<h2 id="plugins">Plugins</h2>
|
||||
<h2>Plugins</h2>
|
||||
<p class="main-paragraph">Plugins are the perfect tool to extend Penpot's functionalities. You can install available plugins or create one that fits your needs!</p>
|
||||
|
||||
<h2 id="installation">How to install Plugins</h2>
|
||||
<h3 id="hub-installation">From PenpotHub</h2>
|
||||
<h3 id="installation">How to install Plugins</h3>
|
||||
<h4>From PenpotHub</h4>
|
||||
|
||||
<p>You can find available Plugins at the <a href="https://help.penpot.app/plugins/getting-started/#examples">Penpot Hub</a> and install plugins directly from the Plugins page by clicking on <strong>Install</strong> next to the desired plugin.</p>
|
||||
|
||||
<img src="/img/plugins/00_PenpotHub_plugins.png" alt="Plugins in the Penpot hub's"></img>
|
||||
|
||||
<h3 id="url-installation">With a URL</h3>
|
||||
<h4>With a URL</h4>
|
||||
<p>Another way to install a plugin is by copying the URL (with .json extension) provided by the plugin’s author and pasting it on to the Plugin Manager.</p>
|
||||
|
||||
<img src="/img/plugins/02_install_url.png" alt="Install plugins using url"></img>
|
||||
@@ -27,7 +28,7 @@ desc: Extend Penpot's functionality with plugins! Install from PenpotHub or via
|
||||
|
||||
<p>Once a plugin has been installed you can access it on your files, as long as you have permissions to access it, in any of your teams. If another member of your team wants to use the plugin, they will need to install it individually.</p>
|
||||
|
||||
<h2 id="plugin-manager">Plugin Manager</h2>
|
||||
<h3 id="plugin-manager">Plugin Manager</h3>
|
||||
<p class="main-paragraph">To start using Plugins you first need to open the Plugin Manager. There are a few different ways to access the Plugins Manager in the Worskpace:</p>
|
||||
<ul>
|
||||
<li>From the toolbar;</li>
|
||||
@@ -35,16 +36,23 @@ desc: Extend Penpot's functionality with plugins! Install from PenpotHub or via
|
||||
<li>Using the shortcut <strong>Ctrl + Alt + P</strong>.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Toolbar</h3>
|
||||
<h4>Toolbar</h4>
|
||||
<img src="/img/plugins/03_toolbar_plugins.png" alt="Open plugin manager via toolbar"></img>
|
||||
|
||||
<h3>Menu</h3>
|
||||
<h4>Menu</h4>
|
||||
<img src="/img/plugins/04_menu_plugins.png" alt="Open plugin manager via menu"></img>
|
||||
|
||||
<h2 id="using-plugins">Using plugins</h2>
|
||||
<h3 id="using-plugins">Using plugins</h3>
|
||||
<p>To use a plugin, go to the Plugin Manager, and click on <strong>Open</strong> next to the desired plugin, and that’s it, enjoy!</p>
|
||||
|
||||
<img src="/img/plugins/05_plugin_manager.png" alt="Plugin manager modal"></img>
|
||||
|
||||
<h2 id="create-plugin">Create a Plugin</h2>
|
||||
<h3 id="create-plugin">Create a Plugin</h3>
|
||||
<p>You can create your own plugin from scratch or use a Template to get started. You can find the complete guide to creating Plugins at the <a href="https://help.penpot.app/plugins/create-a-plugin/">Technical Guide</a>.</p>
|
||||
|
||||
<h2 id="teams-webhooks">Webhooks</h2>
|
||||
|
||||
<p>Webhooks allow other websites and apps to be notified when certain events happen on Penpot, ensuring to create integrations with other services. While we are still working on a plugin system, this is a clever and simple way to create integrations with other services.</p>
|
||||
<figure><img src="/img/teams/webhooks.webp" alt="Webhooks" /></figure>
|
||||
|
||||
<p>You can find detailed info about Penpot webhooks at the <a href="/technical-guide/integration/#webhooks">Technical Guide</a>.</p>
|
||||
22
docs/user-guide/prototyping-testing/index.njk
Normal file
22
docs/user-guide/prototyping-testing/index.njk
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: Prototyping & testing
|
||||
order: 4
|
||||
desc: Begin with the Penpot user guide! Get quickstarts, shortcuts, and tutorials. Learn the interface, layers, objects, styling, and more.
|
||||
---
|
||||
|
||||
<h1 id="section-1">Prototyping & testing</h1>
|
||||
|
||||
<ul class="intro-sections">
|
||||
<li>
|
||||
<a href="/user-guide/prototyping-testing/prototyping">
|
||||
<h2>Prototyping →</h2>
|
||||
<p>Ways to start with Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/user-guide/prototyping-testing/testing-view-mode">
|
||||
<h2>Testing: View mode →</h2>
|
||||
<p>Info of interest about Penpot</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
title: 12· Prototyping
|
||||
title: Prototyping
|
||||
order: 1
|
||||
desc: This Penpot user guide explains how to prototype interactions, connect boards, use triggers/actions, and animations. Learn to build flows and more!
|
||||
---
|
||||
|
||||
<h1 id="prototype">Prototyping interactions</h1>
|
||||
<h1 id="prototyping">Prototyping</h1>
|
||||
<p class="main-paragraph">Learn how to build interactive prototypes to visualize how users navigate through your screens and mimic your product behaviour.<p>
|
||||
|
||||
<p class="main-paragraph">Penpot allows you to prototype interactions by connecting boards, which can act as screens. Once the prototype is prepared with interactions and/or flows, it can be used at the View mode and shared through a link.<p>
|
||||
@@ -20,8 +21,8 @@ desc: This Penpot user guide explains how to prototype interactions, connect boa
|
||||
<li>Activate <strong>Prototype mode</strong> clicking at the tab at the right sidebar.</li>
|
||||
<li><strong>Select</strong> the layer (shape, board or group) that will trigger the interaction.</li>
|
||||
<li><strong>Drag</strong> a connection from the selected layer to the destination board.</li>
|
||||
<li>A <strong>flow start</strong> will be automatically created. <a href="/user-guide/prototyping/#prototyping-flows">More about flows</a>.</li>
|
||||
<li><strong>Launch</strong> the interactive prototype to see it in action in the <a href="/user-guide/view-mode/">View mode</a> (access by clicking the play button at the top right).</li>
|
||||
<li>A <strong>flow start</strong> will be automatically created. <a href="/user-guide/prototyping-testing/prototyping/#prototyping-flows">More about flows</a>.</li>
|
||||
<li><strong>Launch</strong> the interactive prototype to see it in action in the <a href="/user-guide/prototyping-testing/testing-view-mode/">View mode</a> (access by clicking the play button at the top right).</li>
|
||||
</ol>
|
||||
|
||||
<h3 id="prototype-anatomy">Anatomy of a prototype</h3>
|
||||
@@ -189,14 +190,14 @@ desc: This Penpot user guide explains how to prototype interactions, connect boa
|
||||
<img src="/img/prototype/prototype-flows-multiple.webp" alt="Prototyping flows">
|
||||
</figure>
|
||||
|
||||
<h3 id="prototyping-flows-multiple">Flows at the view mode</h3>
|
||||
<h3 id="prototyping-flows-viewmode">Flows at the view mode</h3>
|
||||
<p>At the view mode there’s a menu where you can access to all the flows set and easily switch between them.</p>
|
||||
<figure>
|
||||
<img src="/img/prototype/prototype-flows-viewmode.webp" alt="Prototyping flows">
|
||||
</figure>
|
||||
|
||||
<h2 id="prototyping-fix-scroll">Fix elements at scroll</h2>
|
||||
<p>You can fix the position of an object when scrolling at the presentation view. This is tipically useful for prototyping fixed headers, navbars and floating buttons.</p>
|
||||
<p>You can fix the position of a layer when scrolling at the presentation view. This is tipically useful for prototyping fixed headers, navbars and floating buttons.</p>
|
||||
<figure>
|
||||
<img src="/img/prototype/prototype-fix-scroll.webp" alt="Prototyping fix scroll">
|
||||
</figure>
|
||||
@@ -1,13 +1,14 @@
|
||||
---
|
||||
title: 13· View mode
|
||||
title: "Testing: View mode"
|
||||
order: 2
|
||||
desc: Present designs and share prototypes with Penpot's View mode! Play interactions, navigate boards, zoom, and toggle fullscreen. Try it for free!
|
||||
---
|
||||
|
||||
<h1 id="viewmode">View mode</h1>
|
||||
<p class="main-paragraph">At Penpot, the View mode is the best place to present your designs. You will also be able to share them and play the interactions.<p>
|
||||
<h1 id="testing-viewmode">Testing: View mode</h1>
|
||||
<p class="main-paragraph">At Penpot, the View mode is the best place to test and present your designs. You will also be able to share them and play the interactions.<p>
|
||||
|
||||
<h3 id="viewmode-interface">View mode interface</h3>
|
||||
<p>Take a look at the anatomy of the View mode at this section: <a href="/user-guide/the-interface/#interface-viewmode">View mode interface</a>.</p>
|
||||
<p>Take a look at the anatomy of the View mode at this section: <a href="/user-guide/first-steps/the-interface/#interface-viewmode">View mode interface</a>.</p>
|
||||
<figure>
|
||||
<a href="/img/interface/viewmode-dark.webp" target="_blank">
|
||||
<img src="/img/interface/viewmode-dark.webp" alt="Penpot's viewmode" />
|
||||
@@ -27,7 +28,7 @@ desc: Present designs and share prototypes with Penpot's View mode! Play interac
|
||||
<p>You can navigate through boards by pressing the <kbd>→</kbd> and <kbd>←</kbd> keyboard keys or the navigation buttons at the right and left of the screen.</p>
|
||||
|
||||
<h4>Play prototype interactions</h4>
|
||||
<p>The View mode is the place where you will be able to play prototype interactions. (<a href="/user-guide/prototyping/">More about prototyping interactions</a>).</p>
|
||||
<p>The View mode is the place where you will be able to play prototype interactions. (<a href="/user-guide/prototyping-testing/prototyping/">More about prototyping interactions</a>).</p>
|
||||
|
||||
<h4>Interactions view settings</h4>
|
||||
<p>This setting allows you to decide how to show a visual cue for interactions: always, on click or just don't show.</p>
|
||||
@@ -59,11 +60,11 @@ desc: Present designs and share prototypes with Penpot's View mode! Play interac
|
||||
<p>Toogle fullscreen <kbd>Shift+F</kbd></p>
|
||||
|
||||
<h4>Comments mode</h4>
|
||||
<p>Activate the <a href="/user-guide/view-mode/#viewmode-comments">comments</a> by pressing its button at the top navbar.</p>
|
||||
<p>Activate the <a href="#viewmode-comments">comments</a> by pressing its button at the top navbar.</p>
|
||||
<p>Comments mode will not always be available in view mode shared links, this will depend on the link settings.</p>
|
||||
|
||||
<h4>Inspect mode</h4>
|
||||
<p>Activate the <a href="/user-guide/view-mode/#viewmode-inspect">inspect mode</a> by pressing its button at the top navbar.</p>
|
||||
<p>Activate the <a href="/user-guide/dev-tools/#inspect-design">inspect mode</a> by pressing its button at the top navbar.</p>
|
||||
<p>Inspect mode will not always be available in view mode shared links, this will depend on the link settings.</p>
|
||||
|
||||
|
||||
@@ -117,7 +118,7 @@ desc: Present designs and share prototypes with Penpot's View mode! Play interac
|
||||
<li>On the <strong>left sidebar</strong> you can see the layers tree. Select a layer to inspect it.</li>
|
||||
<li>At the <strong>right sidebar:</strong> you can switch between Info and Code tabs to get design specifications or code.</li>
|
||||
</ul>
|
||||
<p><a href="/user-guide/inspect/">More about inspecting designs</a></p>
|
||||
<p><a href="/user-guide/dev-tools/#inspect-design">More about inspecting designs</a></p>
|
||||
<figure>
|
||||
<video title="A video showing how to activate Inspect at the View mode" muted="" playsinline="" controls="" width="100%" poster="/img/inspect/inspect-viewmode.webp" height="auto">
|
||||
<source src="/img/inspect/inspect-viewmode.mp4" type="video/mp4">
|
||||
134
frontend/playwright/data/design/get-file-12384.json
Normal file
134
frontend/playwright/data/design/get-file-12384.json
Normal file
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"~:features": {
|
||||
"~#set": [
|
||||
"fdata/path-data",
|
||||
"plugins/runtime",
|
||||
"design-tokens/v1",
|
||||
"variants/v1",
|
||||
"layout/grid",
|
||||
"styles/v2",
|
||||
"fdata/objects-map",
|
||||
"render-wasm/v1",
|
||||
"components/v2",
|
||||
"fdata/shape-data-type"
|
||||
]
|
||||
},
|
||||
"~:team-id": "~u3e5ffd68-2819-8084-8006-eb1c616a5afd",
|
||||
"~:permissions": {
|
||||
"~:type": "~:membership",
|
||||
"~:is-owner": true,
|
||||
"~:is-admin": true,
|
||||
"~:can-edit": true,
|
||||
"~:can-read": true,
|
||||
"~:is-logged": true
|
||||
},
|
||||
"~:has-media-trimmed": false,
|
||||
"~:comment-thread-seqn": 0,
|
||||
"~:name": "Bug 12384",
|
||||
"~:revn": 4,
|
||||
"~:modified-at": "~m1761124840773",
|
||||
"~:vern": 0,
|
||||
"~:id": "~ufa6ce865-34dd-80ac-8006-fe0dab5539a7",
|
||||
"~:is-shared": false,
|
||||
"~:migrations": {
|
||||
"~#ordered-set": [
|
||||
"legacy-2",
|
||||
"legacy-3",
|
||||
"legacy-5",
|
||||
"legacy-6",
|
||||
"legacy-7",
|
||||
"legacy-8",
|
||||
"legacy-9",
|
||||
"legacy-10",
|
||||
"legacy-11",
|
||||
"legacy-12",
|
||||
"legacy-13",
|
||||
"legacy-14",
|
||||
"legacy-16",
|
||||
"legacy-17",
|
||||
"legacy-18",
|
||||
"legacy-19",
|
||||
"legacy-25",
|
||||
"legacy-26",
|
||||
"legacy-27",
|
||||
"legacy-28",
|
||||
"legacy-29",
|
||||
"legacy-31",
|
||||
"legacy-32",
|
||||
"legacy-33",
|
||||
"legacy-34",
|
||||
"legacy-36",
|
||||
"legacy-37",
|
||||
"legacy-38",
|
||||
"legacy-39",
|
||||
"legacy-40",
|
||||
"legacy-41",
|
||||
"legacy-42",
|
||||
"legacy-43",
|
||||
"legacy-44",
|
||||
"legacy-45",
|
||||
"legacy-46",
|
||||
"legacy-47",
|
||||
"legacy-48",
|
||||
"legacy-49",
|
||||
"legacy-50",
|
||||
"legacy-51",
|
||||
"legacy-52",
|
||||
"legacy-53",
|
||||
"legacy-54",
|
||||
"legacy-55",
|
||||
"legacy-56",
|
||||
"legacy-57",
|
||||
"legacy-59",
|
||||
"legacy-62",
|
||||
"legacy-65",
|
||||
"legacy-66",
|
||||
"legacy-67",
|
||||
"0001-remove-tokens-from-groups",
|
||||
"0002-normalize-bool-content-v2",
|
||||
"0002-clean-shape-interactions",
|
||||
"0003-fix-root-shape",
|
||||
"0003-convert-path-content-v2",
|
||||
"0004-clean-shadow-color",
|
||||
"0005-deprecate-image-type",
|
||||
"0006-fix-old-texts-fills",
|
||||
"0008-fix-library-colors-v4",
|
||||
"0009-clean-library-colors",
|
||||
"0009-add-partial-text-touched-flags",
|
||||
"0010-fix-swap-slots-pointing-non-existent-shapes",
|
||||
"0011-fix-invalid-text-touched-flags",
|
||||
"0012-fix-position-data",
|
||||
"0013-fix-component-path",
|
||||
"0013-clear-invalid-strokes-and-fills",
|
||||
"0014-fix-tokens-lib-duplicate-ids",
|
||||
"0014-clear-components-nil-objects"
|
||||
]
|
||||
},
|
||||
"~:version": 67,
|
||||
"~:project-id": "~u3e5ffd68-2819-8084-8006-eb1c616e69bf",
|
||||
"~:created-at": "~m1761123649876",
|
||||
"~:backend": "legacy-db",
|
||||
"~:data": {
|
||||
"~:pages": [
|
||||
"~ufa6ce865-34dd-80ac-8006-fe0dab5539a8"
|
||||
],
|
||||
"~:pages-index": {
|
||||
"~ufa6ce865-34dd-80ac-8006-fe0dab5539a8": {
|
||||
"~:objects": {
|
||||
"~#penpot/objects-map/v2": {
|
||||
"~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^H\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~u3fc80ad6-7d08-8031-8006-fe0dba3fddf7\"]]]",
|
||||
"~u3fc80ad6-7d08-8031-8006-fe0dba3fddf7": "[\"~#shape\",[\"^ \",\"~:y\",250,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Board\",\"~:width\",265,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",616,\"~:y\",250]],[\"^=\",[\"^ \",\"~:x\",881,\"~:y\",250]],[\"^=\",[\"^ \",\"~:x\",881,\"~:y\",494]],[\"^=\",[\"^ \",\"~:x\",616,\"~:y\",494]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:exports\",[[\"^ \",\"^:\",\"~:png\",\"~:suffix\",\"\",\"~:scale\",1]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u3fc80ad6-7d08-8031-8006-fe0dba3fddf7\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",616,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",616,\"~:y\",250,\"^9\",265,\"~:height\",244,\"~:x1\",616,\"~:y1\",250,\"~:x2\",881,\"~:y2\",494]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^O\",244,\"~:flip-y\",null,\"~:shapes\",[\"~u20a28a94-4ab0-801b-8006-fe0e8cee02c3\"]]]",
|
||||
"~u20a28a94-4ab0-801b-8006-fe0e8cee02c3": "[\"~#shape\",[\"^ \",\"~:y\",297.00000381469727,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",65,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",644.0000085830688,\"~:y\",297.00000381469727]],[\"^<\",[\"^ \",\"~:x\",709.0000085830688,\"~:y\",297.00000381469727]],[\"^<\",[\"^ \",\"~:x\",709.0000085830688,\"~:y\",362.00000381469727]],[\"^<\",[\"^ \",\"~:x\",644.0000085830688,\"~:y\",362.00000381469727]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:constraints-v\",\"~:top\",\"~:constraints-h\",\"~:left\",\"~:r1\",0,\"~:id\",\"~u20a28a94-4ab0-801b-8006-fe0e8cee02c3\",\"~:parent-id\",\"~u3fc80ad6-7d08-8031-8006-fe0dba3fddf7\",\"~:frame-id\",\"~u3fc80ad6-7d08-8031-8006-fe0dba3fddf7\",\"~:strokes\",[],\"~:x\",644.0000085830688,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",644.0000085830688,\"~:y\",297.00000381469727,\"^8\",65,\"~:height\",65,\"~:x1\",644.0000085830688,\"~:y1\",297.00000381469727,\"~:x2\",709.0000085830688,\"~:y2\",362.00000381469727]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^N\",65,\"~:flip-y\",null]]"
|
||||
}
|
||||
},
|
||||
"~:id": "~ufa6ce865-34dd-80ac-8006-fe0dab5539a8",
|
||||
"~:name": "Page 1"
|
||||
}
|
||||
},
|
||||
"~:id": "~ufa6ce865-34dd-80ac-8006-fe0dab5539a7",
|
||||
"~:options": {
|
||||
"~:components-v2": true,
|
||||
"~:base-font-size": "16px"
|
||||
}
|
||||
}
|
||||
}
|
||||
2182
frontend/playwright/data/render-wasm/get-file-svg-attrs.json
Normal file
2182
frontend/playwright/data/render-wasm/get-file-svg-attrs.json
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user