Compare commits

..

17 Commits

Author SHA1 Message Date
Andrey Antukh
6284ed9e3f Add the ability to accept plugin permission pressing enter 2026-02-23 15:52:05 +01:00
Andrey Antukh
2a323ffaca 💄 Add mainly cosmetic changes to workspace plugins dialog components 2026-02-23 15:52:05 +01:00
Andrey Antukh
84281d1686 Install plugin by pressing enter on plugins dialog 2026-02-23 15:52:05 +01:00
Andrey Antukh
11e233c0c5 Append manifest.json to plugin uri if it comes without it 2026-02-23 15:52:05 +01:00
Andrey Antukh
51458d5afe Make constrast plugin be able to run on subpath 2026-02-23 15:52:05 +01:00
Andrey Antukh
a0fc224533 Make penpot depend on local plugins runtime
This removes the need to publish versions to pnpn
2026-02-23 15:52:05 +01:00
Andrey Antukh
a0f6bee78c Make the colors-to-tokens plugin work on subpath 2026-02-23 15:52:05 +01:00
Andrey Antukh
1544b8082a Make the table-plugin work correctly in subpath 2026-02-23 15:52:05 +01:00
Andrey Antukh
8045b00be5 Add better approach for handling plugin iframe url
Ensure params are passed correctly to plugins declared to be version
2 and are prepared to run in a subpath.
2026-02-23 15:52:05 +01:00
Andrey Antukh
edc9a61933 Update devenv nginx to serve locally builded plugins 2026-02-23 15:52:05 +01:00
Andrey Antukh
1b8afccba2 Remove usage of multipart body size config on backend 2026-02-23 14:44:44 +01:00
Yamila Moreno
dd856ecf50 ♻️ Deprecate PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE envvar 2026-02-23 13:48:01 +01:00
Andrey Antukh
145198c148 📎 Use proper version tag on frontend index template 2026-02-23 12:17:58 +01:00
alonso.torres
eddfc4c4b2 🐛 Fix problem with createText in plugins 2026-02-23 09:35:30 +01:00
alonso.torres
e6e34af391 🐛 Show outline on hidden paths 2026-02-23 09:34:50 +01:00
Andrés Moya
3d41dc276e 🐛 Fix resolve tokens with tokenscript when type is font family 2026-02-20 12:41:17 +01:00
alonso.torres
cee974a906 🐛 Fix problem with tokens in plugins 2026-02-18 17:20:46 +01:00
112 changed files with 1090 additions and 13029 deletions

View File

@@ -2,6 +2,9 @@
## 2.14.0 (Unreleased)
### :boom: Breaking changes & Deprecations
- Deprecate `PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE` in favour of `PENPOT_HTTP_SERVER_MAX_BODY_SIZE`.
### :sparkles: New features & Enhancements
- Access to design tokens in Penpot Plugins [Taiga #8990](https://tree.taiga.io/project/penpot/us/8990)

View File

@@ -28,8 +28,8 @@
com.google.guava/guava {:mvn/version "33.4.8-jre"}
funcool/yetti
{:git/tag "v11.8"
:git/sha "1d1b33f"
{:git/tag "v11.9"
:git/sha "5fad7a9"
:git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]}

View File

@@ -98,7 +98,6 @@
[:http-server-port {:optional true} ::sm/int]
[:http-server-host {:optional true} :string]
[:http-server-max-body-size {:optional true} ::sm/int]
[:http-server-max-multipart-body-size {:optional true} ::sm/int]
[:http-server-io-threads {:optional true} ::sm/int]
[:http-server-max-worker-threads {:optional true} ::sm/int]

View File

@@ -42,8 +42,8 @@
(def default-params
{::port 6060
::host "0.0.0.0"
::max-body-size 31457280 ; default 30 MiB
::max-multipart-body-size 367001600}) ; default 350 MiB
::max-body-size 367001600 ; default 350 MiB
})
(defmethod ig/expand-key ::server
[k v]
@@ -56,7 +56,6 @@
[::io-threads {:optional true} ::sm/int]
[::max-worker-threads {:optional true} ::sm/int]
[::max-body-size {:optional true} ::sm/int]
[::max-multipart-body-size {:optional true} ::sm/int]
[::router {:optional true} [:fn r/router?]]
[::handler {:optional true} ::sm/fn]])
@@ -79,7 +78,7 @@
{:http/port port
:http/host host
:http/max-body-size (::max-body-size cfg)
:http/max-multipart-body-size (::max-multipart-body-size cfg)
:http/max-multipart-body-size (::max-body-size cfg)
:xnio/direct-buffers false
:xnio/io-threads (::io-threads cfg)
:xnio/max-worker-threads (::max-worker-threads cfg)

View File

@@ -226,11 +226,10 @@
::http/server
{::http/port (cf/get :http-server-port)
::http/host (cf/get :http-server-host)
::http/router (ig/ref ::http/router)
::http/io-threads (cf/get :http-server-io-threads)
::http/max-worker-threads (cf/get :http-server-max-worker-threads)
::http/max-body-size (cf/get :http-server-max-body-size)
::http/max-multipart-body-size (cf/get :http-server-max-multipart-body-size)
::http/router (ig/ref ::http/router)
::mtx/metrics (ig/ref ::mtx/metrics)}
::ldap/provider

View File

@@ -605,31 +605,31 @@
add-undo-change-shape
(fn [change-set id]
(let [shape (get objects id)]
(cond-> change-set
(some? shape)
(conj {:type :add-obj
:id id
:page-id page-id
:parent-id (:parent-id shape)
:frame-id (:frame-id shape)
:index (cfh/get-position-on-parent objects id)
:obj (cond-> shape
(contains? shape :shapes)
(assoc :shapes []))}))))
(conj
change-set
{:type :add-obj
:id id
:page-id page-id
:parent-id (:parent-id shape)
:frame-id (:frame-id shape)
:index (cfh/get-position-on-parent objects id)
:obj (cond-> shape
(contains? shape :shapes)
(assoc :shapes []))})))
add-undo-change-parent
(fn [change-set id]
(let [shape (get objects id)
prev-sibling (cfh/get-prev-sibling objects (:id shape))]
(cond-> change-set
(some? shape)
(conj {:type :mov-objects
:page-id page-id
:parent-id (:parent-id shape)
:shapes [id]
:after-shape prev-sibling
:index 0
:ignore-touched true}))))]
(conj
change-set
{:type :mov-objects
:page-id page-id
:parent-id (:parent-id shape)
:shapes [id]
:after-shape prev-sibling
:index 0
:ignore-touched true})))]
(-> changes
(update :redo-changes #(reduce add-redo-change % ids))
@@ -1150,24 +1150,3 @@
[changes]
(::page-id (meta changes)))
(defn set-text-content
[changes id content prev-content]
(assert-page-id! changes)
(let [page-id (::page-id (meta changes))
redo-change
{:type :mod-obj
:page-id page-id
:id id
:operations [{:type :set :attr :content :val content}]}
undo-change
{:type :mod-obj
:page-id page-id
:id id
:operations [{:type :set :attr :content :val prev-content}]}]
(-> changes
(update :redo-changes conj redo-change)
(update :undo-changes conj undo-change))))

View File

@@ -50,6 +50,7 @@ services:
- 4400:4400
- 4401:4401
- 4402:4402
- 4403:4403
# Plugins
- 4200:4200

View File

@@ -126,6 +126,12 @@ http {
proxy_http_version 1.1;
}
location /plugins {
autoindex on;
alias /home/penpot/penpot/plugins/dist/apps;
proxy_http_version 1.1;
}
location /mcp/ws {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';

View File

@@ -30,11 +30,9 @@ x-uri: &penpot-public-uri
PENPOT_PUBLIC_URI: http://localhost:9001
x-body-size: &penpot-http-body-size
# Max body size (30MiB); Used for plain requests, should never be
# greater than multi-part size
PENPOT_HTTP_SERVER_MAX_BODY_SIZE: 31457280
# Max multipart body size (350MiB)
# Max body size
PENPOT_HTTP_SERVER_MAX_BODY_SIZE: 367001600
# Deprecation warning: this variable is deprecated. Use PENPOT_HTTP_SERVER_MAX_BODY (defaults to 367001600)
PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE: 367001600
## Penpot SECRET KEY. It serves as a master key from which other keys for subsystems

View File

@@ -30,8 +30,8 @@ update_flags /var/www/app/js/config.js
export PENPOT_BACKEND_URI=${PENPOT_BACKEND_URI:-http://penpot-backend:6060}
export PENPOT_EXPORTER_URI=${PENPOT_EXPORTER_URI:-http://penpot-exporter:6061}
export PENPOT_NITRATE_URI=${PENPOT_NITRATE_URI:-http://penpot-nitrate:3000}
export PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE:-367001600} # Default to 350MiB
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_NITRATE_URI,\$PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE" \
export PENPOT_HTTP_SERVER_MAX_BODY_SIZE=${PENPOT_HTTP_SERVER_MAX_BODY_SIZE:-367001600} # Default to 350MiB
envsubst "\$PENPOT_BACKEND_URI,\$PENPOT_EXPORTER_URI,\$PENPOT_NITRATE_URI,\$PENPOT_HTTP_SERVER_MAX_BODY_SIZE" \
< /tmp/nginx.conf.template > /etc/nginx/nginx.conf
PENPOT_DEFAULT_INTERNAL_RESOLVER="$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf)"

View File

@@ -76,7 +76,7 @@ http {
listen [::]:8080 default_server;
server_name _;
client_max_body_size $PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE;
client_max_body_size $PENPOT_HTTP_SERVER_MAX_BODY_SIZE;
charset utf-8;
etag off;

View File

@@ -188,8 +188,8 @@ server {
server_name penpot.mycompany.com;
# This value should be in sync with the corresponding in the docker-compose.yml
# PENPOT_HTTP_SERVER_MAX_BODY_SIZE: 31457280
client_max_body_size 31457280;
# PENPOT_HTTP_SERVER_MAX_BODY_SIZE: 367001600
client_max_body_size 367001600;
# Logs: Configure your logs following the best practices inside your company
access_log /path/to/penpot.access.log;

View File

@@ -40,15 +40,15 @@
"watch:app:libs": "node ./scripts/build-libs.js --watch",
"watch:app:main": "clojure -M:dev:shadow-cljs watch main worker storybook",
"clear:shadow-cache": "rm -rf .shadow-cljs",
"clear:wasm": "cargo clean --manifest-path ../render-wasm/Cargo.toml",
"watch": "exit 0",
"watch:app": "pnpm run clear:shadow-cache && pnpm run clear:wasm && pnpm run build:wasm && concurrently --kill-others-on-fail \"pnpm run watch:app:assets\" \"pnpm run watch:app:main\" \"pnpm run watch:app:libs\"",
"watch:storybook": "pnpm run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\""
"watch:app": "pnpm run clear:shadow-cache && pnpm run build:wasm && concurrently --kill-others-on-fail \"pnpm run watch:app:assets\" \"pnpm run watch:app:main\" \"pnpm run watch:app:libs\"",
"watch:storybook": "pnpm run build:storybook:assets && concurrently --kill-others-on-fail \"storybook dev -p 6006 --no-open\" \"node ./scripts/watch-storybook.js\"",
"postinstall": "(cd ../plugins/libs/plugins-runtime; pnpm run build)"
},
"devDependencies": {
"@penpot/draft-js": "workspace:./packages/draft-js",
"@penpot/mousetrap": "workspace:./packages/mousetrap",
"@penpot/plugins-runtime": "1.4.2",
"@penpot/plugins-runtime": "link:../plugins/libs/plugins-runtime",
"@penpot/svgo": "penpot/svgo#v3.2",
"@penpot/text-editor": "workspace:./text-editor",
"@penpot/tokenscript": "workspace:./packages/tokenscript",

View File

@@ -0,0 +1,26 @@
[
{
"~:features": {
"~#set": [
"variants/v1",
"layout/grid",
"styles/v2",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:permissions": {
"~:type": "~:membership",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true
},
"~:name": "Default",
"~:modified-at": "~m1713533116375",
"~:id": "~uc7ce0794-0992-8105-8004-38e630f7920a",
"~:created-at": "~m1713533116375",
"~:is-default": true
}
]

View File

@@ -0,0 +1,26 @@
{
"~:email": "foo@example.com",
"~:is-demo": false,
"~:auth-backend": "penpot",
"~:fullname": "Princesa Leia",
"~:modified-at": "~m1713533116365",
"~:is-active": true,
"~:default-project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
"~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
"~:is-muted": false,
"~:default-team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
"~:created-at": "~m1713533116365",
"~:is-blocked": false,
"~:props": {
"~:nudge": {
"~:big": 10,
"~:small": 1
},
"~:v2-info-shown": true,
"~:viewed-tutorial?": false,
"~:viewed-walkthrough?": false,
"~:onboarding-viewed": true,
"~:builtin-templates-collapsed-status":
true
}
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +0,0 @@
{
"~:file-id": "~u52c4e771-3853-8190-8007-9506c70e8100",
"~:id": "~u4173a29d-4020-80b4-8007-96527ba9d8af",
"~:created-at": "~m1771342236330",
"~:modified-at": "~m1771342236330",
"~:type": "fragment",
"~:backend": "db",
"~:data": {
"~:id": "~uecb0cfd0-0f0b-81f7-8007-950628f9665b",
"~:name": "Page 1",
"~:objects": {
"~#penpot/objects-map/v2": {
"~ude9c6736-45ce-80a1-8007-950643da554d": "[\"~#shape\",[\"^ \",\"~:y\",383.99998939037323,\"~: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\",\"R1\",\"~:width\",74,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",831.999992787838,\"~:y\",383.99998939037323]],[\"^<\",[\"^ \",\"~:x\",905.999992787838,\"~:y\",383.99998939037323]],[\"^<\",[\"^ \",\"~:x\",905.999992787838,\"~:y\",429.99998664855957]],[\"^<\",[\"^ \",\"~:x\",831.999992787838,\"~:y\",429.99998664855957]]],\"~: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]],\"~:page-id\",\"~uecb0cfd0-0f0b-81f7-8007-950628f9665b\",\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~ude9c6736-45ce-80a1-8007-950643da554d\",\"~:parent-id\",\"~ude9c6736-45ce-80a1-8007-95063a202e52\",\"~:frame-id\",\"~ude9c6736-45ce-80a1-8007-95063a202e52\",\"~:strokes\",[],\"~:x\",831.999992787838,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",831.999992787838,\"~:y\",383.99998939037323,\"^8\",74,\"~:height\",45.99999725818634,\"~:x1\",831.999992787838,\"~:y1\",383.99998939037323,\"~:x2\",905.999992787838,\"~:y2\",429.99998664855957]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^K\",45.99999725818634,\"~:flip-y\",null]]",
"~ude9c6736-45ce-80a1-8007-95065b4599ea": "[\"~#shape\",[\"^ \",\"~:y\",439.999969256975,\"~: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\",\"R2\",\"~:width\",300.00007388362076,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",832.0000015918991,\"~:y\",439.99996925697496]],[\"^<\",[\"^ \",\"~:x\",1132.00007547552,\"~:y\",439.99996925697496]],[\"^<\",[\"^ \",\"~:x\",1132.00007547552,\"~:y\",589.9999658711585]],[\"^<\",[\"^ \",\"~:x\",832.0000015918991,\"~:y\",589.9999658711585]]],\"~:r2\",0,\"~:layout-item-h-sizing\",\"~:fix\",\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~uecb0cfd0-0f0b-81f7-8007-950628f9665b\",\"~:layout-item-v-sizing\",\"^?\",\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~ude9c6736-45ce-80a1-8007-95065b4599ea\",\"~:parent-id\",\"~ude9c6736-45ce-80a1-8007-95063a202e52\",\"~:frame-id\",\"~ude9c6736-45ce-80a1-8007-95063a202e52\",\"~:strokes\",[],\"~:x\",832.0000015918993,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",832.0000015918993,\"~:y\",439.999969256975,\"^8\",300.00007388362076,\"~:height\",149.9999966141835,\"~:x1\",832.0000015918993,\"~:y1\",439.999969256975,\"~:x2\",1132.0000754755201,\"~:y2\",589.9999658711586]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^N\",149.9999966141835,\"~:flip-y\",null]]",
"~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]],\"~:page-id\",\"~uecb0cfd0-0f0b-81f7-8007-950628f9665b\",\"~: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,\"^I\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~ude9c6736-45ce-80a1-8007-95062aa41d6b\"]]]",
"~ude9c6736-45ce-80a1-8007-95062aa41d6b": "[\"~#shape\",[\"^ \",\"~:y\",342.0000208153068,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",0,\"~:p3\",0,\"~:p4\",0],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:grow-type\",\"~:fixed\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",false,\"~:name\",\"A\",\"~:layout-align-items\",\"~:start\",\"~:width\",392.9999889135361,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",787.9999763965607,\"~:y\",342.0000208153068]],[\"^L\",[\"^ \",\"~:x\",1180.9999653100967,\"~:y\",342.0000208153068]],[\"^L\",[\"^ \",\"~:x\",1180.9999653100967,\"~:y\",704.000018450968]],[\"^L\",[\"^ \",\"~:x\",787.9999763965607,\"~:y\",704.000018450968]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",0,\"~:column-gap\",0],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~uecb0cfd0-0f0b-81f7-8007-950628f9665b\",\"~:layout-item-v-sizing\",\"~:fix\",\"~:r3\",0,\"~:layout-justify-content\",\"^E\",\"~:r1\",0,\"~:id\",\"~ude9c6736-45ce-80a1-8007-95062aa41d6b\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:layout-flex-dir\",\"~:column\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",787.9999763965607,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",787.9999763965607,\"~:y\",342.0000208153068,\"^F\",392.9999889135361,\"~:height\",361.9999976356612,\"~:x1\",787.9999763965607,\"~:y1\",342.0000208153068,\"~:x2\",1180.9999653100967,\"~:y2\",704.000018450968]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^19\",361.9999976356612,\"~:flip-y\",null,\"~:shapes\",[\"~ude9c6736-45ce-80a1-8007-95062fafbb88\"]]]",
"~ude9c6736-45ce-80a1-8007-95063a202e52": "[\"~#shape\",[\"^ \",\"~:y\",374.00001145409465,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",10,\"~:p2\",10,\"~:p3\",10,\"~:p4\",10],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:grow-type\",\"~:fixed\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"C\",\"~:layout-align-items\",\"~:start\",\"~:width\",320.00009969013945,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",821.9999915631233,\"~:y\",374.0000114540946]],[\"^L\",[\"^ \",\"~:x\",1142.0000912532628,\"~:y\",374.0000114540946]],[\"^L\",[\"^ \",\"~:x\",1142.0000912532628,\"~:y\",600.0000518384436]],[\"^L\",[\"^ \",\"~:x\",821.9999915631233,\"~:y\",600.0000518384436]]],\"~:r2\",0,\"~:layout-item-h-sizing\",\"~:auto\",\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",10,\"~:column-gap\",0],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~uecb0cfd0-0f0b-81f7-8007-950628f9665b\",\"~:layout-item-v-sizing\",\"^O\",\"~:r3\",0,\"~:layout-justify-content\",\"^E\",\"~:r1\",0,\"~:id\",\"~ude9c6736-45ce-80a1-8007-95063a202e52\",\"~:parent-id\",\"~ude9c6736-45ce-80a1-8007-95062fafbb88\",\"~:layout-flex-dir\",\"~:column\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~ude9c6736-45ce-80a1-8007-95062fafbb88\",\"~:strokes\",[[\"^ \",\"~:stroke-alignment\",\"~:inner\",\"~:stroke-style\",\"~:solid\",\"~:stroke-color\",\"#000000\",\"~:stroke-opacity\",1,\"~:stroke-width\",1]],\"~:x\",821.9999915631233,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",821.9999915631233,\"~:y\",374.00001145409465,\"^F\",320.00009969013945,\"~:height\",226.000040384349,\"~:x1\",821.9999915631233,\"~:y1\",374.00001145409465,\"~:x2\",1142.0000912532628,\"~:y2\",600.0000518384436]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^1A\",226.000040384349,\"~:flip-y\",null,\"~:shapes\",[\"~ude9c6736-45ce-80a1-8007-95065b4599ea\",\"~ude9c6736-45ce-80a1-8007-950643da554d\"]]]",
"~ude9c6736-45ce-80a1-8007-95062fafbb88": "[\"~#shape\",[\"^ \",\"~:y\",342.0000083402533,\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",31.999953508377075,\"~:p2\",34.000026052944804,\"~:p3\",31.999953508377075,\"~:p4\",34.000026052944804],\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:layout-wrap-type\",\"~:nowrap\",\"~:grow-type\",\"~:fixed\",\"~:layout\",\"~:flex\",\"~:hide-in-viewer\",true,\"~:name\",\"B\",\"~:layout-align-items\",\"~:start\",\"~:width\",392.9999979687366,\"~:layout-padding-type\",\"~:simple\",\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",788.0000295450811,\"~:y\",342.00000834025326]],[\"^L\",[\"^ \",\"~:x\",1181.0000275138177,\"~:y\",342.00000834025326]],[\"^L\",[\"^ \",\"~:x\",1181.0000275138177,\"~:y\",631.9999659712]],[\"^L\",[\"^ \",\"~:x\",788.0000295450811,\"~:y\",631.9999659712]]],\"~:r2\",0,\"~:layout-item-h-sizing\",\"~:fill\",\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",0,\"~:column-gap\",0],\"~:transform-inverse\",[\"^:\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:page-id\",\"~uecb0cfd0-0f0b-81f7-8007-950628f9665b\",\"~:layout-item-v-sizing\",\"~:auto\",\"~:r3\",0,\"~:layout-justify-content\",\"^E\",\"~:r1\",0,\"~:id\",\"~ude9c6736-45ce-80a1-8007-95062fafbb88\",\"~:parent-id\",\"~ude9c6736-45ce-80a1-8007-95062aa41d6b\",\"~:layout-flex-dir\",\"~:column\",\"~:layout-align-content\",\"~:stretch\",\"~:frame-id\",\"~ude9c6736-45ce-80a1-8007-95062aa41d6b\",\"~:strokes\",[[\"^ \",\"~:stroke-alignment\",\"~:inner\",\"~:stroke-style\",\"~:solid\",\"~:stroke-color\",\"#000000\",\"~:stroke-opacity\",1,\"~:stroke-width\",1]],\"~:x\",788.0000295450811,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",788.0000295450811,\"~:y\",342.0000083402533,\"^F\",392.9999979687366,\"~:height\",289.99995763094677,\"~:x1\",788.0000295450811,\"~:y1\",342.0000083402533,\"~:x2\",1181.0000275138177,\"~:y2\",631.9999659712]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^1B\",289.99995763094677,\"~:flip-y\",null,\"~:shapes\",[\"~ude9c6736-45ce-80a1-8007-95063a202e52\"]]]"
}
}
}
}

View File

@@ -1,131 +0,0 @@
{
"~:features": {
"~#set": [
"fdata/path-data",
"design-tokens/v1",
"variants/v1",
"layout/grid",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:team-id": "~ud715d0a5-a44e-8056-8005-a79999e18b64",
"~: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 flex",
"~:revn": 33,
"~:modified-at": "~m1771342236324",
"~:vern": 0,
"~:id": "~u52c4e771-3853-8190-8007-9506c70e8100",
"~: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",
"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",
"0015-fix-text-attrs-blank-strings",
"0015-clean-shadow-color",
"0016-copy-fills-from-position-data-to-text-node"
]
},
"~:version": 67,
"~:project-id": "~u76eab896-accf-81a5-8007-2b264ebe7817",
"~:created-at": "~m1771255281717",
"~:backend": "legacy-db",
"~:data": {
"~:pages": [
"~uecb0cfd0-0f0b-81f7-8007-950628f9665b"
],
"~:pages-index": {
"~uecb0cfd0-0f0b-81f7-8007-950628f9665b": {
"~#penpot/pointer": [
"~u4173a29d-4020-80b4-8007-96527ba9d8af",
{
"~:created-at": "~m1771342236327"
}
]
}
},
"~:id": "~u52c4e771-3853-8190-8007-9506c70e8100",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
}
}
}

View File

@@ -1,136 +0,0 @@
{
"~: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": "~u99e49e93-362f-80ef-8007-3450ea52c9a4",
"~: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 13385",
"~:revn": 3,
"~:modified-at": "~m1771254407745",
"~:vern": 1173241426,
"~:id": "~u3ea49ce0-9d99-8197-8007-950361d24e43",
"~: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",
"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",
"0015-fix-text-attrs-blank-strings",
"0015-clean-shadow-color",
"0016-copy-fills-from-position-data-to-text-node"
]
},
"~:version": 67,
"~:project-id": "~ucd8f7672-e5d1-810f-8007-87e124eda82a",
"~:created-at": "~m1771254391625",
"~:backend": "legacy-db",
"~:data": {
"~:pages": [
"~u3ea49ce0-9d99-8197-8007-950361d24e44"
],
"~:pages-index": {
"~u3ea49ce0-9d99-8197-8007-950361d24e44": {
"~: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\",[\"~ue0e81bed-4dc2-805c-8007-95036a4a3131\",\"~ue0e81bed-4dc2-805c-8007-95036c27428b\"]]]",
"~ue0e81bed-4dc2-805c-8007-95036a4a3131": "[\"~#shape\",[\"^ \",\"~:y\",252,\"~: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\",177,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",156,\"~:y\",252]],[\"^<\",[\"^ \",\"~:x\",333,\"~:y\",252]],[\"^<\",[\"^ \",\"~:x\",333,\"~:y\",389]],[\"^<\",[\"^ \",\"~:x\",156,\"~:y\",389]]],\"~: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,\"~:r1\",0,\"~:id\",\"~ue0e81bed-4dc2-805c-8007-95036a4a3131\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",156,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",156,\"~:y\",252,\"^8\",177,\"~:height\",137,\"~:x1\",156,\"~:y1\",252,\"~:x2\",333,\"~:y2\",389]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",137,\"~:flip-y\",null]]",
"~ue0e81bed-4dc2-805c-8007-95036c27428b": "[\"~#shape\",[\"^ \",\"~:y\",250,\"~: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\",\"Ellipse\",\"~:width\",148,\"~:type\",\"~:circle\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",362,\"~:y\",250]],[\"^<\",[\"^ \",\"~:x\",510,\"~:y\",250]],[\"^<\",[\"^ \",\"~:x\",510,\"~:y\",389]],[\"^<\",[\"^ \",\"~:x\",362,\"~:y\",389]]],\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:id\",\"~ue0e81bed-4dc2-805c-8007-95036c27428b\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",362,\"~:proportion\",1,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",362,\"~:y\",250,\"^8\",148,\"~:height\",139,\"~:x1\",362,\"~:y1\",250,\"~:x2\",510,\"~:y2\",389]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^F\",139,\"~:flip-y\",null]]"
}
},
"~:id": "~u3ea49ce0-9d99-8197-8007-950361d24e44",
"~:name": "Page 1"
}
},
"~:id": "~u3ea49ce0-9d99-8197-8007-950361d24e43",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
}
}
}

View File

@@ -1,135 +0,0 @@
{
"~: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": "~u99e49e93-362f-80ef-8007-3450ea52c9a4",
"~: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 13385",
"~:revn": 2,
"~:modified-at": "~m1771254464312",
"~:vern": 0,
"~:id": "~u3ea49ce0-9d99-8197-8007-950361d24e43",
"~: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",
"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",
"0015-fix-text-attrs-blank-strings",
"0015-clean-shadow-color",
"0016-copy-fills-from-position-data-to-text-node"
]
},
"~:version": 67,
"~:project-id": "~ucd8f7672-e5d1-810f-8007-87e124eda82a",
"~:created-at": "~m1771254391625",
"~:backend": "legacy-db",
"~:data": {
"~:pages": [
"~u3ea49ce0-9d99-8197-8007-950361d24e44"
],
"~:pages-index": {
"~u3ea49ce0-9d99-8197-8007-950361d24e44": {
"~: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\",[\"~ue0e81bed-4dc2-805c-8007-95036a4a3131\"]]]",
"~ue0e81bed-4dc2-805c-8007-95036a4a3131": "[\"~#shape\",[\"^ \",\"~:y\",252,\"~: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\",177,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",156,\"~:y\",252]],[\"^<\",[\"^ \",\"~:x\",333,\"~:y\",252]],[\"^<\",[\"^ \",\"~:x\",333,\"~:y\",389]],[\"^<\",[\"^ \",\"~:x\",156,\"~:y\",389]]],\"~: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,\"~:r1\",0,\"~:id\",\"~ue0e81bed-4dc2-805c-8007-95036a4a3131\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",156,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",156,\"~:y\",252,\"^8\",177,\"~:height\",137,\"~:x1\",156,\"~:y1\",252,\"~:x2\",333,\"~:y2\",389]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",137,\"~:flip-y\",null]]"
}
},
"~:id": "~u3ea49ce0-9d99-8197-8007-950361d24e44",
"~:name": "Page 1"
}
},
"~:id": "~u3ea49ce0-9d99-8197-8007-950361d24e43",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
}
}
}

View File

@@ -1,135 +0,0 @@
{
"~: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": "~u99e49e93-362f-80ef-8007-3450ea52c9a4",
"~: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 13415",
"~:revn": 14,
"~:modified-at": "~m1771334256704",
"~:vern": 0,
"~:id": "~u0472abff-2573-8186-8007-961793e53f45",
"~: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",
"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",
"0015-fix-text-attrs-blank-strings",
"0015-clean-shadow-color",
"0016-copy-fills-from-position-data-to-text-node"
]
},
"~:version": 67,
"~:project-id": "~ucd8f7672-e5d1-810f-8007-87e124eda82a",
"~:created-at": "~m1771326794644",
"~:backend": "legacy-db",
"~:data": {
"~:pages": [
"~u0472abff-2573-8186-8007-961793e53f46"
],
"~:pages-index": {
"~u0472abff-2573-8186-8007-961793e53f46": {
"~: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\",[\"~uaef184da-e9c1-80f8-8007-961cf253d534\"]]]",
"~uaef184da-e9c1-80f8-8007-961cf253d534": "[\"~#shape\",[\"^ \",\"~:y\",286,\"~:layout-grid-columns\",[[\"^ \",\"~:type\",\"~:flex\",\"~:value\",1],[\"^ \",\"^2\",\"^3\",\"^4\",1]],\"~:hide-fill-on-export\",false,\"~:layout-gap-type\",\"~:multiple\",\"~:layout-padding\",[\"^ \",\"~:p1\",0,\"~:p2\",0,\"~:p3\",0,\"~:p4\",0],\"~: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\",\"~:layout\",\"~:grid\",\"~:hide-in-viewer\",false,\"~:name\",\"Board\",\"~:layout-align-items\",\"~:start\",\"~:width\",298,\"~:layout-grid-cells\",[\"^ \",\"~uaef184da-e9c1-80f8-8007-961cf50d67b4\",[\"^ \",\"~:justify-self\",\"~:auto\",\"~:column\",1,\"~:id\",\"~uaef184da-e9c1-80f8-8007-961cf50d67b4\",\"~:position\",\"^L\",\"~:column-span\",1,\"~:align-self\",\"^L\",\"~:row\",1,\"~:row-span\",1,\"~:shapes\",[]],\"~uaef184da-e9c1-80f8-8007-961cf50d67b5\",[\"^ \",\"^K\",\"^L\",\"^M\",2,\"^N\",\"~uaef184da-e9c1-80f8-8007-961cf50d67b5\",\"^O\",\"^L\",\"^P\",1,\"^Q\",\"^L\",\"^R\",1,\"^S\",1,\"^T\",[]],\"~uaef184da-e9c1-80f8-8007-961cf50d67b6\",[\"^ \",\"^K\",\"^L\",\"^M\",1,\"^N\",\"~uaef184da-e9c1-80f8-8007-961cf50d67b6\",\"^O\",\"^L\",\"^P\",1,\"^Q\",\"^L\",\"^R\",2,\"^S\",1,\"^T\",[]],\"~uaef184da-e9c1-80f8-8007-961cf50d67b7\",[\"^ \",\"^K\",\"^L\",\"^M\",2,\"^N\",\"~uaef184da-e9c1-80f8-8007-961cf50d67b7\",\"^O\",\"^L\",\"^P\",1,\"^Q\",\"^L\",\"^R\",2,\"^S\",1,\"^T\",[]]],\"~:layout-padding-type\",\"~:simple\",\"^2\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",322,\"~:y\",286]],[\"^10\",[\"^ \",\"~:x\",620,\"~:y\",286]],[\"^10\",[\"^ \",\"~:x\",620,\"~:y\",552]],[\"^10\",[\"^ \",\"~:x\",322,\"~:y\",552]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:layout-gap\",[\"^ \",\"~:row-gap\",0,\"~:column-gap\",0],\"~:transform-inverse\",[\"^>\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:layout-justify-content\",\"~:stretch\",\"~:r1\",0,\"^N\",\"~uaef184da-e9c1-80f8-8007-961cf253d534\",\"~:layout-justify-items\",\"^G\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:layout-align-content\",\"^19\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",322,\"~:proportion\",1,\"~:r4\",0,\"~:layout-grid-rows\",[[\"^ \",\"^2\",\"^3\",\"^4\",1],[\"^ \",\"^2\",\"^3\",\"^4\",1]],\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",322,\"~:y\",286,\"^H\",298,\"~:height\",266,\"~:x1\",322,\"~:y1\",286,\"~:x2\",620,\"~:y2\",552]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:layout-grid-dir\",\"^R\",\"~:flip-x\",null,\"^1E\",266,\"~:flip-y\",null,\"^T\",[]]]"
}
},
"~:id": "~u0472abff-2573-8186-8007-961793e53f46",
"~:name": "Page 1"
}
},
"~:id": "~u0472abff-2573-8186-8007-961793e53f45",
"~:options": {
"~:components-v2": true,
"~:base-font-size": "16px"
}
}
}

View File

@@ -1,23 +0,0 @@
[
{
"~:id": "~u3ea49ce0-9d99-8197-8007-95037190405b",
"~:label": "Version 000",
"~:revn": 1,
"~:version": 67,
"~:created-at": "~m1771254407745",
"~:modified-at": "~m1771254407745",
"~:created-by": "user",
"~:profile-id": "~u99e49e93-362f-80ef-8007-3450ea5204aa"
},
{
"~:revn": 0,
"~:modified-at": "~m1771254406526",
"~:deleted-at": "~m1771340806524",
"~:created-by": "system",
"~:label": "internal/snapshot/0",
"~:id": "~u3ea49ce0-9d99-8197-8007-9503705f8b9b",
"~:profile-id": "~u99e49e93-362f-80ef-8007-3450ea5204aa",
"~:version": 67,
"~:created-at": "~m1771254406526"
}
]

View File

@@ -1,9 +0,0 @@
[
{
"~:id": "~u99e49e93-362f-80ef-8007-3450ea5204aa",
"~:email": "belen@example.com",
"~:name": "Belén Albeza",
"~:fullname": "Belén Albeza",
"~:is-active": true
}
]

View File

@@ -1,18 +0,0 @@
{
"~:revn": 14,
"~:lagged": [
{
"~:id": "~u0472abff-2573-8186-8007-96347d525f65",
"~:revn": 15,
"~:file-id": "~u0472abff-2573-8186-8007-961793e53f45",
"~:session-id": "~uf25e6d2f-d10c-8021-8007-96344433f08d",
"~:changes": [
{
"~:type": "~:del-obj",
"~:page-id": "~u0472abff-2573-8186-8007-961793e53f46",
"~:id": "~uaef184da-e9c1-80f8-8007-961cf253d534"
}
]
}
]
}

View File

@@ -1,8 +1,4 @@
export class BasePage {
static async init(page) {
await BasePage.mockConfigFlags(page, []);
}
/**
* Mocks multiple RPC calls in a single call.
*
@@ -26,7 +22,7 @@ export class BasePage {
* @param {*} options
* @returns {Promise<void>}
*/
static async mockRPC(page, path, jsonFilename = "", options = {}) {
static async mockRPC(page, path, jsonFilename, options) {
if (!page) {
throw new TypeError("Invalid page argument. Must be a Playwright page.");
}
@@ -45,7 +41,7 @@ export class BasePage {
return page.route(url, (route) =>
route.fulfill({
...interceptConfig,
path: jsonFilename ? `playwright/data/${jsonFilename}` : undefined,
path: `playwright/data/${jsonFilename}`,
}),
);
}

View File

@@ -2,8 +2,13 @@ import { MockWebSocketHelper } from "../../helpers/MockWebSocketHelper";
import BasePage from "./BasePage";
export class BaseWebSocketPage extends BasePage {
static async init(page) {
await super.init(page);
/**
* This should be called on `test.beforeEach`.
*
* @param {Page} page
* @returns
*/
static async initWebSockets(page) {
await MockWebSocketHelper.init(page);
}

View File

@@ -3,62 +3,54 @@ import { BaseWebSocketPage } from "./BaseWebSocketPage";
export class DashboardPage extends BaseWebSocketPage {
static async init(page) {
await super.init(page);
await BaseWebSocketPage.initWebSockets(page);
await super.mockConfigFlags(page, ["disable-onboarding"]);
await super.mockRPC(
await BaseWebSocketPage.mockRPC(
page,
"get-teams",
"logged-in-user/get-teams-default.json",
);
await super.mockRPC(
await BaseWebSocketPage.mockRPC(
page,
"get-font-variants?team-id=*",
"workspace/get-font-variants-empty.json",
);
await super.mockRPC(
await BaseWebSocketPage.mockRPC(
page,
"get-projects?team-id=*",
"logged-in-user/get-projects-default.json",
);
await super.mockRPC(
await BaseWebSocketPage.mockRPC(
page,
"get-team-members?team-id=*",
"logged-in-user/get-team-members-your-penpot.json",
);
await super.mockRPC(
await BaseWebSocketPage.mockRPC(
page,
"get-team-users?team-id=*",
"logged-in-user/get-team-users-single-user.json",
);
await super.mockRPC(
await BaseWebSocketPage.mockRPC(
page,
"get-unread-comment-threads?team-id=*",
"logged-in-user/get-team-users-single-user.json",
);
await super.mockRPC(
await BaseWebSocketPage.mockRPC(
page,
"get-team-recent-files?team-id=*",
"logged-in-user/get-team-recent-files-empty.json",
);
await super.mockRPC(
await BaseWebSocketPage.mockRPC(
page,
"get-profiles-for-file-comments",
"workspace/get-profile-for-file-comments.json",
);
await super.mockRPC(
await BaseWebSocketPage.mockRPC(
page,
"get-builtin-templates",
"logged-in-user/get-built-in-templates-empty.json",
);
await super.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in.json",
);
}
static anyTeamId = "c7ce0794-0992-8105-8004-38e630f40f6d";

View File

@@ -1,10 +1,6 @@
import { BasePage } from "./BasePage";
export class LoginPage extends BasePage {
static async init(page) {
await super.init(page);
}
constructor(page) {
super(page);
this.loginButton = page.getByRole("button", { name: "Continue" });

View File

@@ -29,13 +29,8 @@ export class RegisterPage extends BasePage {
);
}
static async init(page) {
await BasePage.init(page);
}
static async initWithLoggedOutUser(page) {
await BasePage.init(page);
await BasePage.mockRPC(page, "get-profile", "get-profile-anonymous.json");
await this.mockRPC(page, "get-profile", "get-profile-anonymous.json");
}
}

View File

@@ -3,9 +3,9 @@ import { DashboardPage } from "./DashboardPage";
export class SubscriptionProfilePage extends DashboardPage {
static async init(page) {
await super.init(page);
await DashboardPage.initWebSockets(page);
await super.mockRPC(
await DashboardPage.mockRPC(
page,
"get-subscription-usage",
"subscription/get-subscription-usage.json",

View File

@@ -4,6 +4,16 @@ export class ViewerPage extends BaseWebSocketPage {
static anyFileId = "c7ce0794-0992-8105-8004-38f280443849";
static anyPageId = "c7ce0794-0992-8105-8004-38f28044384a";
/**
* This should be called on `test.beforeEach`.
*
* @param {Page} page
* @returns
*/
static async init(page) {
await BaseWebSocketPage.initWebSockets(page);
}
async setupLoggedInUser() {
await this.mockRPC(
"get-profile",

View File

@@ -35,8 +35,8 @@ export class WasmWorkspacePage extends WorkspacePage {
return WasmWorkspacePage.mockConfigFlags(this.page, flags);
}
constructor(page, options) {
super(page, options);
constructor(page) {
super(page);
this.canvas = page.getByTestId("canvas-wasm-shapes");
}
@@ -54,19 +54,6 @@ export class WasmWorkspacePage extends WorkspacePage {
await this.hideUI();
}
async getRenderCount() {
return this.page.evaluate(() => window.wasmRenderCount || 0);
}
async waitForNextRender(previousCount = null) {
const baseCount =
previousCount === null ? await this.getRenderCount() : previousCount;
await this.page.waitForFunction(
(count) => (window.wasmRenderCount || 0) > count,
baseCount,
);
}
async hideUI() {
await this.page.keyboard.press("\\");
await expect(this.pageName).not.toBeVisible();

View File

@@ -35,9 +35,45 @@ export class WorkspacePage extends BaseWebSocketPage {
}
async waitForEditor() {
const typographyInput =
this.workspacePage.rightSidebar.getByLabel("Font Size");
await expect(typographyInput).toBeVisible();
return this.page.waitForSelector('[data-itype="editor"]');
}
async waitForRoot() {
return this.page.waitForSelector('[data-itype="root"]');
}
async waitForParagraph(nth) {
if (!nth) {
return this.page.waitForSelector('[data-itype="paragraph"]');
}
return this.page.waitForSelector(
`[data-itype="paragraph"]:nth-child(${nth})`,
);
}
async waitForParagraphStyle(nth, styleName) {
const paragraph = await this.waitForParagraph(nth);
return this.waitForStyle(paragraph, styleName);
}
async waitForTextSpan(nth = 0) {
if (!nth) {
return this.page.waitForSelector('[data-itype="span"]');
}
return this.page.waitForSelector(
`[data-itype="span"]:nth-child(${nth})`,
);
}
async waitForTextSpanContent(nth = 0) {
const textSpan = await this.waitForTextSpan(nth);
const textContent = await textSpan.textContent();
return textContent;
}
async waitForTextSpanStyle(nth, styleName) {
const textSpan = await this.waitForTextSpan(nth);
return this.waitForStyle(textSpan, styleName);
}
async startEditing() {
@@ -45,27 +81,24 @@ export class WorkspacePage extends BaseWebSocketPage {
return this.waitForEditor();
}
async stopEditing() {
await this.page.keyboard.press("Escape");
stopEditing() {
return this.page.keyboard.press("Escape");
}
async moveToLeft(amount = 0) {
for (let i = 0; i < amount; i++) {
await this.page.keyboard.press("ArrowLeft");
}
await this.waitForIdle();
}
async moveToRight(amount = 0) {
for (let i = 0; i < amount; i++) {
await this.page.keyboard.press("ArrowRight");
}
await this.waitForIdle();
}
async moveFromStart(offset = 0) {
await this.page.keyboard.press("Home");
await this.waitForIdle();
await this.page.keyboard.press("ArrowLeft");
await this.moveToRight(offset);
}
@@ -92,7 +125,7 @@ export class WorkspacePage extends BaseWebSocketPage {
await expect(locator).toBeVisible();
await locator.focus();
await locator.fill(`${newValue}`);
await this.page.keyboard.press("Enter");
await locator.blur();
}
changeFontSize(newValue) {
@@ -106,10 +139,6 @@ export class WorkspacePage extends BaseWebSocketPage {
changeLetterSpacing(newValue) {
return this.changeNumericInput(this.letterSpacing, newValue);
}
async waitForIdle() {
await this.page.evaluate(() => new Promise((resolve) => globalThis.requestIdleCallback(resolve)));
}
};
/**
@@ -119,9 +148,9 @@ export class WorkspacePage extends BaseWebSocketPage {
* @returns
*/
static async init(page) {
await super.init(page);
await BaseWebSocketPage.initWebSockets(page);
await super.mockRPCs(page, {
await BaseWebSocketPage.mockRPCs(page, {
"get-profile": "logged-in-user/get-profile-logged-in.json",
"get-team-users?file-id=*":
"logged-in-user/get-team-users-single-user.json",
@@ -288,6 +317,7 @@ export class WorkspacePage extends BaseWebSocketPage {
body,
}),
);
// await this.mockRPC(/get\-file\?/, jsonFile);
}
async mockGetAsset(regex, asset) {
@@ -361,12 +391,10 @@ export class WorkspacePage extends BaseWebSocketPage {
const timeToWait = options?.timeToWait ?? 100;
await this.page.keyboard.press("T");
await this.page.waitForTimeout(timeToWait);
const layersCountBefore = await this.layers.getByTestId("layer-row").count();
await this.clickAndMove(x1, y1, x2, y2);
await expect(this.page.getByTestId("text-editor")).toBeVisible();
if (initialText) {
await this.waitForSelectedShapeName("Text");
await this.page.keyboard.type(initialText);
}
}
@@ -466,23 +494,10 @@ export class WorkspacePage extends BaseWebSocketPage {
async expectSelectedLayer(name) {
await expect(
this.layers.getByRole("checkbox", { name, checked: true }),
).toBeVisible();
}
async getSelectedShapeName() {
const selectedLayer = this.layers
.getByRole("checkbox", { checked: true })
.first();
await selectedLayer.waitFor({ state: "visible" });
return (await selectedLayer.innerText()).trim();
}
async waitForSelectedShapeName(expectedName) {
const selectedLayer = this.layers
.getByRole("checkbox", { checked: true })
.first();
await expect(selectedLayer).toHaveText(expectedName);
this.layers
.getByTestId("layer-row")
.filter({ has: this.page.getByText(name) }),
).toHaveClass(/selected/);
}
async expectHiddenToolbarOptions() {

View File

@@ -356,39 +356,3 @@ test("Renders shapes with multiple fills and blur", async ({
await expect(workspace.canvas).toHaveScreenshot();
});
test("Keeps component visible when focusing after creating it", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.mockRPC(/get\-file\?/, "workspace/get-file-not-empty.json");
await workspace.mockRPC(
"update-file?id=*",
"workspace/update-file-create-rect.json",
);
await workspace.goToWorkspace({
fileId: "6191cd35-bb1f-81f7-8004-7cc63d087374",
pageId: "6191cd35-bb1f-81f7-8004-7cc63d087375",
});
await workspace.waitForFirstRender();
await workspace.clickLayers();
await workspace.clickLeafLayer("Rectangle");
await page.keyboard.press("ControlOrMeta+k");
const componentLayer = workspace.layers
.getByTestId("layer-row")
.filter({ has: page.getByTestId("icon-component") })
.first();
await expect(componentLayer).toBeVisible();
await componentLayer.click();
const previousRenderCount = await workspace.getRenderCount();
await page.keyboard.press("f");
await workspace.waitForNextRender(previousRenderCount);
await workspace.hideUI();
await expect(workspace.canvas).toHaveScreenshot();
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 KiB

After

Width:  |  Height:  |  Size: 348 KiB

View File

@@ -3,6 +3,11 @@ import DashboardPage from "../pages/DashboardPage";
test.beforeEach(async ({ page }) => {
await DashboardPage.init(page);
await DashboardPage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in-no-onboarding.json",
);
});
test.describe("Dashboard Deleted Page", () => {

View File

@@ -3,6 +3,11 @@ import DashboardPage from "../pages/DashboardPage";
test.beforeEach(async ({ page }) => {
await DashboardPage.init(page);
await DashboardPage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in-no-onboarding.json",
);
});
test("BUG 10421 - Fix libraries context menu", async ({ page }) => {

View File

@@ -3,6 +3,11 @@ import DashboardPage from "../pages/DashboardPage";
test.beforeEach(async ({ page }) => {
await DashboardPage.init(page);
await DashboardPage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in-no-onboarding.json",
);
});
test("BUG 12359 - Selected invitations count is not pluralized", async ({

View File

@@ -3,7 +3,11 @@ import DashboardPage from "../pages/DashboardPage";
test.beforeEach(async ({ page }) => {
await DashboardPage.init(page);
await DashboardPage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in-no-onboarding.json",
);
await DashboardPage.mockRPC(
page,
"get-teams",

View File

@@ -3,6 +3,11 @@ import DashboardPage from "../pages/DashboardPage";
test.beforeEach(async ({ page }) => {
await DashboardPage.init(page);
await DashboardPage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in-no-onboarding.json",
);
});
test("Dashboad page has title ", async ({ page }) => {

View File

@@ -2,8 +2,6 @@ import { test, expect } from "@playwright/test";
import { LoginPage } from "../pages/LoginPage";
test.beforeEach(async ({ page }) => {
await LoginPage.init(page);
const login = new LoginPage(page);
await login.initWithLoggedOutUser();

View File

@@ -4,7 +4,6 @@ import OnboardingPage from "../pages/OnboardingPage";
test.beforeEach(async ({ page }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, ["enable-onboarding"]);
await DashboardPage.mockRPC(
page,
"get-profile",

View File

@@ -3,6 +3,11 @@ import DashboardPage from "../pages/DashboardPage";
test.beforeEach(async ({ page }) => {
await DashboardPage.init(page);
await DashboardPage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in-no-onboarding.json",
);
});
test("Navigate to penpot changelog from profile menu", async ({ page }) => {

View File

@@ -1,12 +1,14 @@
import { test, expect } from "@playwright/test";
import { Clipboard } from "../../helpers/Clipboard";
import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
import { WorkspacePage } from "../pages/WorkspacePage";
const timeToWait = 100;
test.beforeEach(async ({ page, context }) => {
await Clipboard.enable(context, Clipboard.Permission.ALL);
await WasmWorkspacePage.init(page);
await WasmWorkspacePage.mockConfigFlags(page, ["enable-feature-text-editor-v2"]);
await WorkspacePage.init(page);
await WorkspacePage.mockConfigFlags(page, ["enable-feature-text-editor-v2"]);
});
test.afterEach(async ({ context }) => {
@@ -15,36 +17,39 @@ test.afterEach(async ({ context }) => {
test("Create a new text shape", async ({ page }) => {
const initialText = "Lorem ipsum";
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
await workspace.goToWorkspace();
await workspace.createTextShape(190, 150, 300, 200, initialText);
await workspace.textEditor.stopEditing();
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe(initialText);
await workspace.waitForSelectedShapeName(initialText);
await workspace.textEditor.stopEditing();
});
test("Create a new text shape from pasting text", async ({ page, context }) => {
const textToPaste = "Lorem ipsum";
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
await workspace.mockRPC("update-file?id=*", "text-editor/update-file.json");
await workspace.goToWorkspace();
await workspace.moveButton.click();
await Clipboard.writeText(page, textToPaste);
await workspace.clickAt(190, 150);
await workspace.paste("keyboard");
await workspace.textEditor.stopEditing();
await page.waitForTimeout(timeToWait);
await expect(workspace.layers.getByText(textToPaste)).toBeVisible();
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe(textToPaste);
await workspace.textEditor.stopEditing();
});
test("Create a new text shape from pasting text using context menu", async ({
@@ -52,26 +57,27 @@ test("Create a new text shape from pasting text using context menu", async ({
context,
}) => {
const textToPaste = "Lorem ipsum";
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
await workspace.goToWorkspace();
await workspace.moveButton.click();
await Clipboard.writeText(page, textToPaste);
await workspace.clickAt(190, 150);
await workspace.paste("context-menu");
await workspace.textEditor.stopEditing();
await expect(workspace.layers.getByText(textToPaste)).toBeVisible();
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe(textToPaste);
await workspace.textEditor.stopEditing();
});
test("Update an already created text shape by appending text", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -81,14 +87,15 @@ test("Update an already created text shape by appending text", async ({
await workspace.textEditor.startEditing();
await workspace.textEditor.moveFromEnd(0);
await page.keyboard.type(" dolor sit amet");
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe("Lorem ipsum dolor sit amet");
await workspace.textEditor.stopEditing();
await workspace.waitForSelectedShapeName("Lorem ipsum dolor sit amet");
});
test("Update an already created text shape by prepending text", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -98,14 +105,15 @@ test("Update an already created text shape by prepending text", async ({
await workspace.textEditor.startEditing();
await workspace.textEditor.moveFromStart(0);
await page.keyboard.type("Dolor sit amet ");
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe("Dolor sit amet Lorem ipsum");
await workspace.textEditor.stopEditing();
await workspace.waitForSelectedShapeName("Dolor sit amet Lorem ipsum");
});
test.skip("Update an already created text shape by inserting text in between", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -115,8 +123,9 @@ test.skip("Update an already created text shape by inserting text in between", a
await workspace.textEditor.startEditing();
await workspace.textEditor.moveFromStart(5);
await page.keyboard.type(" dolor sit amet");
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe("Lorem dolor sit amet ipsum");
await workspace.textEditor.stopEditing();
await workspace.waitForSelectedShapeName("Lorem dolor sit amet ipsum");
});
test("Update a new text shape appending text by pasting text", async ({
@@ -124,7 +133,7 @@ test("Update a new text shape appending text by pasting text", async ({
context,
}) => {
const textToPaste = " dolor sit amet";
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -137,8 +146,9 @@ test("Update a new text shape appending text by pasting text", async ({
await workspace.textEditor.startEditing();
await workspace.textEditor.moveFromEnd();
await workspace.paste("keyboard");
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe("Lorem ipsum dolor sit amet");
await workspace.textEditor.stopEditing();
await workspace.waitForSelectedShapeName("Lorem ipsum dolor sit amet");
});
test.skip("Update a new text shape prepending text by pasting text", async ({
@@ -146,7 +156,7 @@ test.skip("Update a new text shape prepending text by pasting text", async ({
context,
}) => {
const textToPaste = "Dolor sit amet ";
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -159,17 +169,16 @@ test.skip("Update a new text shape prepending text by pasting text", async ({
await workspace.textEditor.startEditing();
await workspace.textEditor.moveFromStart();
await workspace.paste("keyboard");
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe("Dolor sit amet Lorem ipsum");
await workspace.textEditor.stopEditing();
await workspace.hideUI();
await expect(workspace.canvas).toHaveScreenshot();
});
test("Update a new text shape replacing (starting) text with pasted text", async ({
page,
}) => {
const textToPaste = "Dolor sit amet";
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -183,15 +192,17 @@ test("Update a new text shape replacing (starting) text with pasted text", async
await workspace.paste("keyboard");
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe("Dolor sit amet ipsum");
await workspace.textEditor.stopEditing();
await workspace.waitForSelectedShapeName("Dolor sit amet ipsum");
});
test("Update a new text shape replacing (ending) text with pasted text", async ({
page,
}) => {
const textToPaste = "dolor sit amet";
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -205,15 +216,17 @@ test("Update a new text shape replacing (ending) text with pasted text", async (
await workspace.paste("keyboard");
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe("Lorem dolor sit amet");
await workspace.textEditor.stopEditing();
await workspace.waitForSelectedShapeName("Lorem dolor sit amet");
});
test("Update a new text shape replacing (in between) text with pasted text", async ({
page,
}) => {
const textToPaste = "dolor sit amet";
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -227,14 +240,16 @@ test("Update a new text shape replacing (in between) text with pasted text", asy
await workspace.paste("keyboard");
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe("Lordolor sit ametsum");
await workspace.textEditor.stopEditing();
await workspace.waitForSelectedShapeName("Lordolor sit ametsum");
});
test("Update text font size selecting a part of it (starting)", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -245,16 +260,18 @@ test("Update text font size selecting a part of it (starting)", async ({
await workspace.textEditor.startEditing();
await workspace.textEditor.selectFromStart(5);
await workspace.textEditor.changeFontSize(36);
await workspace.textEditor.stopEditing();
await workspace.hideUI();
await expect(workspace.canvas).toHaveScreenshot();
const textContent1 = await workspace.textEditor.waitForTextSpanContent(1);
expect(textContent1).toBe("Lorem");
const textContent2 = await workspace.textEditor.waitForTextSpanContent(2);
expect(textContent2).toBe(" ipsum");
await workspace.textEditor.stopEditing();
});
test("Update text line height selecting a part of it (starting)", async ({
test.skip("Update text line height selecting a part of it (starting)", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -264,17 +281,24 @@ test("Update text line height selecting a part of it (starting)", async ({
await workspace.clickLeafLayer("Lorem ipsum");
await workspace.textEditor.startEditing();
await workspace.textEditor.selectFromStart(5);
await workspace.textEditor.changeLineHeight(4.4);
await workspace.textEditor.stopEditing();
await workspace.textEditor.changeLineHeight(1.4);
await workspace.hideUI();
await expect(workspace.canvas).toHaveScreenshot();
const lineHeight = await workspace.textEditor.waitForParagraphStyle(
1,
"line-height",
);
expect(lineHeight).toBe("1.4");
const textContent = await workspace.textEditor.waitForTextSpanContent();
expect(textContent).toBe("Lorem ipsum");
await workspace.textEditor.stopEditing();
});
test.skip("Update text letter spacing selecting a part of it (starting)", async ({
page,
}) => {
const workspace = new WasmWorkspacePage(page, {
const workspace = new WorkspacePage(page, {
textEditor: true,
});
await workspace.setupEmptyFile();
@@ -285,14 +309,16 @@ test.skip("Update text letter spacing selecting a part of it (starting)", async
await workspace.textEditor.startEditing();
await workspace.textEditor.selectFromStart(5);
await workspace.textEditor.changeLetterSpacing(10);
await workspace.textEditor.stopEditing();
await workspace.hideUI();
await expect(workspace.canvas).toHaveScreenshot();
const textContent1 = await workspace.textEditor.waitForTextSpanContent(1);
expect(textContent1).toBe("Lorem");
const textContent2 = await workspace.textEditor.waitForTextSpanContent(2);
expect(textContent2).toBe(" ipsum");
await workspace.textEditor.stopEditing();
});
test("BUG 11552 - Apply styles to the current caret", async ({ page }) => {
const workspace = new WasmWorkspacePage(page);
const workspace = new WorkspacePage(page);
await workspace.setupEmptyFile();
await workspace.mockGetFile("text-editor/get-file-11552.json");
await workspace.mockRPC(

View File

@@ -1,5 +1,5 @@
import { test, expect } from "@playwright/test";
import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
import { BaseWebSocketPage } from "../pages/BaseWebSocketPage";
import { Clipboard } from "../../helpers/Clipboard";
@@ -7,7 +7,7 @@ test.beforeEach(async ({ page, context }) => {
await Clipboard.enable(context, Clipboard.Permission.ALL);
await WasmWorkspacePage.init(page);
await WasmWorkspacePage.mockConfigFlags(page, ["enable-feature-variants"]);
await BaseWebSocketPage.mockRPC(page, "get-teams", "get-teams-variants.json");
});
test.afterEach(async ({ context }) => {

View File

@@ -1,5 +1,6 @@
import { test, expect } from "@playwright/test";
import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
import { presenceFixture } from "../../data/workspace/ws-notifications";
test.beforeEach(async ({ page }) => {
await WasmWorkspacePage.init(page);
@@ -105,37 +106,9 @@ test("BUG 11006 - Fix history panel shortcut", async ({ page }) => {
await workspacePage.goToWorkspace();
await page.keyboard.press("ControlOrMeta+Alt+h");
await page.keyboard.press("Control+Alt+h");
await expect(
workspacePage.rightSidebar.getByText("There are no versions yet"),
).toBeVisible();
});
test("BUG 13385 - Fix viewport not updating when restoring version", async ({ page }) => {
const workspacePage = new WasmWorkspacePage(page);
await workspacePage.setupEmptyFile();
await workspacePage.mockGetFile("workspace/get-file-13385.json");
await workspacePage.mockRPC("get-profiles-for-file-comments?file-id=*", "workspace/get-profiles-for-file-comments-13385.json");
// navigate to workspace and check that the circle shape is not there
await workspacePage.goToWorkspace();
await expect(workspacePage.layers.getByText("Ellipse")).not.toBeVisible();
// mock network requests to restore the version
await workspacePage.mockGetFile("workspace/get-file-13385-2.json");
await workspacePage.mockRPC("get-file-snapshots?file-id=*", "workspace/get-file-snapshots-13385.json");
await workspacePage.mockRPC("restore-file-snapshot", "", {
status: 204,
});
// request to restore the version
await workspacePage.rightSidebar.getByRole("button", { name: "History" }).click();
await workspacePage.rightSidebar.getByRole("button", { name: "Open version menu" }).click();
await workspacePage.rightSidebar.getByRole("button", { name: "Restore" }).click();
// confirm modal
await workspacePage.page.getByRole("button", { name: /Restore/i }).click();
// assert that the circle shape exists
await expect(workspacePage.layers.getByText("Ellipse")).toBeVisible();
});

View File

@@ -23,35 +23,4 @@ test("BUG 13305 - Fix resize board to fit content", async ({ page }) => {
await expect(workspacePage.rightSidebar.getByTitle("Height").getByRole("textbox")).toHaveValue("630");
await expect(workspacePage.rightSidebar.getByTitle("X axis").getByRole("textbox")).toHaveValue("110");
await expect(workspacePage.rightSidebar.getByTitle("Y axis").getByRole("textbox")).toHaveValue("110");
});
test("BUG 13382 - Fix problem with flex layout", async ({ page }) => {
const workspacePage = new WasmWorkspacePage(page);
await workspacePage.setupEmptyFile();
await workspacePage.mockGetFile("workspace/get-file-13382.json");
await workspacePage.mockRPC(
"get-file-fragment?file-id=*&fragment-id=*",
"workspace/get-file-13382-fragment.json",
);
await workspacePage.mockRPC("update-file?id=*", "workspace/update-file-empty.json");
await workspacePage.goToWorkspace({
fileId: "52c4e771-3853-8190-8007-9506c70e8100",
pageId: "ecb0cfd0-0f0b-81f7-8007-950628f9665b",
});
await workspacePage.clickToggableLayer("A");
await workspacePage.clickToggableLayer("B");
await workspacePage.clickToggableLayer("C");
await workspacePage.clickLeafLayer("R2");
const heightText = workspacePage.rightSidebar.getByTitle("Height").getByPlaceholder('--');
await heightText.fill("200");
await heightText.press("Enter");
await workspacePage.clickLeafLayer("B");
await expect(workspacePage.rightSidebar.getByTitle("Height").getByRole("textbox")).toHaveValue("340");
});
});

View File

@@ -483,25 +483,3 @@ test("Bug 8371 - Flatten option is not visible in context menu", async ({
.filter({ visible: true }),
).toBeVisible();
});
test("BUG 13415 - Grid layout overlay is not removed when deleting a board", async ({
page,
}) => {
const workspacePage = new WasmWorkspacePage(page);
await workspacePage.setupEmptyFile(page);
await workspacePage.mockGetFile("workspace/get-file-13415.json");
await workspacePage.mockRPC(
"update-file?id=*",
"workspace/update-file-13415.json",
);
await workspacePage.goToWorkspace();
await workspacePage.clickLeafLayer("Board");
const currentRenderCount = await workspacePage.getRenderCount();
await workspacePage.page.keyboard.press("Delete");
await workspacePage.waitForNextRender(currentRenderCount);
await workspacePage.hideUI();
await expect(workspacePage.canvas).toHaveScreenshot();
});

View File

@@ -3,6 +3,11 @@ import DashboardPage from "../pages/DashboardPage";
test.beforeEach(async ({ page }) => {
await DashboardPage.init(page);
await DashboardPage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in-no-onboarding.json",
);
});
test("User goes to an empty dashboard", async ({ page }) => {

View File

@@ -2,8 +2,6 @@ import { test, expect } from "@playwright/test";
import { LoginPage } from "../pages/LoginPage";
test.beforeEach(async ({ page }) => {
await LoginPage.init(page);
const login = new LoginPage(page);
await login.initWithLoggedOutUser();
await login.page.goto("/#/auth/login");

View File

@@ -20,8 +20,8 @@ importers:
specifier: workspace:./packages/mousetrap
version: link:packages/mousetrap
'@penpot/plugins-runtime':
specifier: 1.4.2
version: 1.4.2
specifier: link:../plugins/libs/plugins-runtime
version: link:../plugins/libs/plugins-runtime
'@penpot/svgo':
specifier: penpot/svgo#v3.2
version: svgo@https://codeload.github.com/penpot/svgo/tar.gz/8c9b0e32e9cb5f106085260bd9375f3c91a5010b
@@ -581,15 +581,6 @@ packages:
'@dabh/diagnostics@2.0.8':
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
'@endo/cache-map@1.1.0':
resolution: {integrity: sha512-owFGshs/97PDw9oguZqU/px8Lv1d0KjAUtDUiPwKHNXRVUE/jyettEbRoTbNJR1OaI8biMn6bHr9kVJsOh6dXw==}
'@endo/env-options@1.1.11':
resolution: {integrity: sha512-p9OnAPsdqoX4YJsE98e3NBVhIr2iW9gNZxHhAI2/Ul5TdRfoOViItzHzTqrgUVopw6XxA1u1uS6CykLMDUxarA==}
'@endo/immutable-arraybuffer@1.1.2':
resolution: {integrity: sha512-u+NaYB2aqEugQ3u7w3c5QNkPogf8q/xGgsPaqdY6pUiGWtYiTiFspKFcha6+oeZhWXWQ23rf0KrUq0kfuzqYyQ==}
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'}
@@ -1258,12 +1249,6 @@ packages:
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
engines: {node: '>= 10.0.0'}
'@penpot/plugin-types@1.4.2':
resolution: {integrity: sha512-O8wU6RSYE8bIVU7g8cSTYi32ppxs3R13dq7X3Nn9tmDaJjBOKOBpVLuoRPIp3fJC65fv8/7om0sdrtFoL5v19g==}
'@penpot/plugins-runtime@1.4.2':
resolution: {integrity: sha512-y9TDZOnb96JBW9E33dHKpmTMeAPXLtHDIZruUVjtM8hBJWZK7RCv+vAGDGxeoZJC/OB2YAHrCZG+mukePBzcuQ==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -4636,9 +4621,6 @@ packages:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
ses@1.14.0:
resolution: {integrity: sha512-T07hNgOfVRTLZGwSS50RnhqrG3foWP+rM+Q5Du4KUQyMLFI3A8YA4RKl0jjZzhihC1ZvDGrWi/JMn4vqbgr/Jg==}
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -5499,9 +5481,6 @@ packages:
peerDependencies:
zod: ^3.25.0 || ^4.0.0
zod@3.25.76:
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
@@ -5775,12 +5754,6 @@ snapshots:
enabled: 2.0.0
kuler: 2.0.0
'@endo/cache-map@1.1.0': {}
'@endo/env-options@1.1.11': {}
'@endo/immutable-arraybuffer@1.1.2': {}
'@esbuild/aix-ppc64@0.21.5':
optional: true
@@ -6297,14 +6270,6 @@ snapshots:
'@parcel/watcher-win32-x64': 2.5.6
optional: true
'@penpot/plugin-types@1.4.2': {}
'@penpot/plugins-runtime@1.4.2':
dependencies:
'@penpot/plugin-types': 1.4.2
ses: 1.14.0
zod: 3.25.76
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -10000,12 +9965,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
ses@1.14.0:
dependencies:
'@endo/cache-map': 1.1.0
'@endo/env-options': 1.1.11
'@endo/immutable-arraybuffer': 1.1.2
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -10974,6 +10933,4 @@ snapshots:
dependencies:
zod: 4.3.6
zod@3.25.76: {}
zod@4.3.6: {}

View File

@@ -18,7 +18,7 @@
<meta name="twitter:creator" content="@penpotapp">
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
<link id="theme" href="css/main.css?version={{& version_tag}}" rel="stylesheet" type="text/css" />
<link href="css/ui.css?ts={{& ts}}" rel="stylesheet" type="text/css" />
<link href="css/ui.css?ts={{& version_tag}}" rel="stylesheet" type="text/css" />
{{#isDebug}}
<link href="css/debug.css?version={{& version_tag}}" rel="stylesheet" type="text/css" />
{{/isDebug}}

View File

@@ -66,22 +66,22 @@
(update-in state [:workspace-local :open-plugins] (fnil disj #{}) id))))
(defn- load-plugin!
[{:keys [plugin-id name description host code icon permissions]}]
[{:keys [plugin-id name version description host code icon permissions]}]
(try
(st/emit! (save-current-plugin plugin-id)
(reset-plugin-flags plugin-id))
(.ɵloadPlugin
^js ug/global
#js {:pluginId plugin-id
:name name
:description description
:host host
:code code
:icon icon
:permissions (apply array permissions)}
(fn []
(st/emit! (remove-current-plugin plugin-id))))
(.ɵloadPlugin ^js ug/global
#js {:pluginId plugin-id
:name name
:description description
:version version
:host host
:code code
:icon icon
:permissions (apply array permissions)}
(fn []
(st/emit! (remove-current-plugin plugin-id))))
(catch :default e
(st/emit! (remove-current-plugin plugin-id))

View File

@@ -79,7 +79,7 @@
Structured tokens are non-primitive token types like `typography` or `box-shadow`."
[^js token-symbol]
(if (instance? js/Array (.-value token-symbol))
(mapv structured-token->penpot-map (.-value token-symbol))
(mapv tokenscript-symbols->penpot-unit (.-value token-symbol))
(let [entries (es6-iterator-seq (.entries (.-value token-symbol)))]
(into {} (map (fn [[k v :as V]]
[(keyword k) (tokenscript-symbols->penpot-unit v)])
@@ -88,7 +88,7 @@
(defn tokenscript-symbols->penpot-unit [^js v]
(cond
(structured-token? v) (structured-token->penpot-map v)
(list-symbol? v) (tokenscript-symbols->penpot-unit (.nth 1 v))
(list-symbol? v) (structured-token->penpot-map v)
(color-symbol? v) (.-value (.to v "hex"))
(rem-number-with-unit? v) (rem->px v)
:else (.-value v)))

View File

@@ -222,16 +222,9 @@
ptk/UpdateEvent
(update [_ state]
(let [pending-version-id (:workspace-pending-file-version-id state)
state (-> state
(assoc :thumbnails thumbnails)
(update :files assoc file-id file)
(dissoc :workspace-pending-file-version-id))]
(cond-> state
(some? pending-version-id)
(assoc :workspace-file-version-id pending-version-id)
(nil? pending-version-id)
(dissoc :workspace-file-version-id))))))
(-> state
(assoc :thumbnails thumbnails)
(update :files assoc file-id file)))))
(defn zoom-to-frame
[]
@@ -287,197 +280,192 @@
(wasm.api/process-object shape))))))
(defn initialize-workspace
([team-id file-id]
(initialize-workspace team-id file-id nil))
([team-id file-id version-id]
(assert (uuid? team-id) "expected valud uuid for `team-id`")
(assert (uuid? file-id) "expected valud uuid for `file-id`")
[team-id file-id]
(assert (uuid? team-id) "expected valud uuid for `team-id`")
(assert (uuid? file-id) "expected valud uuid for `file-id`")
(ptk/reify ::initialize-workspace
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc :recent-colors (:recent-colors storage/user))
(assoc :recent-fonts (:recent-fonts storage/user))
(assoc :current-file-id file-id)
(assoc :workspace-presence {})
;; Store pending version-id; bundle-fetched will set workspace-file-version-id
;; when the new bundle is applied so the viewport re-inits with new data
(assoc :workspace-pending-file-version-id version-id)))
(ptk/reify ::initialize-workspace
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc :recent-colors (:recent-colors storage/user))
(assoc :recent-fonts (:recent-fonts storage/user))
(assoc :current-file-id file-id)
(assoc :workspace-presence {})))
ptk/WatchEvent
(watch [_ state stream]
(let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream)
rparams (rt/get-params state)
features (features/get-enabled-features state team-id)
render-wasm? (contains? features "render-wasm/v1")]
ptk/WatchEvent
(watch [_ state stream]
(let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream)
rparams (rt/get-params state)
features (features/get-enabled-features state team-id)
render-wasm? (contains? features "render-wasm/v1")]
(log/debug :hint "initialize-workspace"
:team-id (dm/str team-id)
:file-id (dm/str file-id))
(log/debug :hint "initialize-workspace"
:team-id (dm/str team-id)
:file-id (dm/str file-id))
(->> (rx/merge
(rx/concat
;; Fetch all essential data that should be loaded before the file
(rx/merge
(if ^boolean render-wasm?
(->> (rx/from @wasm/module)
(rx/filter true?)
(rx/tap (fn [_]
(let [event (ug/event "penpot:wasm:loaded")]
(ug/dispatch! event))))
(rx/ignore))
(rx/empty))
(->> (rx/merge
(rx/concat
;; Fetch all essential data that should be loaded before the file
(rx/merge
(if ^boolean render-wasm?
(->> (rx/from @wasm/module)
(rx/filter true?)
(rx/tap (fn [_]
(let [event (ug/event "penpot:wasm:loaded")]
(ug/dispatch! event))))
(rx/ignore))
(rx/empty))
(->> stream
(rx/filter (ptk/type? ::df/fonts-loaded))
(rx/take 1)
(rx/ignore))
(->> stream
(rx/filter (ptk/type? ::df/fonts-loaded))
(rx/take 1)
(rx/ignore))
(rx/of (ntf/hide)
(dcmt/retrieve-comment-threads file-id)
(dcmt/fetch-profiles)
(df/fetch-fonts team-id)))
(rx/of (ntf/hide)
(dcmt/retrieve-comment-threads file-id)
(dcmt/fetch-profiles)
(df/fetch-fonts team-id)))
;; Once the essential data is fetched, lets proceed to
;; fetch teh file bunldle
(rx/of (fetch-bundle file-id features)))
;; Once the essential data is fetched, lets proceed to
;; fetch teh file bunldle
(rx/of (fetch-bundle file-id features)))
(->> stream
(rx/filter (ptk/type? ::bundle-fetched))
(rx/take 1)
(rx/map deref)
(rx/mapcat
(fn [{:keys [file]}]
(log/debug :hint "bundle fetched"
:team-id (dm/str team-id)
:file-id (dm/str file-id))
(->> stream
(rx/filter (ptk/type? ::bundle-fetched))
(rx/take 1)
(rx/map deref)
(rx/mapcat
(fn [{:keys [file]}]
(log/debug :hint "bundle fetched"
:team-id (dm/str team-id)
:file-id (dm/str file-id))
(rx/of (dpj/initialize-project (:project-id file))
(dwn/initialize team-id file-id)
(dwsl/initialize-shape-layout)
(fetch-libraries file-id features)
(-> (workspace-initialized file-id)
(with-meta {:team-id team-id
:file-id file-id}))))))
(rx/of (dpj/initialize-project (:project-id file))
(dwn/initialize team-id file-id)
(dwsl/initialize-shape-layout)
(fetch-libraries file-id features)
(-> (workspace-initialized file-id)
(with-meta {:team-id team-id
:file-id file-id}))))))
;; Install dev perf observers once the workspace is ready
(when (contains? cf/flags :perf-logs)
(->> stream
(rx/filter (ptk/type? ::workspace-initialized))
(rx/take 1)
(rx/tap (fn [_] (perf/setup)))))
;; Install dev perf observers once the workspace is ready
(when (contains? cf/flags :perf-logs)
(->> stream
(rx/filter (ptk/type? ::workspace-initialized))
(rx/take 1)
(rx/tap (fn [_] (perf/setup)))))
(->> stream
(rx/filter (ptk/type? ::dps/persistence-notification))
(rx/take 1)
(rx/map dwc/set-workspace-visited))
(->> stream
(rx/filter (ptk/type? ::dps/persistence-notification))
(rx/take 1)
(rx/map dwc/set-workspace-visited))
(when-let [component-id (some-> rparams :component-id uuid/parse)]
(->> stream
(rx/filter (ptk/type? ::workspace-initialized))
(rx/observe-on :async)
(rx/take 1)
(rx/map #(dwl/go-to-local-component :id component-id :update-layout? (:update-layout rparams)))))
(when-let [component-id (some-> rparams :component-id uuid/parse)]
(->> stream
(rx/filter (ptk/type? ::workspace-initialized))
(rx/observe-on :async)
(rx/take 1)
(rx/map #(dwl/go-to-local-component :id component-id :update-layout? (:update-layout rparams)))))
(when (:board-id rparams)
(->> stream
(rx/filter (ptk/type? ::dwv/initialize-viewport))
(rx/take 1)
(rx/map zoom-to-frame)))
(when (:board-id rparams)
(->> stream
(rx/filter (ptk/type? ::dwv/initialize-viewport))
(rx/take 1)
(rx/map zoom-to-frame)))
(when-let [comment-id (some-> rparams :comment-id uuid/parse)]
(->> stream
(rx/filter (ptk/type? ::workspace-initialized))
(rx/observe-on :async)
(rx/take 1)
(rx/map #(dwcm/navigate-to-comment-id comment-id))))
(when-let [comment-id (some-> rparams :comment-id uuid/parse)]
(->> stream
(rx/filter (ptk/type? ::workspace-initialized))
(rx/observe-on :async)
(rx/take 1)
(rx/map #(dwcm/navigate-to-comment-id comment-id))))
(when render-wasm?
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/mapcat
(fn [{:keys [redo-changes]}]
(let [added (->> redo-changes
(filter #(= (:type %) :add-obj))
(map :id))]
(->> (rx/from added)
(rx/map process-wasm-object)))))))
(when render-wasm?
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/mapcat
(fn [{:keys [redo-changes]}]
(let [added (->> redo-changes
(filter #(= (:type %) :add-obj))
(map :id))]
(->> (rx/from added)
(rx/map process-wasm-object)))))))
(when render-wasm?
(let [local-commits-s
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/filter #(and (= :local (:source %))
(not (contains? (:tags %) :position-data))))
(rx/filter (complement empty?)))
(when render-wasm?
(let [local-commits-s
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/filter #(and (= :local (:source %))
(not (contains? (:tags %) :position-data))))
(rx/filter (complement empty?)))
notifier-s
(rx/merge
(->> local-commits-s (rx/debounce 1000))
(->> stream (rx/filter dps/force-persist?)))
notifier-s
(rx/merge
(->> local-commits-s (rx/debounce 1000))
(->> stream (rx/filter dps/force-persist?)))
objects-s
(rx/from-atom refs/workspace-page-objects {:emit-current-value? true})
objects-s
(rx/from-atom refs/workspace-page-objects {:emit-current-value? true})
current-page-id-s
(rx/from-atom refs/current-page-id {:emit-current-value? true})]
current-page-id-s
(rx/from-atom refs/current-page-id {:emit-current-value? true})]
(->> local-commits-s
(rx/buffer-until notifier-s)
(rx/with-latest-from objects-s)
(rx/map
(fn [[commits objects]]
(->> commits
(mapcat :redo-changes)
(filter #(contains? #{:mod-obj :add-obj} (:type %)))
(filter #(cfh/text-shape? objects (:id %)))
(map #(vector
(:id %)
(wasm.api/calculate-position-data (get objects (:id %))))))))
(->> local-commits-s
(rx/buffer-until notifier-s)
(rx/with-latest-from objects-s)
(rx/map
(fn [[commits objects]]
(->> commits
(mapcat :redo-changes)
(filter #(contains? #{:mod-obj :add-obj} (:type %)))
(filter #(cfh/text-shape? objects (:id %)))
(map #(vector
(:id %)
(wasm.api/calculate-position-data (get objects (:id %))))))))
(rx/with-latest-from current-page-id-s)
(rx/map
(fn [[text-position-data page-id]]
(let [changes
(->> text-position-data
(mapv (fn [[id position-data]]
{:type :mod-obj
:id id
:page-id page-id
:operations
[{:type :set
:attr :position-data
:val position-data
:ignore-touched true
:ignore-geometry true}]})))]
(when (d/not-empty? changes)
(dch/commit-changes
{:redo-changes changes :undo-changes []
:save-undo? false
:tags #{:position-data}})))))
(rx/take-until stoper-s))))
(rx/with-latest-from current-page-id-s)
(rx/map
(fn [[text-position-data page-id]]
(let [changes
(->> text-position-data
(mapv (fn [[id position-data]]
{:type :mod-obj
:id id
:page-id page-id
:operations
[{:type :set
:attr :position-data
:val position-data
:ignore-touched true
:ignore-geometry true}]})))]
(when (d/not-empty? changes)
(dch/commit-changes
{:redo-changes changes :undo-changes []
:save-undo? false
:tags #{:position-data}})))))
(rx/take-until stoper-s))))
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/mapcat
(fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo?]}]
(if (and save-undo? (seq undo-changes))
(let [entry {:undo-changes undo-changes
:redo-changes redo-changes
:undo-group undo-group
:tags tags}]
(rx/of (dwu/append-undo entry stack-undo?)))
(rx/empty))))))
(rx/take-until stoper-s))))
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/mapcat
(fn [{:keys [save-undo? undo-changes redo-changes undo-group tags stack-undo?]}]
(if (and save-undo? (seq undo-changes))
(let [entry {:undo-changes undo-changes
:redo-changes redo-changes
:undo-group undo-group
:tags tags}]
(rx/of (dwu/append-undo entry stack-undo?)))
(rx/empty))))))
(rx/take-until stoper-s))))
ptk/EffectEvent
(effect [_ _ _]
(let [name (dm/str "workspace-" file-id)]
(unchecked-set ug/global "name" name))))))
ptk/EffectEvent
(effect [_ _ _]
(let [name (dm/str "workspace-" file-id)]
(unchecked-set ug/global "name" name)))))
(defn finalize-workspace
[_team-id file-id]

View File

@@ -197,12 +197,11 @@
objects (:objects page)
undo-id (or (:undo-id options) (js/Symbol))
[all-parents changes]
(-> (pcb/empty-changes it (:id page))
(cls/generate-delete-shapes fdata page objects ids
{:ignore-touched (:allow-altering-copies options)
:undo-group (:undo-group options)
:undo-id undo-id}))]
[all-parents changes] (-> (pcb/empty-changes it (:id page))
(cls/generate-delete-shapes fdata page objects ids
{:ignore-touched (:allow-altering-copies options)
:undo-group (:undo-group options)
:undo-id undo-id}))]
(rx/of (dwu/start-undo-transaction undo-id)
(dc/detach-comment-thread ids)

View File

@@ -10,7 +10,6 @@
[app.common.attrs :as attrs]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
@@ -20,7 +19,6 @@
[app.common.types.shape.layout :as ctl]
[app.common.types.text :as txt]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.event :as ev]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.common :as dwc]
@@ -918,11 +916,11 @@
(update-in state [:workspace-text-modifier shape-id] {:position-data position-data}))))
(defn v2-update-text-shape-content
[id content & {:keys [update-name? name finalize? save-undo? original-content]
:or {update-name? false name nil finalize? false save-undo? true original-content nil}}]
[id content & {:keys [update-name? name finalize? save-undo?]
:or {update-name? false name nil finalize? false save-undo? true}}]
(ptk/reify ::v2-update-text-shape-content
ptk/WatchEvent
(watch [it state _]
(watch [_ state _]
(if (features/active-feature? state "render-wasm/v1")
(let [objects (dsh/lookup-page-objects state)
shape (get objects id)
@@ -952,11 +950,11 @@
new-shape))
{:save-undo? save-undo? :undo-group (when new-shape? id)})
(when-let [modifiers (dwwt/resize-wasm-text-modifiers shape content)]
(let [options {:undo-group (when new-shape? id)}]
(if (and (not= :fixed (:grow-type shape)) finalize?)
(dwm/apply-wasm-modifiers modifiers options)
(dwm/set-wasm-modifiers modifiers options)))))
(let [modifiers (dwwt/resize-wasm-text-modifiers shape content)
options {:undo-group (when new-shape? id)}]
(if (and (not= :fixed (:grow-type shape)) finalize?)
(dwm/apply-wasm-modifiers modifiers options)
(dwm/set-wasm-modifiers modifiers options))))
(when finalize?
(rx/concat
@@ -972,13 +970,7 @@
{:save-undo? false}))
(dws/deselect-shape id)
(dwsh/delete-shapes #{id})))
(rx/of
;; This commit is necesary for undo and component propagation
;; on finalization
(dch/commit-changes
(-> (pcb/empty-changes it (:current-page-id state))
(pcb/set-text-content id content original-content)))
(dwt/finish-transform))))))
(rx/of (dwt/finish-transform))))))
(let [objects (dsh/lookup-page-objects state)
shape (get objects id)

View File

@@ -108,7 +108,7 @@
(rx/take 1)
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
(rx/tap #(th/clear-queue!))
(rx/map #(dw/initialize-workspace team-id file-id id)))
(rx/map #(dw/initialize-workspace team-id file-id)))
(case origin
:version
(rx/of (ptk/event ::ev/event {::ev/name "restore-pin-version"}))
@@ -231,7 +231,7 @@
(rx/filter #(or (nil? %) (= :saved %)))
(rx/take 1)
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
(rx/map #(dw/initialize-workspace team-id file-id id)))
(rx/map #(dw/initialize-workspace team-id file-id)))
(->> (rx/of 1)
(rx/tap resolve)

View File

@@ -27,28 +27,27 @@
(resize-wasm-text-modifiers shape (:content shape)))
([{:keys [id points selrect grow-type] :as shape} content]
(when id
(wasm.api/use-shape id)
(wasm.api/set-shape-text-content id content)
(wasm.api/set-shape-text-images id content)
(wasm.api/use-shape id)
(wasm.api/set-shape-text-content id content)
(wasm.api/set-shape-text-images id content)
(let [dimension (wasm.api/get-text-dimensions)
width-scale (if (#{:fixed :auto-height} grow-type)
1.0
(/ (:width dimension) (:width selrect)))
height-scale (if (= :fixed grow-type)
1.0
(/ (:height dimension) (:height selrect)))
resize-v (gpt/point width-scale height-scale)
origin (first points)]
(let [dimension (wasm.api/get-text-dimensions)
width-scale (if (#{:fixed :auto-height} grow-type)
1.0
(/ (:width dimension) (:width selrect)))
height-scale (if (= :fixed grow-type)
1.0
(/ (:height dimension) (:height selrect)))
resize-v (gpt/point width-scale height-scale)
origin (first points)]
{id
{:modifiers
(ctm/resize-modifiers
resize-v
origin
(:transform shape (gmt/matrix))
(:transform-inverse shape (gmt/matrix)))}}))))
{id
{:modifiers
(ctm/resize-modifiers
resize-v
origin
(:transform shape (gmt/matrix))
(:transform-inverse shape (gmt/matrix)))}})))
(defn resize-wasm-text
"Resize a single text shape (auto-width/auto-height) by id.

View File

@@ -256,9 +256,6 @@
(def workspace-layout
(l/derived :workspace-layout st/state))
(def workspace-file-version-id
(l/derived :workspace-file-version-id st/state))
(def snap-pixel?
(l/derived #(contains? % :snap-pixel-grid) workspace-layout))

View File

@@ -13,7 +13,7 @@
[rumext.v2 :as mf]))
(mf/defc search-bar*
[{:keys [id class value placeholder icon-id auto-focus on-change on-clear children]}]
[{:keys [id class value placeholder icon-id auto-focus on-change on-clear on-submit children]}]
(let [handle-change
(mf/use-fn
(mf/deps on-change)
@@ -31,12 +31,19 @@
handle-key-down
(mf/use-fn
(mf/deps on-submit)
(fn [event]
(let [enter? (kbd/enter? event)
esc? (kbd/esc? event)
node (dom/get-target event)]
(when ^boolean enter? (dom/blur! node))
(when ^boolean enter?
(dom/blur! node)
(when (fn? on-submit)
(let [value (dom/get-target-val event)]
(on-submit value event))))
(when ^boolean esc? (dom/blur! node)))))]
[:span {:class (stl/css-case :search-box true
:has-children (some? children))}
children

View File

@@ -50,7 +50,7 @@
(mf/defc workspace-content*
{::mf/private true}
[{:keys [file layout page wglobal file-version-id]}]
[{:keys [file layout page wglobal]}]
(let [palete-size (mf/use-state nil)
selected (mf/deref refs/selected-shapes)
@@ -109,7 +109,6 @@
:wglobal wglobal
:selected selected
:layout layout
:file-version-id file-version-id
:palete-size
(when (and (or colorpalette? textpalette?) (not hide-ui?))
@palete-size)}]]]
@@ -169,7 +168,7 @@
(mf/defc workspace-inner*
{::mf/private true}
[{:keys [page-id file-id file layout wglobal file-version-id]}]
[{:keys [page-id file-id file layout wglobal]}]
(let [page-ref (mf/with-memo [file-id page-id]
(make-page-ref file-id page-id))
page (mf/deref page-ref)]
@@ -188,8 +187,7 @@
[:> workspace-content* {:file file
:page page
:wglobal wglobal
:layout layout
:file-version-id file-version-id}]
:layout layout}]
[:> workspace-loader*])))
(mf/defc workspace*
@@ -201,7 +199,6 @@
layout (mf/deref refs/workspace-layout)
wglobal (mf/deref refs/workspace-global)
file-version-id (mf/deref refs/workspace-file-version-id)
team-ref (mf/with-memo [team-id]
(make-team-ref team-id))
@@ -277,8 +274,7 @@
:file-id file-id
:file file
:wglobal wglobal
:layout layout
:file-version-id file-version-id}])
:layout layout}])
(when (or (not (and file-loaded? page-id))
;; in wasm renderer, extend the pixel loader until the first frame is rendered
;; but do not apply it when switching pages

View File

@@ -9,6 +9,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.uri :as u]
[app.config :as cfg]
[app.main.data.event :as ev]
[app.main.data.modal :as modal]
@@ -24,6 +25,7 @@
[app.plugins.register :as preg]
[app.util.avatars :as avatars]
[app.util.dom :as dom]
[app.util.globals :as global]
[app.util.i18n :as i18n :refer [tr]]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
@@ -33,7 +35,19 @@
(def ^:private close-icon
(deprecated-icon/icon-xref :close (stl/css :close-icon)))
(defn icon-url
(defn- normalize-plugin-url
"Automatically appens manifest.json if the plugin-uri comes without it."
[plugin-url]
(if (str/ends-with? plugin-url "manifest.json")
plugin-url
(-> (u/uri plugin-url)
(update :path (fn [path]
(if (str/ends-with? path "/")
(str path "manifest.json")
(str path "/manifest.json"))))
(str))))
(defn- icon-url
"Creates an sanitizes de icon URL to display"
[host icon]
(dm/str host
@@ -94,48 +108,49 @@
::mf/register-as :plugin-management}
[]
(let [plugins-state* (mf/use-state #(preg/plugins-list))
plugins-state (deref plugins-state*)
(let [plugins-state* (mf/use-state #(preg/plugins-list))
plugins-state (deref plugins-state*)
plugin-url* (mf/use-state "")
plugin-url (deref plugin-url*)
plugin-url* (mf/use-state "")
plugin-url (deref plugin-url*)
fetching-manifest? (mf/use-state false)
input-status* (mf/use-state nil) ;; :error-url :error-manifest :success
input-status (deref input-status*)
input-status* (mf/use-state nil) ;; :error-url :error-manifest :success
input-status @input-status*
error-url? (= :error-url input-status)
error-url? (= :error-url input-status)
error-manifest? (= :error-manifest input-status)
error? (or error-url? error-manifest?)
error? (or error-url? error-manifest?)
user-can-edit? (:can-edit (deref refs/permissions))
permissions (mf/deref refs/permissions)
user-can-edit? (get permissions :can-edit)
handle-url-input
fetching-manifest?
(mf/use-state false)
on-url-change
(mf/use-fn
(fn [value]
(reset! input-status* nil)
(reset! plugin-url* value)))
handle-install-click
on-install
(mf/use-fn
(mf/deps plugins-state plugin-url)
(fn []
(reset! fetching-manifest? true)
(->> (dp/fetch-manifest plugin-url)
(->> (dp/fetch-manifest (normalize-plugin-url plugin-url))
(rx/subs!
(fn [plugin]
(reset! fetching-manifest? false)
(if plugin
(do
(st/emit! (ptk/event ::ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url}))
(modal/show!
:plugin-permissions
{:plugin plugin
:on-accept
#(do
(preg/install-plugin! plugin)
(modal/show! :plugin-management {}))})
(modal/show! :plugin-permissions
{:plugin plugin
:on-accept
#(do
(preg/install-plugin! plugin)
(modal/show! :plugin-management {}))})
(reset! input-status* :success)
(reset! plugin-url* ""))
;; Cannot get the manifest
@@ -145,7 +160,7 @@
(reset! fetching-manifest? false)
(reset! input-status* :error-url))))))
handle-open-plugin
on-open-plugin
(mf/use-fn
(fn [manifest]
(st/emit! (ptk/event ::ev/event {::ev/name "start-plugin"
@@ -155,7 +170,7 @@
(dp/open-plugin! manifest user-can-edit?)
(modal/hide!)))
handle-remove-plugin
on-remove-plugin
(mf/use-fn
(mf/deps plugins-state)
(fn [plugin-index]
@@ -175,14 +190,16 @@
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :top-bar)}
[:> search-bar* {:on-change handle-url-input
[:> search-bar* {:on-change on-url-change
:on-submit on-install
:value plugin-url
:placeholder (tr "workspace.plugins.search-placeholder")
:class (stl/css-case :input-error error?)}]
[:button {:class (stl/css :primary-button)
:disabled @fetching-manifest?
:on-click handle-install-click} (tr "workspace.plugins.install")]]
:on-click on-install}
(tr "workspace.plugins.install")]]
(when error-url?
[:div {:class (stl/css-case :info true :error error?)}
@@ -220,10 +237,11 @@
:index idx
:manifest manifest
:user-can-edit user-can-edit?
:on-open-plugin handle-open-plugin
:on-remove-plugin handle-remove-plugin}])]])]]]))
:on-open-plugin on-open-plugin
:on-remove-plugin on-remove-plugin}])]])]]]))
(mf/defc plugins-permission-list
(mf/defc plugins-permission-list*
{::mf/private true}
[{:keys [permissions]}]
[:div {:class (stl/css :permissions-list)}
(cond
@@ -291,36 +309,45 @@
::mf/register-as :plugin-permissions}
[{:keys [plugin on-accept on-close]}]
(let [{:keys [host permissions]} plugin
permissions (set permissions)
(let [host
(:host plugin)
handle-accept-dialog
permissions
(-> plugin :permissions set)
on-accept-dialog
(mf/use-fn
(mf/deps on-accept)
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "allow-plugin-permissions"
:host host
:permissions (->> permissions (str/join ", "))})
(st/emit! (ev/event {::ev/name "allow-plugin-permissions"
:host host
:permissions (str/join ", " permissions)})
(modal/hide))
(when on-accept (on-accept))))
handle-close-dialog
on-close-dialog
(mf/use-fn
(mf/deps on-close)
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "reject-plugin-permissions"
:host host
:permissions (->> permissions (str/join ", "))})
(st/emit! (ev/event {::ev/name "reject-plugin-permissions"
:host host
:permissions (str/join ", " permissions)})
(modal/hide))
(when on-close (on-close))))]
(mf/with-effect [on-accept-dialog]
(.addEventListener ^js global/document "keydown" on-accept-dialog)
#(.removeEventListener ^js global/document "keydown" on-accept-dialog))
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog :plugin-permissions)}
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} close-icon]
[:button {:class (stl/css :close-btn) :on-click on-close-dialog} close-icon]
[:div {:class (stl/css :modal-title)} (tr "workspace.plugins.permissions.title" (str/upper (:name plugin)))]
[:div {:class (stl/css :modal-content)}
[:& plugins-permission-list {:permissions permissions}]
[:> plugins-permission-list* {:permissions permissions}]
(when-not (contains? cfg/plugins-whitelist host)
[:div {:class (stl/css :permissions-disclaimer)}
@@ -332,13 +359,13 @@
{:class (stl/css :cancel-button :button-expand)
:type "button"
:value (tr "ds.confirm-cancel")
:on-click handle-close-dialog}]
:on-click on-close-dialog}]
[:input
{:class (stl/css :primary-button :button-expand)
:type "button"
:value (tr "ds.confirm-allow")
:on-click handle-accept-dialog}]]]]]))
:on-click on-accept-dialog}]]]]]))
(mf/defc plugins-permissions-updated-dialog
@@ -346,11 +373,15 @@
::mf/register-as :plugin-permissions-update}
[{:keys [plugin on-accept on-close]}]
(let [{:keys [host permissions]} plugin
permissions (set permissions)
(let [host
(:host plugin)
handle-accept-dialog
permissions
(-> plugin :permissions set)
on-accept-dialog
(mf/use-fn
(mf/deps on-accept)
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "allow-plugin-permissions"
@@ -359,8 +390,9 @@
(modal/hide))
(when on-accept (on-accept))))
handle-close-dialog
on-close-dialog
(mf/use-fn
(mf/deps on-close)
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "reject-plugin-permissions"
@@ -369,16 +401,20 @@
(modal/hide))
(when on-close (on-close))))]
(mf/with-effect [on-accept-dialog]
(.addEventListener ^js global/document "keydown" on-accept-dialog)
#(.removeEventListener ^js global/document "keydown" on-accept-dialog))
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog :plugin-permissions)}
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} close-icon]
[:button {:class (stl/css :close-btn) :on-click on-close-dialog} close-icon]
[:div {:class (stl/css :modal-title)}
(tr "workspace.plugins.permissions-update.title" (str/upper (:name plugin)))]
[:div {:class (stl/css :modal-content)}
[:div {:class (stl/css :modal-paragraph)}
(tr "workspace.plugins.permissions-update.warning")]
[:& plugins-permission-list {:permissions permissions}]]
[:> plugins-permission-list* {:permissions permissions}]]
[:div {:class (stl/css :modal-footer)}
[:div {:class (stl/css :action-buttons)}
@@ -386,13 +422,13 @@
{:class (stl/css :cancel-button :button-expand)
:type "button"
:value (tr "ds.confirm-cancel")
:on-click handle-close-dialog}]
:on-click on-close-dialog}]
[:input
{:class (stl/css :primary-button :button-expand)
:type "button"
:value (tr "ds.confirm-allow")
:on-click handle-accept-dialog}]]]]]))
:on-click on-accept-dialog}]]]]]))
(mf/defc plugins-try-out-dialog
@@ -402,25 +438,31 @@
(let [{:keys [icon host name]} plugin
handle-accept-dialog
on-accept-dialog
(mf/use-fn
(mf/deps on-accept)
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "try-out-accept"})
(modal/hide))
(when on-accept (on-accept))))
handle-close-dialog
on-close-dialog
(mf/use-fn
(mf/deps on-close)
(fn [event]
(dom/prevent-default event)
(st/emit! (ptk/event ::ev/event {::ev/name "try-out-cancel"})
(modal/hide))
(when on-close (on-close))))]
(mf/with-effect [on-accept-dialog]
(.addEventListener ^js global/document "keydown" on-accept-dialog)
#(.removeEventListener ^js global/document "keydown" on-accept-dialog))
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog :plugin-try-out)}
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} close-icon]
[:button {:class (stl/css :close-btn) :on-click on-close-dialog} close-icon]
[:div {:class (stl/css :modal-title)}
[:div {:class (stl/css :plugin-icon)}
[:img {:src (if (some? icon)
@@ -438,10 +480,10 @@
{:class (stl/css :cancel-button :button-expand)
:type "button"
:value (tr "workspace.plugins.try-out.cancel")
:on-click handle-close-dialog}]
:on-click on-close-dialog}]
[:input
{:class (stl/css :primary-button :button-expand)
:type "button"
:value (tr "workspace.plugins.try-out.try")
:on-click handle-accept-dialog}]]]]]))
:on-click on-accept-dialog}]]]]]))

View File

@@ -118,8 +118,7 @@
:update-name? update-name?
:name generated-name
:finalize? true
:save-undo? false
:original-content original-content))))
:save-undo? false))))
(let [container-node (mf/ref-val container-ref)]
(dom/set-style! container-node "opacity" 0)))

View File

@@ -84,8 +84,6 @@
:on-click on-select-shape
:on-context-menu on-context-menu
:data-testid "layer-row"
:role "checkbox"
:aria-checked selected?
:class (stl/css-case
:layer-row true
:highlight highlighted?

View File

@@ -540,7 +540,7 @@
[:values schema:layout-item-props-schema]
[:applied-tokens [:maybe [:map-of :keyword :string]]]
[:ids [::sm/vec ::sm/uuid]]
[:v-sizing {:optional true} [:maybe [:enum :fill :fix :auto]]]])
[:v-sizing {:optional true} [:maybe [:= :fill]]]])
(mf/defc layout-size-constraints*
{::mf/private true

View File

@@ -111,11 +111,6 @@
:modifier modifier
:zoom zoom}]))))
(defn- show-outline?
[shape]
(and (not (:hidden shape))
(not (:blocked shape))))
(mf/defc shape-outlines
{::mf/wrap-props false}
[props]
@@ -133,8 +128,7 @@
shapes (-> #{}
(into (comp (remove edition?)
(keep lookup)
(filter show-outline?))
(keep lookup))
(set/union selected hover))
(into (comp (remove edition?)
(keep lookup))

View File

@@ -73,7 +73,7 @@
objects)))
(mf/defc viewport*
[{:keys [selected wglobal wlocal layout file page palete-size file-version-id]}]
[{:keys [selected wglobal wlocal layout file page palete-size]}]
(let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check
;; that the new parameter is sent
{:keys [edit-path
@@ -141,7 +141,6 @@
canvas-ref (mf/use-ref nil)
text-editor-ref (mf/use-ref nil)
last-file-version-id-ref (mf/use-ref nil)
;; STATE REFS
disable-paste-ref (mf/use-ref false)
@@ -224,9 +223,8 @@
show-gradient-handlers? (= (count selected) 1)
show-grids? (contains? layout :display-guides)
show-frame-outline? (and (= transform :move) (not panning))
show-frame-outline? (= transform :move)
show-outlines? (and (nil? transform)
(not panning)
(not edition)
(not drawing-obj)
(not (#{:comments :path :curve} drawing-tool)))
@@ -346,18 +344,11 @@
(when (and @canvas-init? preview-blend)
(wasm.api/request-render "with-effect")))
(mf/with-effect [@canvas-init? file-version-id zoom vbox background]
(when @canvas-init?
(if (not @initialized?)
(do
(wasm.api/clear-canvas-pixels)
(wasm.api/initialize-viewport base-objects zoom vbox background)
(reset! initialized? true)
(mf/set-ref-val! last-file-version-id-ref file-version-id))
(when (and (some? file-version-id)
(not= file-version-id (mf/ref-val last-file-version-id-ref)))
(wasm.api/initialize-viewport base-objects zoom vbox background)
(mf/set-ref-val! last-file-version-id-ref file-version-id)))))
(mf/with-effect [@canvas-init? zoom vbox background]
(when (and @canvas-init? (not @initialized?))
(wasm.api/clear-canvas-pixels)
(wasm.api/initialize-viewport base-objects zoom vbox background)
(reset! initialized? true)))
(mf/with-effect [focus]
(when (and @canvas-init? @initialized?)
@@ -562,7 +553,7 @@
:shift? @shift?}])
[:> widgets/frame-titles*
{:objects objects-modified
{:objects (with-meta objects-modified nil)
:selected selected
:zoom zoom
:is-show-artboard-names show-artboard-names?

View File

@@ -27,6 +27,7 @@
[app.main.data.workspace.media :as dwm]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.wasm-text :as dwwt]
[app.main.features :as features]
[app.main.fonts :refer [fetch-font-css]]
[app.main.router :as rt]
[app.main.store :as st]
@@ -365,8 +366,10 @@
(cb/add-object shape))]
(st/emit! (ch/commit-changes changes)
(se/event plugin-id "create-shape" :type :text)
(dwwt/resize-wasm-text-debounce (:id shape)))
(se/event plugin-id "create-shape" :type :text))
(when (features/active-feature? @st/state "render-wasm/v1")
(st/emit! (dwwt/resize-wasm-text-debounce (:id shape))))
(shape/shape-proxy plugin-id (:id shape)))))

View File

@@ -78,6 +78,7 @@
(d/without-nils
{:plugin-id plugin-id
:url (str plugin-url)
:version vers
:name name
:description desc
:host origin

View File

@@ -1305,7 +1305,8 @@
tokens)))}
:applyToken
{:schema [:tuple
{:enumerable false
:schema [:tuple
[:fn token-proxy?]
[:maybe [:set [:and ::sm/keyword [:fn cto/token-attr?]]]]]
:fn (fn [token attrs]

View File

@@ -144,14 +144,16 @@
(st/emit! (dwtl/delete-token set-id id)))
:applyToShapes
{:schema [:tuple
{:enumerable false
:schema [:tuple
[:vector [:fn shape-proxy?]]
[:maybe [:set [:and ::sm/keyword [:fn cto/token-attr?]]]]]
:fn (fn [shapes attrs]
(apply-token-to-shapes file-id set-id id (map #(obj/get % "$id") shapes) attrs))}
:applyToSelected
{:schema [:tuple [:maybe [:set [:and ::sm/keyword [:fn cto/token-attr?]]]]]
{:enumerable false
:schema [:tuple [:maybe [:set [:and ::sm/keyword [:fn cto/token-attr?]]]]]
:fn (fn [attrs]
(let [selected (get-in @st/state [:workspace-local :selected])]
(apply-token-to-shapes file-id set-id id selected attrs)))}))
@@ -236,14 +238,16 @@
(apply array))))}
:getTokenById
{:schema [:tuple ::sm/uuid]
{:enumerable false
:schema [:tuple ::sm/uuid]
:fn (fn [token-id]
(let [token (u/locate-token file-id id token-id)]
(when (some? token)
(token-proxy plugin-id file-id id token-id))))}
:addToken
{:schema (fn [args]
{:enumerable false
:schema (fn [args]
[:tuple (-> (cfo/make-token-schema
(-> (u/locate-tokens-lib file-id) (ctob/get-tokens id))
(cto/dtcg-token-type->token-type (-> args (first) (get "type"))))
@@ -353,13 +357,15 @@
{:this true :get (fn [_])}
:addSet
{:schema [:tuple [:fn token-set-proxy?]]
{:enumerable false
:schema [:tuple [:fn token-set-proxy?]]
:fn (fn [token-set]
(let [theme (u/locate-token-theme file-id id)]
(st/emit! (dwtl/update-token-theme id (ctob/enable-set theme (obj/get token-set :name))))))}
:removeSet
{:schema [:tuple [:fn token-set-proxy?]]
{:enumerable false
:schema [:tuple [:fn token-set-proxy?]]
:fn (fn [token-set]
(let [theme (u/locate-token-theme file-id id)]
(st/emit! (dwtl/update-token-theme id (ctob/disable-set theme (obj/get token-set :name))))))}
@@ -406,7 +412,8 @@
(apply array (map #(token-set-proxy plugin-id file-id (ctob/get-id %)) sets))))}
:addTheme
{:schema (fn [attrs]
{:enumerable false
:schema (fn [attrs]
[:tuple (-> (sm/schema (cfo/make-token-theme-schema
(u/locate-tokens-lib file-id)
(or (obj/get attrs "group") "")
@@ -419,7 +426,8 @@
(token-theme-proxy plugin-id file-id (:id theme))))}
:addSet
{:schema [:tuple (-> (sm/schema (cfo/make-token-set-schema
{:enumerable false
:schema [:tuple (-> (sm/schema (cfo/make-token-set-schema
(u/locate-tokens-lib file-id)
nil))
(sm/dissoc-key :id))] ;; We don't allow plugins to set the id
@@ -431,14 +439,16 @@
(token-set-proxy plugin-id file-id (ctob/get-id set))))}
:getThemeById
{:schema [:tuple ::sm/uuid]
{:enumerable false
:schema [:tuple ::sm/uuid]
:fn (fn [theme-id]
(let [theme (u/locate-token-theme file-id theme-id)]
(when (some? theme)
(token-theme-proxy plugin-id file-id theme-id))))}
:getSetById
{:schema [:tuple ::sm/uuid]
{:enumerable false
:schema [:tuple ::sm/uuid]
:fn (fn [set-id]
(let [set (u/locate-token-set file-id set-id)]
(when (some? set)

View File

@@ -642,15 +642,13 @@ export class SelectionController extends EventTarget {
} else {
this.#anchorNode = anchorNode;
this.#anchorOffset = anchorOffset;
this.#focusNode = focusNode;
this.#focusOffset = focusOffset;
// setPosition() collapses the selection to a single caret. We must only use it
// when anchorOffset === focusOffset. When both points are in the same node but
// offsets differ (e.g. selecting "hola" in "hola adios"), we need setBaseAndExtent()
// to preserve the range so we don't incorrectly collapse ranges and lose the selection.
if (anchorNode === focusNode && anchorOffset === focusOffset) {
if (anchorNode === focusNode) {
this.#focusNode = this.#anchorNode;
this.#focusOffset = this.#anchorOffset;
this.#selection.setPosition(anchorNode, anchorOffset);
} else {
this.#focusNode = focusNode;
this.#focusOffset = focusOffset;
this.#selection.setBaseAndExtent(
anchorNode,
anchorOffset,

View File

@@ -12,7 +12,10 @@
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/apps/contrast-plugin",
"outputPath": {
"base": "dist/apps/contrast-plugin",
"browser": "",
},
"index": "apps/contrast-plugin/src/index.html",
"browser": "apps/contrast-plugin/src/main.ts",
"polyfills": ["zone.js"],
@@ -20,6 +23,7 @@
"assets": [
"apps/contrast-plugin/src/_headers",
"apps/contrast-plugin/src/favicon.ico",
"apps/contrast-plugin/src/manifest.json",
"apps/contrast-plugin/src/assets"
],
"styles": [
@@ -218,7 +222,10 @@
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/apps/table-plugin",
"outputPath": {
"base": "dist/apps/table-plugin",
"browser": ""
},
"index": "apps/table-plugin/src/index.html",
"browser": "apps/table-plugin/src/main.ts",
"polyfills": ["zone.js"],
@@ -226,6 +233,7 @@
"assets": [
"apps/table-plugin/src/_headers",
"apps/table-plugin/src/favicon.ico",
"apps/table-plugin/src/manifest.json",
"apps/table-plugin/src/assets"
],
"styles": [
@@ -356,7 +364,10 @@
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/apps/colors-to-tokens-plugin",
"outputPath": {
"base": "dist/apps/colors-to-tokens-plugin",
"browser": ""
},
"index": "apps/colors-to-tokens-plugin/src/index.html",
"browser": "apps/colors-to-tokens-plugin/src/main.ts",
"polyfills": ["zone.js"],
@@ -364,6 +375,7 @@
"assets": [
"apps/colors-to-tokens-plugin/src/_headers",
"apps/colors-to-tokens-plugin/src/favicon.ico",
"apps/colors-to-tokens-plugin/src/manifest.json",
"apps/colors-to-tokens-plugin/src/assets"
],
"styles": [

View File

@@ -7,9 +7,9 @@
"build": "ng build colors-to-tokens-plugin && pnpm run build:plugin",
"build:dev": "ng build colors-to-tokens-plugin --configuration development",
"build:plugin": "node ../../tools/scripts/build-plugin.mjs --plugin=colors-to-tokens-plugin",
"build:plugin:watch": "node ../../tools/scripts/build-plugin.mjs --plugin=colors-to-tokens-plugin --watch",
"watch": "node ../../tools/scripts/build-plugin.mjs --plugin=colors-to-tokens-plugin --watch",
"serve": "ng serve colors-to-tokens-plugin",
"init": "concurrently --kill-others --names plugin,serve \"pnpm run build:plugin:watch\" \"pnpm run serve\"",
"init": "concurrently --kill-others --names plugin,serve \"pnpm run watch\" \"pnpm run serve\"",
"lint": "eslint .",
"test": "vitest"
}

View File

@@ -1,6 +1,8 @@
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideRouter, withHashLocation } from '@angular/router';
export const appConfig: ApplicationConfig = {
providers: [provideRouter([])],
};
providers: [
provideRouter([], withHashLocation())
],
};

View File

@@ -3,7 +3,6 @@
<head>
<meta charset="utf-8" />
<title>colors-to-tokens-plugin</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>

View File

@@ -1,7 +1,8 @@
{
"name": "Colors to Tokens",
"description": "Generate a design tokens file from a list of colors",
"code": "/assets/plugin.js",
"icon": "/assets/icon.png",
"version": 2,
"code": "assets/plugin.js",
"icon": "assets/icon.png",
"permissions": ["content:read", "library:read", "allow:downloads"]
}

View File

@@ -7,9 +7,9 @@
"build": "ng build contrast-plugin && pnpm run build:plugin",
"build:dev": "ng build contrast-plugin --configuration development",
"build:plugin": "node ../../tools/scripts/build-plugin.mjs --plugin=contrast-plugin",
"build:plugin:watch": "node ../../tools/scripts/build-plugin.mjs --plugin=contrast-plugin --watch",
"watch": "node ../../tools/scripts/build-plugin.mjs --plugin=contrast-plugin --watch",
"serve": "ng serve contrast-plugin",
"init": "concurrently --kill-others --names plugin,serve \"pnpm run build:plugin:watch\" \"pnpm run serve\"",
"init": "concurrently --kill-others --names plugin,serve \"pnpm run watch\" \"pnpm run serve\"",
"lint": "eslint .",
"test": "vitest"
}

View File

@@ -1,6 +1,6 @@
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideRouter, withHashLocation } from '@angular/router';
export const appConfig: ApplicationConfig = {
providers: [provideRouter([])],
providers: [provideRouter([], withHashLocation())],
};

View File

@@ -3,7 +3,6 @@
<head>
<meta charset="utf-8" />
<title>contrast-plugin</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>

View File

@@ -1,7 +1,8 @@
{
"name": "Contrast",
"description": "Measure contrast plugin",
"code": "/assets/plugin.js",
"icon": "/assets/icon.png",
"version": 2,
"code": "assets/plugin.js",
"icon": "assets/icon.png",
"permissions": ["content:read"]
}

View File

@@ -7,9 +7,9 @@
"build": "ng build table-plugin && pnpm run build:plugin",
"build:dev": "ng build table-plugin --configuration development",
"build:plugin": "node ../../tools/scripts/build-plugin.mjs --plugin=table-plugin",
"build:plugin:watch": "node ../../tools/scripts/build-plugin.mjs --plugin=table-plugin --watch",
"watch": "node ../../tools/scripts/build-plugin.mjs --plugin=table-plugin --watch",
"serve": "ng serve table-plugin",
"init": "concurrently --kill-others --names plugin,serve \"pnpm run build:plugin:watch\" \"pnpm run serve\"",
"init": "concurrently --kill-others --names plugin,serve \"pnpm run watch\" \"pnpm run serve\"",
"lint": "eslint .",
"test": "vitest"
}

View File

@@ -1,7 +1,7 @@
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideRouter, withHashLocation } from '@angular/router';
import { appRoutes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(appRoutes)],
providers: [provideRouter(appRoutes, withHashLocation())],
};

View File

@@ -3,7 +3,6 @@
<head>
<meta charset="utf-8" />
<title>table-plugin</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>

View File

@@ -1,7 +1,8 @@
{
"name": "Table plugin",
"description": "Table plugin to import or create tables",
"code": "/assets/plugin.js",
"icon": "/assets/icon.png",
"version": 2,
"code": "assets/plugin.js",
"icon": "assets/icon.png",
"permissions": ["content:read", "content:write"]
}

View File

@@ -0,0 +1,6 @@
/// <reference types="vitest/config" />
import { defineConfig } from 'vite';
export default defineConfig({
root: "./"
});

View File

@@ -6,8 +6,8 @@
"ses": "^1.1.0",
"zod": "^3.22.4"
},
"module": "./index.mjs",
"typings": "./index.d.ts",
"module": "./dist/index.js",
"typings": "./dist/index.d.ts",
"type": "module",
"scripts": {
"build": "vite build",

View File

@@ -6,6 +6,7 @@ export const manifestSchema = z.object({
host: z.string().url(),
code: z.string(),
icon: z.string().optional(),
version: z.number().optional(),
description: z.string().max(200).optional(),
permissions: z.array(
z.enum([

View File

@@ -2,5 +2,5 @@ import { z } from 'zod';
export const openUISchema = z.object({
width: z.number().positive(),
height: z.number().positive(),
height: z.number().positive()
});

View File

@@ -1,8 +1,28 @@
import { Manifest } from './models/manifest.model.js';
import { manifestSchema } from './models/manifest.schema.js';
export function getValidUrl(host: string, path: string): string {
return new URL(path, host).toString();
export function getValidUrl(host: string, path: string): URL {
return new URL(path, host);
}
export function prepareUrl(manifest: Manifest, url: string, params: Object): string {
const result = getValidUrl(manifest.host, url);
for (let [k, v] of Object.entries(params)) {
if (!result.searchParams.has(k)) {
result.searchParams.set(k, v);
}
}
if (manifest.version === undefined || manifest.version === 1) {
return result.toString();
} else if (manifest.version === 2) {
const queryString = result.searchParams.toString();
result.search = "";
result.hash = `/?${queryString}`;
return result.toString();
} else {
throw new Error("invalid manifest version");
}
}
export function loadManifest(url: string): Promise<Manifest> {

View File

@@ -1,6 +1,6 @@
import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest';
import { createPluginManager } from './plugin-manager';
import { loadManifestCode, getValidUrl } from './parse-manifest.js';
import { loadManifestCode, getValidUrl, prepareUrl } from './parse-manifest.js';
import { PluginModalElement } from './modal/plugin-modal.js';
import { openUIApi } from './api/openUI.api.js';
import type { Context, Theme } from '@penpot/plugin-types';
@@ -9,6 +9,7 @@ import type { Manifest } from './models/manifest.model.js';
vi.mock('./parse-manifest.js', () => ({
loadManifestCode: vi.fn(),
getValidUrl: vi.fn(),
prepareUrl: vi.fn(),
}));
vi.mock('./api/openUI.api.js', () => ({
@@ -71,7 +72,8 @@ describe('createPluginManager', () => {
vi.mocked(loadManifestCode).mockResolvedValue(
'console.log("Plugin loaded");',
);
vi.mocked(getValidUrl).mockReturnValue('https://example.com/plugin');
vi.mocked(getValidUrl).mockReturnValue(new URL('https://example.com/plugin'));
vi.mocked(prepareUrl).mockReturnValue('https://example.com/plugin');
});
afterEach(() => {
@@ -110,7 +112,7 @@ describe('createPluginManager', () => {
height: 300,
});
expect(getValidUrl).toHaveBeenCalledWith(manifest.host, '/test-url');
expect(prepareUrl).toHaveBeenCalledWith(manifest, '/test-url', {theme: "light"});
expect(openUIApi).toHaveBeenCalledWith(
'Test Modal',
'https://example.com/plugin',

View File

@@ -1,6 +1,6 @@
import type { Context, Theme } from '@penpot/plugin-types';
import { getValidUrl, loadManifestCode } from './parse-manifest.js';
import { prepareUrl, loadManifestCode } from './parse-manifest.js';
import { Manifest } from './models/manifest.model.js';
import { PluginModalElement } from './modal/plugin-modal.js';
import { openUIApi } from './api/openUI.api.js';
@@ -8,6 +8,7 @@ import { OpenUIOptions } from './models/open-ui-options.model.js';
import { RegisterListener } from './models/plugin.model.js';
import { openUISchema } from './models/open-ui-options.schema.js';
export async function createPluginManager(
context: Context,
manifest: Manifest,
@@ -80,9 +81,8 @@ export async function createPluginManager(
};
const openModal = (name: string, url: string, options?: OpenUIOptions) => {
const theme = context.theme as 'light' | 'dark';
const modalUrl = getValidUrl(manifest.host, url);
const theme = context.theme as Theme;
const modalUrl = prepareUrl(manifest, url, {theme});
if (modal?.getAttribute('iframe-src') === modalUrl) {
return;

View File

@@ -35,7 +35,7 @@ export default defineConfig({
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: '../../dist/plugins-runtime',
outDir: './dist/',
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,

View File

@@ -412,9 +412,6 @@ fn set_children_set(entries: Vec<Uuid>) {
for id in entries {
state.touch_shape(id);
if let Some(children_shape) = state.shapes.get_mut(&id) {
children_shape.set_deleted(false);
}
}
});

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