diff --git a/CHANGES.md b/CHANGES.md
index f34c011a8c..340df7330c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,12 +1,54 @@
# CHANGELOG
+## 2.9.0 (Unreleased)
+
+### :rocket: Epics and highlights
+
+### :boom: Breaking changes & Deprecations
+
+### :heart: Community contributions (Thank you!)
+
+- Clarify message when inviting existing team members to make it more user-friendly and clear which invitations will be sent. [Taiga #11441](https://tree.taiga.io/project/penpot/issue/11441) by [@iprithvitharun](https://github.com/iprithvitharun)
+- Update email change confirmation message for clarity and correct grammar. [GitHub #6786](https://github.com/penpot/penpot/issues/6786) by [@iprithvitharun](https://github.com/iprithvitharun)
+
+### :sparkles: New features & Enhancements
+
+- Add visual indicator for new comments in the workspace [Taiga #11328](https://tree.taiga.io/project/penpot/issue/11328)
+- On components overrides, separate the content of the text from the rest of properties [Taiga #7434](https://tree.taiga.io/project/penpot/us/7434)
+- Improve dashboard's sidebar [Taiga #10700](https://tree.taiga.io/project/penpot/us/10700)
+- Change "Save color" button to primary button [Taiga #9410](https://tree.taiga.io/project/penpot/issue/9410)
+- Support for exif rotated images [GitHub #6767](https://github.com/penpot/penpot/issues/6767)
+- Display Blend Mode and Layer Opacity properties in the Inspect tab [Taiga #11283](https://tree.taiga.io/project/penpot/issue/11283)
+- Provide CSS `mix-blend-mode` property in code editor when present on shape [Taiga #11282](https://tree.taiga.io/project/penpot/issue/11282)
+- Add the option to import tokens in a .zip file. [Taiga #11378](https://tree.taiga.io/project/penpot/us/11378)
+- New typography token type - font size token [Taiga #10938](https://tree.taiga.io/project/penpot/us/10938)
+- Hide bounding box while editing visual effects [Taiga #11576](https://tree.taiga.io/project/penpot/issue/11576)
+- Improved text layer resizing: Allow double-click on text bounding box to set auto-width/auto-height [Taiga #11577](https://tree.taiga.io/project/penpot/issue/11577)
+- Improve text layer auto-resize: auto-width switches to auto-height on horizontal resize, and only switches to fixed on vertical resize [Taiga #11578](https://tree.taiga.io/project/penpot/issue/11578)
+- Highlight first font in font selector search. Apply only on Enter or click. [Taiga #11579](https://tree.taiga.io/project/penpot/issue/11579)
+- Add the ability to show login dialog on profile settings [Github #6871](https://github.com/penpot/penpot/pull/6871)
+
+### :bug: Bugs fixed
+
+- Copying font size does not copy the unit [Taiga #11143](https://tree.taiga.io/project/penpot/issue/11143)
+- Fix text-decoration line-through that displays a wrong property value [Taiga #11145](https://tree.taiga.io/project/penpot/issue/11145)
+- Fix display error message on register form [Taiga #11444](https://tree.taiga.io/project/penpot/issue/11444)
+- Fix toggle focus mode did not restore viewport and selection upon exit [GitHub #6280](https://github.com/penpot/penpot/issues/6820)
+- Fix problem when creating a layout from an existing layout [Taiga #11554](https://tree.taiga.io/project/penpot/issue/11554)
+- Fix title button from Title Case to Capitalize [Taiga #11476](https://tree.taiga.io/project/penpot/issue/11476)
+- Fix touchpad swipe leading to navigating back/forth [GitHub #4246](https://github.com/penpot/penpot/issues/4246)
+- Keep color data when copying from info tab into CSS [Taiga #11144](https://tree.taiga.io/project/penpot/issue/11144)
+- Update HSL values to modern syntax as defined in W3C CSS Color Module Level 4 [Taiga #11144](https://tree.taiga.io/project/penpot/issue/11144)
+- Fix main component receives focus and is selected when using 'Show Main Component' [Taiga #11402](https://tree.taiga.io/project/penpot/issue/11402)
+- Fix duplicating pages with mainInstance shapes nested inside groups [Taiga #10774](https://tree.taiga.io/project/penpot/issue/10774)
+- Fix ESC key not closing Add/Manage Libraries modal [Taiga #11523](https://tree.taiga.io/project/penpot/issue/11523)
+
## 2.8.1 (Unreleased)
### :bug: Bugs fixed
- Fix unexpected exception on processing old texts [Github #6889](https://github.com/penpot/penpot/pull/6889)
-
## 2.8.0
### :rocket: Epics and highlights
@@ -27,6 +69,7 @@ in future versions. Therefore, **migration from Redis to ValKey is recommended f
on-premises instances** that want to keep up to date.
### :heart: Community contributions (Thank you!)
+
- Add Serbian language [GitHub #5002](https://github.com/penpot/penpot/issues/5002) by [crnobog69](https://github.com/crnobog69)
### :sparkles: New features & Enhancements
@@ -82,7 +125,6 @@ on-premises instances** that want to keep up to date.
- Fix copy in error message [GitHub #6615](https://github.com/penpot/penpot/pull/6615)
- Fix url on invitation link [Taiga #11284](https://tree.taiga.io/project/penpot/issue/11284)
-
## 2.7.1
### :bug: Bugs fixed
@@ -90,7 +132,6 @@ on-premises instances** that want to keep up to date.
- Fix incorrect handling of strokes with images on importing files
- Fix tokens disappearing after manual additions [Taiga #11063](https://tree.taiga.io/project/penpot/issue/11063)
-
## 2.7.0
### :rocket: Epics and highlights
@@ -222,7 +263,6 @@ on-premises instances** that want to keep up to date.
- Add character limitation to asset inputs [Taiga #10669](https://tree.taiga.io/project/penpot/issue/10669)
- Fix Storybook link 'list of all available icons' wrong path [Taiga #10705](https://tree.taiga.io/project/penpot/issue/10705)
-
## 2.5.4
### :heart: Community contributions (Thank you!)
@@ -267,7 +307,7 @@ on-premises instances** that want to keep up to date.
### :boom: Breaking changes & Deprecations
-Although this is not a breaking change, we believe it’s important to highlight it in this
+Although this is not a breaking change, we believe it's important to highlight it in this
section:
This release includes a fix for an internal bug in Penpot that caused incorrect handling
@@ -275,9 +315,9 @@ of media assets (e.g., fill images). The issue has been resolved since version 2
no new incorrect references will be generated. However, existing files may still contain
incorrect references.
-To address this, we’ve provided a script to correct these references in existing files.
+To address this, we've provided a script to correct these references in existing files.
-While having incorrect references generally doesn’t result in visible issues, there are
+While having incorrect references generally doesn't result in visible issues, there are
rare cases where it can cause problems. For example, if a component library (containing
images) is deleted, and that library is being used in other files, running the FileGC task
(responsible for freeing up space and performing logical deletions) could leave those
@@ -352,7 +392,6 @@ is a number of cores)
- Fix missing methods reference on API Docs
- Fix memory usage issue on file-gc asynchronous task (related to snapshots feature)
-
## 2.4.1
### :bug: Bugs fixed
@@ -360,7 +399,6 @@ is a number of cores)
- Fix error when importing files with touched components [Taiga #9625](https://tree.taiga.io/project/penpot/issue/9625)
- Fix problem when changing color libraries [Plugins #184](https://github.com/penpot/penpot-plugins/issues/184)
-
## 2.4.0
### :rocket: Epics and highlights
@@ -414,7 +452,6 @@ is a number of cores)
- Add initial documentation for Kubernetes
-
## 2.3.1
### :bug: Bugs fixed
@@ -422,7 +459,6 @@ is a number of cores)
- Fix unexpected issue on interaction between plugins sandbox and
internal impl of promise
-
## 2.3.0
### :rocket: Epics and highlights
@@ -448,7 +484,6 @@ is a number of cores)
You can enable it with the `enable-feature-text-editor-v2` configuration flag.
-
### :bug: Bugs fixed
- Fix problem with constraints buttons [Taiga #8465](https://tree.taiga.io/project/penpot/issue/8465)
@@ -488,8 +523,8 @@ is a number of cores)
### :boom: Breaking changes & Deprecations
- Removed "merge assets" option when exporting ".svg + .json" files. After the components changes the option wasn't
-working properly and we're planning to change the format soon. We think it's better to deprecate the option for the
-time being.
+ working properly and we're planning to change the format soon. We think it's better to deprecate the option for the
+ time being.
### :heart: Community contributions (Thank you!)
@@ -505,7 +540,7 @@ time being.
freeing up space in the database. It can be enabled with the
`enable-enable-tiered-file-data-storage` flag.
- *(On-Premise feature, EXPERIMENTAL).*
+ _(On-Premise feature, EXPERIMENTAL)._
- **JSON Interoperability for HTTP API** [Taiga #8372](https://tree.taiga.io/project/penpot/us/8372)
@@ -548,7 +583,7 @@ time being.
- **Design System**
- We implemented and subbed in new components from our Design System: `loader*` ([Taiga #8355](https://tree.taiga.io/project/penpot/task/8355)) and `tab-switcher*` ([Taiga #8518](https://tree.taiga.io/project/penpot/task/8518)).
+ We implemented and subbed in new components from our Design System: `loader*` ([Taiga #8355](https://tree.taiga.io/project/penpot/task/8355)) and `tab-switcher*` ([Taiga #8518](https://tree.taiga.io/project/penpot/task/8518)).
- **Storybook** [Taiga #6329](https://tree.taiga.io/project/penpot/us/6329)
@@ -603,11 +638,11 @@ time being.
### :sparkles: New features
-- Consolidate templates new order and naming [Taiga #8392](https://tree.taiga.io/project/penpot/task/8392)
+- Consolidate templates new order and naming [Taiga #8392](https://tree.taiga.io/project/penpot/task/8392)
### :bug: Bugs fixed
-- Fix the “search” label in translations [Taiga #8402](https://tree.taiga.io/project/penpot/issue/8402)
+- Fix the "search" label in translations [Taiga #8402](https://tree.taiga.io/project/penpot/issue/8402)
- Fix pencil loader [Taiga #8348](https://tree.taiga.io/project/penpot/issue/8348)
- Fix several issues on the OIDC.
- Fix regression on the `email-verification` flag [Taiga #8398](https://tree.taiga.io/project/penpot/issue/8398)
@@ -687,22 +722,21 @@ time being.
- Fix color palette sorting [Taiga #7458](https://tree.taiga.io/project/penpot/issue/7458)
- Fix style scoping problem with imported SVG [Taiga #7671](https://tree.taiga.io/project/penpot/issue/7671)
-
## 2.0.1
### :bug: Bugs fixed
- Fix different issues related to components v2 migrations including [Github #4443](https://github.com/penpot/penpot/issues/4443)
-
## 2.0.0 - I Just Can't Get Enough
### :rocket: Epics and highlights
+
- Grid CSS layout [Taiga #4915](https://tree.taiga.io/project/penpot/epic/4915)
- UI redesign [Taiga #4958](https://tree.taiga.io/project/penpot/epic/4958)
- New components System [Taiga #2662](https://tree.taiga.io/project/penpot/epic/2662)
- Swap components [Taiga #1331](https://tree.taiga.io/project/penpot/us/1331)
-- Images as fill [Taiga #2983](https://tree.taiga.io/project/penpot/us/2983)
+- Images as fill [Taiga #2983](https://tree.taiga.io/project/penpot/us/2983)
- HTML code generation [Taiga #5277](https://tree.taiga.io/project/penpot/us/5277)
- Light and dark themes [Taiga #2287](https://tree.taiga.io/project/penpot/us/2287)
@@ -711,9 +745,9 @@ time being.
- New strokes default to inside border [Taiga #6847](https://tree.taiga.io/project/penpot/issue/6847)
- Change default z ordering on layers in flex layout. The previous behavior was inconsistent with how HTML works and we changed it to be more consistent. Previous layers that overlapped could be hidden, the fastest way to fix this is changing the z-index property but a better way is to change the order of your layers.
-
### :heart: Community contributions (Thank you!)
-- New Hausa, Yoruba and Igbo translations and update translation files (by All For Tech Empowerment Foundation) [Taiga #6950](https://tree.taiga.io/project/penpot/us/6950), [Taiga #6534](https://tree.taiga.io/project/penpot/us/6534)
+
+- New Hausa, Yoruba and Igbo translations and update translation files (by All For Tech Empowerment Foundation) [Taiga #6950](https://tree.taiga.io/project/penpot/us/6950), [Taiga #6534](https://tree.taiga.io/project/penpot/us/6534)
- Hide bounding-box when editing shape (by @VasilevsVV) [#3930](https://github.com/penpot/penpot/pull/3930)
- CTRL + "+" to zoom into canvas instead of browser (by @audriu) [#3848](https://github.com/penpot/penpot/pull/3848)
- Add dev deps.edn in the project root (by @PEZ) [#3794](https://github.com/penpot/penpot/pull/3794)
@@ -722,6 +756,7 @@ time being.
- Typo (by StephanEggermont) [#157](https://github.com/penpot/penpot-docs/pull/157)
### :sparkles: New features
+
- Send comments with Ctrl+Enter / Cmd + Enter [Taiga #6085](https://tree.taiga.io/project/penpot/issue/6085)
- Select through stroke only rectangle [Taiga #5484](https://tree.taiga.io/project/penpot/issue/5484)
- Stroke default position [Taiga #6847](https://tree.taiga.io/project/penpot/issue/6847)
@@ -789,6 +824,7 @@ time being.
- [REDESIGN] Onboarding slides [Taiga #6678](https://tree.taiga.io/project/penpot/us/6678)
### :bug: Bugs fixed
+
- Fix pixelated thumbnails [Github #3681](https://github.com/penpot/penpot/issues/3681), [Github #3661](https://github.com/penpot/penpot/issues/3661)
- Fix problem with not applying colors to boards [Github #3941](https://github.com/penpot/penpot/issues/3941)
- Fix problem with path editor undoing changes [Github #3998](https://github.com/penpot/penpot/issues/3998)
@@ -797,7 +833,7 @@ time being.
- Selecting from Color Palette does not work for board when there is no existing fill [Taiga #6464](https://tree.taiga.io/project/penpot/issue/6464)
- Color thumbnails are consistently rounded in the inspect code mode [Taiga #5886](https://tree.taiga.io/project/penpot/issue/5886)
- Adding vector path points before first point of existing open path not working [Taiga #6593](https://tree.taiga.io/project/penpot/issue/6593)
-- Some image formats include the extension when importing [Taiga #5485](https://tree.taiga.io/project/penpot/issue/5485)
+- Some image formats include the extension when importing [Taiga #5485](https://tree.taiga.io/project/penpot/issue/5485)
- Gradient color tool doesn't work properly with flipped items [Taiga #6485](https://tree.taiga.io/project/penpot/issue/6485)
- [TEXT] Align options are not shown when several text are selected [Taiga #5948](https://tree.taiga.io/project/penpot/issue/5948)
- [VIEW MODE] Comments not working properly on multiple pages [Taiga #6281](https://tree.taiga.io/project/penpot/issue/6281)
@@ -841,7 +877,7 @@ time being.
### :sparkles: New features
-- Improve selected colors [Taiga #5805]( https://tree.taiga.io/project/penpot/us/5805)
+- Improve selected colors [Taiga #5805](https://tree.taiga.io/project/penpot/us/5805)
### :bug: Bugs fixed
@@ -876,7 +912,6 @@ time being.
- Fix deleted pages comments shown in right sidebar [Taiga #5648](https://tree.taiga.io/project/penpot/us/5648)
- Fix tooltip on toggle visibility and toggle lock buttons [Taiga #5141](https://tree.taiga.io/project/penpot/issue/5141)
-
## 1.19.1
### :bug: Bugs fixed
@@ -990,7 +1025,6 @@ time being.
- Update google fonts catalog (at 2023/07/06) [Taiga #5592](https://tree.taiga.io/project/penpot/issue/5592)
-
### :heart: Community contributions by (Thank you!)
- Update Typography palette order (by @akshay-gupta7) [Github #3156](https://github.com/penpot/penpot/pull/3156)
@@ -1144,12 +1178,14 @@ time being.
- Fix problem with opacity in imported SVG's [Taiga #4923](https://tree.taiga.io/project/penpot/issue/4923)
### :heart: Community contributions by (Thank you!)
+
- To @ondrejkonec: for contributing to the code with:
- Refactor CSS variables [Github #2948](https://github.com/penpot/penpot/pull/2948)
## 1.17.3
### :bug: Bugs fixed
+
- Fix copy and paste very nested inside itself [Taiga #4848](https://tree.taiga.io/project/penpot/issue/4848)
- Fix custom fonts not rendered correctly [Taiga #4874](https://tree.taiga.io/project/penpot/issue/4874)
- Fix problem with shadows and blur on multiple selection
@@ -1182,6 +1218,7 @@ time being.
## 1.17.1
### :bug: Bugs fixed
+
- Fix components groups items show the component name in list mode [Taiga #4770](https://tree.taiga.io/project/penpot/issue/4770)
- Fix typing CMD+Z on MacOS turns the cursor into a Zoom cursor [Taiga #4778](https://tree.taiga.io/project/penpot/issue/4778)
- Fix white space on small screens [Taiga #4774](https://tree.taiga.io/project/penpot/issue/4774)
@@ -1296,7 +1333,7 @@ time being.
### :boom: Breaking changes & Deprecations
-- Removed the support for v2 internal file data blob format. This
+- Removed the support for v2 internal file data blob format. This
version has never been documented nor set as default value so
technically this is not a breaking change because we are removing
a "private API".
@@ -1401,7 +1438,6 @@ time being.
- Fix when ungrouping, the items previously grouped should ALWAYS remain selected [Taiga #4064](https://tree.taiga.io/project/penpot/issue/4064)
- Change shortcut for "Clear undo" [#2219](https://github.com/penpot/penpot/issues/2219)
-
## 1.15.2-beta
### :bug: Bugs fixed
@@ -1485,6 +1521,7 @@ time being.
- Fix bringing complete file data when launching the export dialog [Taiga #4006](https://tree.taiga.io/project/penpot/issue/4006)
### :arrow_up: Deps updates
+
### :heart: Community contributions by (Thank you!)
## 1.14.2-beta
@@ -1525,10 +1562,10 @@ time being.
- Prototype connection should be under the rules [Taiga #3384](https://tree.taiga.io/project/penpot/issue/3384)
- Fix problem with empty text boxes events [Taiga #3627](https://tree.taiga.io/project/penpot/issue/3627)
-
## 1.13.5-beta
### :bug: Bugs fixed
+
- Fix orientation artboard preset not working with differently sized artboards [Taiga #3548](https://tree.taiga.io/project/penpot/issue/3548)
- Fix background on export arboards [Taiga #1991](https://tree.taiga.io/project/penpot/issue/1991)
@@ -1672,6 +1709,7 @@ time being.
- Fix problem when resizing a group with texts with auto-width/height [#3171](https://tree.taiga.io/project/penpot/issue/3171)
### :arrow_up: Deps updates
+
### :heart: Community contributions by (Thank you!)
## 1.12.4-beta
@@ -1689,7 +1727,7 @@ time being.
### :bug: Bugs fixed
- Fix issue with shift+select to deselect shapes [Taiga #3154](https://tree.taiga.io/project/penpot/issue/3154)
-- Fix issue with drag-select shapes [Taiga #3165](https://tree.taiga.io/project/penpot/issue/3165)
+- Fix issue with drag-select shapes [Taiga #3165](https://tree.taiga.io/project/penpot/issue/3165)
- Fix issue on password persistence after registration process on private instances
## 1.12.2-beta
@@ -1707,7 +1745,6 @@ time being.
- Fix length of names in sidebar [Taiga #2962](https://tree.taiga.io/project/penpot/issue/2962)
- Fix issues on loki integration
-
## 1.12.0-beta
### :boom: Breaking changes
diff --git a/backend/resources/app/email/change-email/en.html b/backend/resources/app/email/change-email/en.html
index 7a5f1f1189..fa021f55ee 100644
--- a/backend/resources/app/email/change-email/en.html
+++ b/backend/resources/app/email/change-email/en.html
@@ -193,7 +193,7 @@
- Click to the link below to confirm the change:
+ Click the link below to confirm the change.
@@ -217,8 +217,7 @@
- If you received this email by mistake, please consider changing your password for security
- reasons.
+ If you did not request this change, consider changing your password for security reasons.
diff --git a/backend/resources/app/email/change-email/en.txt b/backend/resources/app/email/change-email/en.txt
index 09d6e84a56..eff98baf6f 100644
--- a/backend/resources/app/email/change-email/en.txt
+++ b/backend/resources/app/email/change-email/en.txt
@@ -2,12 +2,11 @@ Hello {{name|abbreviate:25}}!
We received a request to change your current email to {{ pending-email }}.
-Click to the link below to confirm the change:
+Click the link below to confirm the change.
{{ public-uri }}/#/auth/verify-token?token={{token}}
-If you received this email by mistake, please consider changing your password
-for security reasons.
+If you did not request this change, consider changing your password for security reasons.
Enjoy!
The Penpot team.
diff --git a/backend/scripts/manage.py b/backend/scripts/manage.py
index f731319c9a..88b5d4f43a 100755
--- a/backend/scripts/manage.py
+++ b/backend/scripts/manage.py
@@ -71,19 +71,27 @@ def run_cmd(params):
print("EXC:", str(cause))
sys.exit(-2)
-def create_profile(fullname, email, password):
+def create_profile(fullname, email, password, skip_tutorial=False, skip_walkthrough=False):
+ props = {}
+ if skip_tutorial:
+ props["viewed-tutorial?"] = True
+ if skip_walkthrough:
+ props["viewed-walkthrough?"] = True
+
params = {
"cmd": "create-profile",
"params": {
"fullname": fullname,
"email": email,
- "password": password
+ "password": password,
+ **props
}
}
res = run_cmd(params)
print(f"Created: {res['email']} / {res['id']}")
+
def update_profile(email, fullname, password, is_active):
params = {
"cmd": "update-profile",
@@ -170,6 +178,8 @@ parser.add_argument("-n", "--fullname", help="fullname", action="store")
parser.add_argument("-e", "--email", help="email", action="store")
parser.add_argument("-p", "--password", help="password", action="store")
parser.add_argument("-c", "--connect", help="connect to PREPL", action="store", default="tcp://localhost:6063")
+parser.add_argument("--skip-tutorial", help="mark tutorial as viewed", action="store_true")
+parser.add_argument("--skip-walkthrough", help="mark walkthrough as viewed", action="store_true")
args = parser.parse_args()
diff --git a/backend/src/app/media.clj b/backend/src/app/media.clj
index 710275a542..51649db3d5 100644
--- a/backend/src/app/media.clj
+++ b/backend/src/app/media.clj
@@ -10,6 +10,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
+ [app.common.logging :as l]
[app.common.media :as cm]
[app.common.schema :as sm]
[app.common.schema.openapi :as-alias oapi]
@@ -21,6 +22,7 @@
[buddy.core.bytes :as bb]
[buddy.core.codecs :as bc]
[clojure.java.shell :as sh]
+ [clojure.string]
[clojure.xml :as xml]
[cuerdas.core :as str]
[datoteka.fs :as fs]
@@ -215,6 +217,23 @@
{:width (int width)
:height (int height)})))]))
+(defn- get-dimensions-with-orientation [^String path]
+ ;; Image magick doesn't give info about exif rotation so we use the identify command
+ ;; If we are processing an animated gif we use the first frame with -scene 0
+ (let [dim-result (sh/sh "identify" "-format" "%w %h\n" path)
+ orient-result (sh/sh "identify" "-format" "%[EXIF:Orientation]\n" path)]
+ (if (and (= 0 (:exit dim-result))
+ (= 0 (:exit orient-result)))
+ (let [[w h] (-> (:out dim-result)
+ str/trim
+ (clojure.string/split #"\s+")
+ (->> (mapv #(Integer/parseInt %))))
+ orientation (-> orient-result :out str/trim)]
+ (case orientation
+ ("6" "8") {:width h :height w} ; Rotated 90 or 270 degrees
+ {:width w :height h})) ; Normal or unknown orientation
+ nil)))
+
(defmethod process :info
[{:keys [input] :as params}]
(let [{:keys [path mtype] :as input} (check-input input)]
@@ -234,13 +253,17 @@
:code :media-type-mismatch
:hint (str "Seems like you are uploading a file whose content does not match the extension."
"Expected: " mtype ". Got: " mtype')))
- ;; For an animated GIF, getImageWidth/Height returns the delta size of one frame (if no frame given
- ;; it returns size of the last one), whereas getPageWidth/Height always return the full size of
- ;; any frame.
- (assoc input
- :width (.getPageWidth instance)
- :height (.getPageHeight instance)
- :ts (dt/now))))))
+ (let [{:keys [width height]}
+ (or (get-dimensions-with-orientation (str path))
+ (do
+ (l/warn "Failed to read image dimensions with orientation; falling back to im4java"
+ {:path path})
+ {:width (.getPageWidth instance)
+ :height (.getPageHeight instance)}))]
+ (assoc input
+ :width width
+ :height height
+ :ts (dt/now)))))))
(defmethod process-error org.im4java.core.InfoException
[error]
diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj
index b0b21cc2fe..bb23c6997c 100644
--- a/backend/src/app/rpc/commands/teams.clj
+++ b/backend/src/app/rpc/commands/teams.clj
@@ -140,7 +140,7 @@
WHEN 'professional' THEN 'active'
ELSE COALESCE(p.props->'~:subscription'->>'~:status', 'incomplete')
END,
- '~:seats', p.props->'~:quantity'
+ '~:seats', p.props->'~:subscription'->'~:quantity'
) AS subscription
FROM team_profile_rel AS tp
JOIN team AS t ON (t.id = tp.team_id)
@@ -193,7 +193,8 @@
(def ^:private sql:get-owned-teams
"SELECT t.id, t.name,
- (SELECT count(*) FROM team_profile_rel WHERE team_id=t.id) AS total_members
+ (SELECT count(*) FROM team_profile_rel WHERE team_id=t.id) AS total_members,
+ (SELECT count(*) FROM team_profile_rel WHERE team_id=t.id AND can_edit=true) AS total_editors
FROM team AS t
JOIN team_profile_rel AS tpr ON (tpr.team_id = t.id)
WHERE t.is_default IS false
diff --git a/backend/test/backend_tests/rpc_team_test.clj b/backend/test/backend_tests/rpc_team_test.clj
index ddb2e33bbc..60bdb62d6d 100644
--- a/backend/test/backend_tests/rpc_team_test.clj
+++ b/backend/test/backend_tests/rpc_team_test.clj
@@ -460,11 +460,14 @@
::rpc/profile-id (:id profile1)}
out (th/command! params)]
+ ;; (th/print-result! out)
(t/is (th/success? out))
- (let [result (:result out)]
+ (let [[item1 :as result] (:result out)]
(t/is (= 1 (count result)))
- (t/is (= (:id team1) (-> result first :id)))
- (t/is (not= (:default-team-id profile1) (-> result first :id))))))
+ (t/is (= (:id team1) (:id item1)))
+ (t/is (= 1 (:total-members item1)))
+ (t/is (= 1 (:total-editors item1)))
+ (t/is (not= (:default-team-id profile1) (:id item1))))))
(t/deftest team-deletion-1
diff --git a/common/src/app/common/colors.cljc b/common/src/app/common/colors.cljc
index 7f4d3adb67..e16acf94a3 100644
--- a/common/src/app/common/colors.cljc
+++ b/common/src/app/common/colors.cljc
@@ -349,7 +349,7 @@
rounded-s (d/format-number (* 100 s) precision)
rounded-l (d/format-number (* 100 l) precision)
rounded-a (d/format-number a precision)]
- (str/concat "" rounded-h ", " rounded-s "%, " rounded-l "%, " rounded-a)))
+ (str/concat "" rounded-h " " rounded-s "% " rounded-l "% / " rounded-a)))
(defn format-rgba
[[r g b a]]
diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc
index c5d1812291..d9965ce556 100644
--- a/common/src/app/common/files/changes.cljc
+++ b/common/src/app/common/files/changes.cljc
@@ -241,7 +241,7 @@
[:shapes ::sm/any]
[:index {:optional true} [:maybe :int]]
[:after-shape {:optional true} ::sm/any]
- [:component-swap {:optional true} :boolean]]]
+ [:allow-altering-copies {:optional true} :boolean]]]
[:reorder-children
[:map {:title "ReorderChildrenChange"}
@@ -418,7 +418,7 @@
[:type [:= :set-token-set]]
[:set-name :string]
[:group? :boolean]
- [:token-set [:maybe ctob/schema:token-set-attrs]]]]
+ [:token-set [:maybe [:fn ctob/token-set?]]]]]
[:set-token
[:map {:title "SetTokenChange"}
@@ -761,7 +761,7 @@
(d/update-in-when data [:components component-id :objects] reg-objects))))
(defmethod process-change :mov-objects
- [data {:keys [parent-id shapes index page-id component-id ignore-touched after-shape component-swap syncing]}]
+ [data {:keys [parent-id shapes index page-id component-id ignore-touched after-shape allow-altering-copies syncing]}]
(letfn [(calculate-invalid-targets [objects shape-id]
(let [reduce-fn #(into %1 (calculate-invalid-targets objects %2))]
(->> (get-in objects [shape-id :shapes])
@@ -776,7 +776,7 @@
(and shape
(not (invalid-targets parent-id))
(not (cfh/components-nesting-loop? objects shape-id parent-id))
- (or component-swap ;; On a component swap it's allowed to change the structure of a copy
+ (or allow-altering-copies ;; In some cases (like a component swap) it's allowed to change the structure of a copy
syncing ;; If we are syncing the changes of a main component, it's allowed to change the structure of a copy
(and
(not (ctk/in-component-copy? (get objects (:parent-id shape)))) ;; We don't want to change the structure of component copies
@@ -1027,11 +1027,10 @@
(ctob/delete-set lib' set-name))
(not (ctob/get-set lib' set-name))
- (ctob/add-set lib' (ctob/make-token-set token-set))
+ (ctob/add-set lib' token-set)
:else
- (ctob/update-set lib' set-name (fn [prev-token-set]
- (ctob/make-token-set (merge prev-token-set token-set)))))))))
+ (ctob/update-set lib' set-name (fn [_] token-set)))))))
(defmethod process-change :set-token-theme
[data {:keys [group theme-name theme]}]
diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc
index 9dd4da715d..35f95a78fe 100644
--- a/common/src/app/common/files/changes_builder.cljc
+++ b/common/src/app/common/files/changes_builder.cljc
@@ -464,8 +464,8 @@
(some? index)
(assoc :index index)
- (:component-swap options)
- (assoc :component-swap true)
+ (:allow-altering-copies options)
+ (assoc :allow-altering-copies true)
(:ignore-touched options)
(assoc :ignore-touched true))
@@ -473,12 +473,14 @@
(fn [undo-changes shape]
(let [prev-sibling (cfh/get-prev-sibling objects (:id shape))]
(conj undo-changes
- {:type :mov-objects
- :page-id (::page-id (meta changes))
- :parent-id (:parent-id shape)
- :shapes [(:id shape)]
- :after-shape prev-sibling
- :index 0}))) ; index is used in case there is no after-shape (moving bottom shapes)
+ (cond-> {:type :mov-objects
+ :page-id (::page-id (meta changes))
+ :parent-id (:parent-id shape)
+ :shapes [(:id shape)]
+ :after-shape prev-sibling
+ :index 0} ; index is used in case there is no after-shape (moving bottom shapes)
+ (:allow-altering-copies options)
+ (assoc :allow-altering-copies true)))))
restore-touched-change
{:type :mod-obj
@@ -916,7 +918,7 @@
(-> changes
(update :redo-changes conj {:type :set-token-set
:set-name name
- :token-set (assoc prev-token-set :name new-name)
+ :token-set (ctob/rename prev-token-set new-name)
:group? false})
(update :undo-changes conj {:type :set-token-set
:set-name new-name
@@ -937,11 +939,11 @@
:group? group?})
(update :undo-changes conj (if prev-token-set
{:type :set-token-set
- :set-name (or
- ;; Undo of edit
- (:name token-set)
- ;; Undo of delete
- set-name)
+ :set-name (if token-set
+ ;; Undo of edit
+ (ctob/get-name token-set)
+ ;; Undo of delete
+ set-name)
:token-set prev-token-set
:group? group?}
;; Undo of create
diff --git a/common/src/app/common/files/helpers.cljc b/common/src/app/common/files/helpers.cljc
index 360d8428d3..7f5c60f12f 100644
--- a/common/src/app/common/files/helpers.cljc
+++ b/common/src/app/common/files/helpers.cljc
@@ -152,12 +152,22 @@
(dm/get-prop shape :type))))
(defn get-children-ids
- [objects id]
- (letfn [(get-children-ids-rec [id processed]
- (when (not (contains? processed id))
- (when-let [shapes (-> (get objects id) :shapes (some-> vec))]
- (into shapes (mapcat #(get-children-ids-rec % (conj processed id))) shapes))))]
- (get-children-ids-rec id #{})))
+ "Returns the ids of all the descendants of the shape identified
+ by the id. Optionally, you can pass an ignore function to indicate
+ when to ignore a descendant (and all its descendants)"
+ ([objects id]
+ (get-children-ids objects id {}))
+ ([objects id {:keys [ignore-children-fn]
+ ;;ignore-children-fn should receive a shape and return a boolean
+ :or {ignore-children-fn (constantly false)}}]
+ (letfn [(get-children-ids-rec [id processed]
+ (when-not (contains? processed id)
+ (when-let [shapes (as-> (get objects id) $
+ (:shapes $)
+ (remove ignore-children-fn $)
+ (some-> $ vec))]
+ (into shapes (mapcat #(get-children-ids-rec % (conj processed id))) shapes))))]
+ (get-children-ids-rec id #{}))))
(defn get-children-ids-with-self
[objects id]
diff --git a/common/src/app/common/files/migrations.cljc b/common/src/app/common/files/migrations.cljc
index 3cb3e8daf2..5973451a94 100644
--- a/common/src/app/common/files/migrations.cljc
+++ b/common/src/app/common/files/migrations.cljc
@@ -32,6 +32,7 @@
[app.common.types.shape :as cts]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.shadow :as ctss]
+ [app.common.types.text :as cttx]
[app.common.uuid :as uuid]
[clojure.set :as set]
[cuerdas.core :as str]))
@@ -1527,6 +1528,31 @@
colors
colors))))
+(defmethod migrate-data "0009-add-partial-text-touched-flags"
+ [data _]
+ (letfn [(update-object [page object]
+ (if (and (cfh/text-shape? object)
+ (ctk/in-component-copy? object))
+ (let [file {:id (:id data) :data data}
+ libs (when (:libs data)
+ (deref (:libs data)))
+ ref-shape (ctf/find-ref-shape file page libs object
+ {:include-deleted? true :with-context? true})
+ partial-touched (when ref-shape
+ (cttx/get-diff-type (:content object) (:content ref-shape)))]
+ (if (seq partial-touched)
+ (update object :touched (fn [touched]
+ (reduce #(ctk/set-touched-group %1 %2)
+ touched
+ partial-touched)))
+ object))
+ object))
+
+ (update-page [page]
+ (d/update-when page :objects d/update-vals (partial update-object page)))]
+
+ (update data :pages-index d/update-vals update-page)))
+
(def available-migrations
(into (d/ordered-set)
["legacy-2"
@@ -1591,4 +1617,5 @@
"0006-fix-old-texts-fills"
"0007-clear-invalid-strokes-and-fills-v2"
"0008-fix-library-colors-v4"
- "0009-clean-library-colors"]))
+ "0009-clean-library-colors"
+ "0009-add-partial-text-touched-flags"]))
diff --git a/common/src/app/common/files/repair.cljc b/common/src/app/common/files/repair.cljc
index bf51851003..1d638586ee 100644
--- a/common/src/app/common/files/repair.cljc
+++ b/common/src/app/common/files/repair.cljc
@@ -96,7 +96,7 @@
(log/dbg :hint "repairing shape :invalid-parent" :id (:id shape) :name (:name shape) :page-id page-id)
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
- (pcb/change-parent (:parent-id args) [shape] nil {:component-swap true})))
+ (pcb/change-parent (:parent-id args) [shape] nil {:allow-altering-copies true})))
(defmethod repair-error :frame-not-found
[_ {:keys [shape page-id] :as error} file-data _]
@@ -387,7 +387,7 @@
(-> (pcb/empty-changes nil page-id)
(pcb/with-file-data file-data)
(pcb/update-shapes [(:id shape)] repair-shape)
- (pcb/change-parent uuid/zero [shape] nil {:component-swap true}))))
+ (pcb/change-parent uuid/zero [shape] nil {:allow-altering-copies true}))))
(defmethod repair-error :root-copy-not-allowed
[_ {:keys [shape page-id] :as error} file-data _]
@@ -602,11 +602,6 @@
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
file)
-(defmethod repair-error :variant-no-properties
- [_ error file _]
- (log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
- file)
-
(defmethod repair-error :variant-bad-variant-name
[_ error file _]
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc
index 2659f65336..5bdb410669 100644
--- a/common/src/app/common/files/validate.cljc
+++ b/common/src/app/common/files/validate.cljc
@@ -68,7 +68,6 @@
:variant-bad-name
:variant-bad-variant-name
:variant-component-bad-name
- :variant-no-properties
:variant-component-bad-id})
(def ^:private schema:error
@@ -589,11 +588,7 @@
(when-not (ctk/is-variant? main-component)
(report-error :not-a-variant
(str/ffmt "Shape % should be a variant" (:id main-component))
- main-component file component-page))
- (when (< (count (:variant-properties component)) 1)
- (report-error :variant-no-properties
- (str/ffmt "Component variant % should have properties" (:id main-component))
- main-component file nil))))
+ main-component file component-page))))
(defn- check-component
"Validate semantic coherence of a component. Report all errors found."
diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc
index 804cac400b..80f96fd8aa 100644
--- a/common/src/app/common/flags.cljc
+++ b/common/src/app/common/flags.cljc
@@ -117,6 +117,7 @@
;; Only for developtment.
:tiered-file-data-storage
:token-units
+ :token-typography-types
:transit-readable-response
:user-feedback
;; TODO: remove this flag.
@@ -149,7 +150,8 @@
:enable-onboarding
:enable-dashboard-templates-section
:enable-google-fonts-provider
- :enable-component-thumbnails])
+ :enable-component-thumbnails
+ :enable-render-wasm-dpr])
(defn parse
[& flags]
diff --git a/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc b/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc
index 424dcbb34f..1ec6bffd5a 100644
--- a/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc
+++ b/common/src/app/common/geom/shapes/grid_layout/layout_data.cljc
@@ -467,15 +467,15 @@
row-tracks (set-flex-multi-span parent row-tracks children-map shape-cells bounds objects :row)
;; Once auto sizes have been calculated we get calculate the `fr` unit with the remainining size and adjust the size
- free-column-space (max 0 (- bound-width (+ column-total-size-nofr column-total-gap)))
- free-row-space (max 0 (- bound-height (+ row-total-size-nofr row-total-gap)))
+ fr-column-space (max 0 (- bound-width (+ column-total-size-nofr column-total-gap)))
+ fr-row-space (max 0 (- bound-height (+ row-total-size-nofr row-total-gap)))
;; Get the minimum values for fr's
min-column-fr (min-fr-value column-tracks)
min-row-fr (min-fr-value row-tracks)
- column-fr (if auto-width? min-column-fr (mth/finite (/ free-column-space column-frs) 0))
- row-fr (if auto-height? min-row-fr (mth/finite (/ free-row-space row-frs) 0))
+ column-fr (if auto-width? min-column-fr (mth/finite (/ fr-column-space column-frs) 0))
+ row-fr (if auto-height? min-row-fr (mth/finite (/ fr-row-space row-frs) 0))
column-tracks (set-fr-value column-tracks column-fr auto-width?)
row-tracks (set-fr-value row-tracks row-fr auto-height?)
@@ -484,13 +484,13 @@
column-total-size (tracks-total-size column-tracks)
row-total-size (tracks-total-size row-tracks)
- free-column-space (max 0 (if auto-width? 0 (- bound-width (+ column-total-size column-total-gap))))
- free-row-space (max 0 (if auto-height? 0 (- bound-height (+ row-total-size row-total-gap))))
+ auto-column-space (max 0 (if auto-width? 0 (- bound-width (+ column-total-size column-total-gap))))
+ auto-row-space (max 0 (if auto-height? 0 (- bound-height (+ row-total-size row-total-gap))))
column-autos (tracks-total-autos column-tracks)
row-autos (tracks-total-autos row-tracks)
- column-add-auto (/ free-column-space column-autos)
- row-add-auto (/ free-row-space row-autos)
+ column-add-auto (/ auto-column-space column-autos)
+ row-add-auto (/ auto-row-space row-autos)
column-tracks (cond-> column-tracks
(= :stretch (:layout-justify-content parent))
diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc
index 197f8c8a29..d928268d6e 100644
--- a/common/src/app/common/logic/libraries.cljc
+++ b/common/src/app/common/logic/libraries.cljc
@@ -29,6 +29,7 @@
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
+ [app.common.types.text :as cttx]
[app.common.types.token :as cto]
[app.common.types.typography :as cty]
[app.common.types.variant :as ctv]
@@ -1664,28 +1665,33 @@
:shapes all-parents})]))))
+(defn- text-partial-change-value
+ [touched-content untouched-content touched]
+ (cond
+ (touched :text-content-structure-same-attrs)
+ (if (touched :text-content-attribute)
+ ;; Both structure and attrs has been touched, keep the
+ ;; touched-content
+ touched-content
+ ;; Keep the touched-content structure and texts, update
+ ;; its attrs to make them like the untouched-content
+ (cttx/copy-attrs-keys touched-content (cttx/get-first-paragraph-text-attrs untouched-content)))
+
+ (touched :text-content-text)
+ ;; Keep the texts touched in touched-content, so copy the
+ ;; texts from touched-content into untouched-content
+ (cttx/copy-text-keys touched-content untouched-content)
+
+ (touched :text-content-attribute)
+ ;; Keep the attrs touched in touched-content, so copy the
+ ;; texts from untouched-content into touched-content
+ (cttx/copy-text-keys untouched-content touched-content)))
+
(defn- add-update-attr-operations
- [attr dest-shape origin-shape roperations uoperations touched]
- (let [orig-value (get origin-shape attr)
- dest-value (get dest-shape attr)
- ;; position-data is a special case because can be affected by :geometry-group and :content-group
- ;; so, if the position-data changes but the geometry is touched we need to reset the position-data
- ;; so it's calculated again
- reset-pos-data?
- (and (cfh/text-shape? origin-shape)
- (= attr :position-data)
- (not= orig-value dest-value)
- (touched :geometry-group))
-
- val (cond
- ;; If position data changes and the geometry group is touched
- ;; we need to put to nil so we can regenerate it
- reset-pos-data? nil
- :else orig-value)
-
- roperation {:type :set
+ [attr dest-shape roperations uoperations attr-val]
+ (let [roperation {:type :set
:attr attr
- :val val
+ :val attr-val
:ignore-touched true}
uoperation {:type :set
:attr attr
@@ -1694,6 +1700,34 @@
[(conj roperations roperation)
(conj uoperations uoperation)]))
+(defn- is-text-partial-change?
+ "Check if the attr update is a text partial change"
+ [untouched-shape touched-shape]
+ (let [touched (get touched-shape :touched #{})
+ partial-text-keys [:text-content-attribute :text-content-text]
+ active-keys (filter touched partial-text-keys)
+ untouched-content (:content untouched-shape)
+ untouched-attrs (cttx/get-first-paragraph-text-attrs untouched-content)
+ eq-untouched-attrs? (cttx/equal-attrs? untouched-content untouched-attrs)]
+ (and
+ (or
+ ;; One and only one of the keys is pressent
+ (= 1 (count active-keys))
+ (and
+ (not (touched :text-content-attribute))
+ (touched :text-content-structure-same-attrs)))
+
+ (or
+ ;; Both has the same structure
+ (cttx/equal-structure? untouched-content (:content touched-shape))
+
+ ;; The origin and destiny have different structures, but each have the same attrs
+ ;; for all the items on its content tree
+ (and
+ eq-untouched-attrs?
+ (touched :text-content-structure-same-attrs))))))
+
+
(defn- update-attrs
"The main function that implements the attribute sync algorithm. Copy
attributes that have changed in the origin shape to the dest shape.
@@ -1737,58 +1771,271 @@
(generate-update-tokens container dest-shape origin-shape touched omit-touched?))
(let [attr-group (get ctk/sync-attrs attr)
- skip-operations? (or (= (get origin-shape attr) (get dest-shape attr))
- (and (touched attr-group)
- omit-touched?))
+ ;; position-data is a special case because can be affected by
+ ;; :geometry-group and :content-group so, if the position-data
+ ;; changes but the geometry is touched we need to reset the position-data
+ ;; so it's calculated again
+ reset-pos-data? (and (cfh/text-shape? origin-shape)
+ (= attr :position-data)
+ (not= (:position-data origin-shape) (:position-data dest-shape))
+ (touched :geometry-group))
+
+ ;; On texts, when we want to omit the touched attrs, both text (the actual letters)
+ ;; and attrs (bold, font, etc) are in the same attr :content.
+ ;; If only one of them is touched, we want to adress this case and
+ ;; only update the untouched one
+ text-partial-change?
+ (when (and
+ omit-touched?
+ (cfh/text-shape? origin-shape)
+ (= :content attr)
+ (touched attr-group))
+ (is-text-partial-change? origin-shape dest-shape))
+
+ skip-operations?
+ (or (= (get origin-shape attr) (get dest-shape attr))
+ (and (touched attr-group)
+ omit-touched?
+ ;; When it is a text-partial-change, we should generate operations
+ ;; even when omit-touched? is true, but updating only the text or
+ ;; the attributes, omiting the other part
+ (not text-partial-change?)))
+
+ attr-val (when-not skip-operations?
+ (cond
+ ;; If position data changes and the geometry group is touched
+ ;; we need to put to nil so we can regenerate it
+ reset-pos-data?
+ nil
+
+ text-partial-change?
+ (text-partial-change-value (:content dest-shape)
+ (:content origin-shape)
+ touched)
+
+ :else
+ (get origin-shape attr)))
+
+ ;; If the final attr-value is the actual value, skip
+ skip-operations? (or skip-operations?
+ (= attr-val (get dest-shape attr)))
+
+
+ ;; On a text-partial-change, we want to force a position-data reset
+ ;; so it's calculated again
+ [roperations uoperations]
+ (if (and text-partial-change? (not skip-operations?))
+ (add-update-attr-operations :position-data dest-shape roperations uoperations nil)
+ [roperations uoperations])
[roperations' uoperations']
(if skip-operations?
[roperations uoperations]
- (add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched))]
+ (add-update-attr-operations attr dest-shape roperations uoperations attr-val))]
(recur (next attrs)
roperations'
uoperations')))))))
+
+(defn- switch-text-change-value
+ [prev-content ;; The :content of the text before the switch
+ current-content ;; The :content of the text after the switch (a clean copy)
+ ref-content touched] ;; The :content of the referenced text on the main component
+ ;; before the switch
+ (let [;; We need the differences between the contents on the main
+ ;; components. current-content is the content of a clean copy,
+ ;; so for all effects its the same as the content on its main
+ main-comps-diff (cttx/get-diff-type ref-content current-content)
+ can-keep-text? (not (contains? main-comps-diff :text-content-text))
+ can-keep-attr? (not (contains? main-comps-diff :text-content-attribute))
+ main-diff-structure? (contains? main-comps-diff :text-content-structure)
+
+ current-attrs (cttx/get-first-paragraph-text-attrs current-content)
+ ;; Have current content an uniform style?
+ curr-unif-style? (cttx/equal-attrs? current-content current-attrs)
+ prev-attrs (cttx/get-first-paragraph-text-attrs prev-content)
+ ;; Have prev content an uniform style?
+ prev-unif-style? (cttx/equal-attrs? prev-content prev-attrs)
+ ref-attrs (cttx/get-first-paragraph-text-attrs ref-content)
+ ;; Have ref content an uniform style?
+ ref-unif-style? (cttx/equal-attrs? ref-content ref-attrs)]
+ (cond
+ ;; When the main components have a difference in structure
+ ;; (different number of paragraph or text entries)
+ main-diff-structure?
+ ;; Special case for adding or removing paragraphs:
+ ;; If the structure has changed between ref-content and current-content,
+ ;; but each one have uniform attributes, and the attrs on the main
+ ;; components were equal, we keep the touched-content structure and
+ ;; texts, updating its attrs to make them like the current-content
+ (if (and curr-unif-style?
+ ref-unif-style?
+ prev-unif-style?
+ (= ref-attrs current-attrs))
+ (cttx/copy-attrs-keys current-content prev-attrs)
+ ;; In any other case of structure change, we discard all
+ ;; the overrides and keep the content of the current-shape
+ current-content)
+
+ ;; When the main components are equal, we keep the updated
+ ;; content from previous-shape as is
+ (and can-keep-text? can-keep-attr?)
+ prev-content
+
+ ;; When we can't keep anything, we discard all the
+ ;; overrides and keep the content of the current-shape
+ (and (not can-keep-text?) (not can-keep-attr?))
+ current-content
+
+ ;; Special case for added or removed paragraphs:
+ ;; If the structure has changed on current-content, but it has uniform attributes
+ ;; and the previous-content also has uniform attributes, and we can keep the changes
+ ;; on the text, we keep the touched-content structure and texts, updating
+ ;; its attrs to make them like the current-content
+ (and (touched :text-content-structure)
+ curr-unif-style?
+ prev-unif-style?)
+ (if can-keep-text?
+ (cttx/copy-attrs-keys prev-content current-attrs)
+ (cttx/copy-attrs-keys current-content prev-attrs))
+
+ ;; In any other case of structure change, we discard all
+ ;; the overrides and keep the content of the current-shape
+ (touched :text-content-structure)
+ current-content
+
+ ;; When there is a change on :text-content-text,
+ ;; and and we can keep it, we copy the texts from
+ ;; previous-shape over the attrs of current-shape
+ (and
+ (touched :text-content-text) can-keep-text?)
+ (cttx/copy-text-keys prev-content current-content)
+
+ ;; When there is a change on :text-content-attribute,
+ ;; and we can keep it, we copy the texts from current-shape
+ ;; over the attrs of previous-shape
+ (and
+ (touched :text-content-attribute) can-keep-attr?)
+ (cttx/copy-text-keys current-content prev-content)
+
+ ;; In any other case, we discard all the overrides
+ ;; and keep the content of the current-shape
+ :else
+ current-content)))
+
(defn update-attrs-on-switch
- "Copy attributes that have changed in the origin shape to the dest shape. Used on variants switch"
- [changes dest-shape origin-shape dest-root origin-root origin-ref-shape container]
+ "Copy attributes that have changed in the shape previous to the switch
+ to the current shape (post switch). Used only on variants switch"
+ ;; NOTE: This function have similitudes but is very different to
+ ;; update-attrs:
+ ;; In components (update-attrs), the source shape is "clean", and the destination
+ ;; shape may have touched elements that shouldn't be overwritten.
+ ;; In variants (update-attrs-on-switch), the destination shape is "clean",
+ ;; and it's the source shape that may have touched elements, and we only want
+ ;; to copy those touched elements.
+ [changes current-shape previous-shape current-root prev-root origin-ref-shape container]
(let [;; We need to sync only the position relative to the origin of the component.
;; (see update-attrs for a full explanation)
- origin-shape (reposition-shape origin-shape origin-root dest-root)
- touched (get dest-shape :touched #{})
- touched-origin (get origin-shape :touched #{})]
+ previous-shape (reposition-shape previous-shape prev-root current-root)
+ touched (get previous-shape :touched #{})]
(loop [attrs updatable-attrs
- roperations [{:type :set-touched :touched (:touched origin-shape)}]
- uoperations (list {:type :set-touched :touched (:touched dest-shape)})]
+ roperations [{:type :set-touched :touched (:touched previous-shape)}]
+ uoperations (list {:type :set-touched :touched (:touched current-shape)})]
(if-let [attr (first attrs)]
(let [attr-group (get ctk/sync-attrs attr)
+ skip-operations?
+ (or
+ ;; If the attribute is not valid for the destiny, don't copy it
+ (not (cts/is-allowed-attr? attr (:type current-shape)))
+
+ ;; If the values are already equal, don't copy them
+ (= (get previous-shape attr) (get current-shape attr))
+
+ ;; If both variants (origin and destiny) don't have the same value
+ ;; for that attribute, don't copy it.
+ ;; Exceptions: :points :selrect and :content can be different
+ ;;
+ ;; Sample:
+ ;; 1. We have a variant with C1 (bg red) and C2 (bg blue).
+ ;; 2. We make a copy of C1 called Copy.
+ ;; 3. We set Copy’s bg to green (so it it has an override on the bg).
+ ;; 4. We switch Copy to use C2 as base.
+ ;; 5. The bg of Copy now is blue (we ignore the override)
+ (and
+ (not (contains? #{:points :selrect :content} attr))
+ (not= (get origin-ref-shape attr) (get current-shape attr)))
+
+ ;; The :content attr cant't be copied to elements of different type
+ (and (= attr :content) (not= (:type previous-shape) (:type current-shape)))
+
+ ;; If the attr is not touched, don't copy it
+ (not (touched attr-group)))
+
+ ;; On texts, both text (the actual letters)
+ ;; and attrs (bold, font, etc) are in the same attr :content.
+ ;; If only one of them is touched, we want to adress this case and
+ ;; only update the untouched one
+ text-change?
+ (and
+ (not skip-operations?)
+ (cfh/text-shape? current-shape)
+ (cfh/text-shape? previous-shape)
+ (= :content attr)
+ (touched attr-group))
+
+ ;; position-data is a special case because can be affected by :geometry-group and :content-group
+ ;; so, if the position-data changes but the geometry is touched we need to reset the position-data
+ ;; so it's calculated again
+ reset-pos-data? (and
+ (not skip-operations?)
+ (cfh/text-shape? previous-shape)
+ (= attr :position-data)
+ (not= (:position-data previous-shape) (:position-data current-shape))
+ (touched :geometry-group))
+
+ attr-val
+ (when-not skip-operations?
+ (cond
+ ;; If position data changes and the geometry group is touched
+ ;; we need to put to nil so we can regenerate it
+ reset-pos-data?
+ nil
+
+ text-change?
+ (switch-text-change-value (:content previous-shape)
+ (:content current-shape)
+ (:content origin-ref-shape)
+ touched)
+
+ :else
+ (get previous-shape attr)))
+
+ ;; If the final attr-value is the actual value, skip
+ skip-operations? (or skip-operations?
+ (= attr-val (get current-shape attr)))
+
+
+ ;; On a text-change, we want to force a position-data reset
+ ;; so it's calculated again
+ [roperations uoperations]
+ (if (and (not skip-operations?) text-change?)
+ (add-update-attr-operations :position-data current-shape roperations uoperations nil)
+ [roperations uoperations])
+
[roperations' uoperations']
- (if (or
- ;; If the attribute is not valid for the destiny, don't copy it
- (not (cts/is-allowed-attr? attr (:type dest-shape)))
- ;; If the values are already equal, don't copy it
- (= (get origin-shape attr) (get dest-shape attr))
- ;; If the referenced shape on the original component doesn't have the same value, don't copy it
- ;; Exceptions: :points :selrect and :content can be different
- (and
- (not (contains? #{:points :selrect :content} attr))
- (not= (get origin-ref-shape attr) (get dest-shape attr)))
- ;; The :content attr cant't be copied to elements of different type
- (and (= attr :content) (not= (:type origin-shape) (:type dest-shape)))
- ;; If the attr is not touched in the origin shape, don't copy it
- (not (touched-origin attr-group)))
+ (if skip-operations?
[roperations uoperations]
- (add-update-attr-operations attr dest-shape origin-shape roperations uoperations touched))]
+ (add-update-attr-operations attr current-shape roperations uoperations attr-val))]
(recur (next attrs)
roperations'
uoperations'))
(cond-> changes
(> (count roperations) 1)
- (add-update-attr-changes dest-shape container roperations uoperations)
+ (add-update-attr-changes current-shape container roperations uoperations)
:always
- (generate-update-tokens container dest-shape origin-shape touched false))))))
+ (generate-update-tokens container current-shape previous-shape touched false))))))
(defn- propagate-attrs
"Helper that puts the origin attributes (attrs) into dest but only if
@@ -2115,7 +2362,7 @@
(pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values))
;; We need to set the same index as the original shape
- (pcb/change-parent (:parent-id shape) [new-shape] index {:component-swap true
+ (pcb/change-parent (:parent-id shape) [new-shape] index {:allow-altering-copies true
:ignore-touched true})
(change-touched new-shape
shape
@@ -2123,10 +2370,21 @@
{}))]))
(defn generate-component-swap
- [changes objects shape file page libraries id-new-component index target-cell keep-props-values]
- (let [[all-parents changes]
+ [changes objects shape file page libraries id-new-component
+ index target-cell keep-props-values ignore-swapped?]
+ (let [;; When we keep the touched properties, we can't delete the
+ ;; swapped children (we will keep them too)
+ ignore-swapped-fn
+ (if ignore-swapped?
+ #(-> (get objects %)
+ (ctk/get-swap-slot))
+ (constantly false))
+
+ [all-parents changes]
(-> changes
- (cls/generate-delete-shapes file page objects (d/ordered-set (:id shape)) {:component-swap true}))
+ (cls/generate-delete-shapes
+ file page objects (d/ordered-set (:id shape))
+ {:allow-altering-copies true :ignore-children-fn ignore-swapped-fn}))
[new-shape changes]
(-> changes
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]
diff --git a/common/src/app/common/logic/shapes.cljc b/common/src/app/common/logic/shapes.cljc
index f26b9f95a8..6590c7bed8 100644
--- a/common/src/app/common/logic/shapes.cljc
+++ b/common/src/app/common/logic/shapes.cljc
@@ -99,7 +99,14 @@
(pcb/with-library-data file))
ids
options))
- ([changes ids {:keys [ignore-touched component-swap]}]
+ ([changes ids {:keys [ignore-touched
+ allow-altering-copies
+ ;; We will delete the shapes and its descendants.
+ ;; ignore-children-fn is used to ignore some descendants
+ ;; on the deletion process. It should receive a shape and
+ ;; return a boolean
+ ignore-children-fn]
+ :or {ignore-children-fn (constantly false)}}]
(let [objects (pcb/get-objects changes)
data (pcb/get-library-data changes)
page-id (pcb/get-page-id changes)
@@ -112,11 +119,12 @@
;; Look for shapes that are inside a component copy, but are
;; not the root. In this case, they must not be deleted,
;; but hidden (to be able to recover them more easily).
- ;; Unless we are doing a component swap, in which case we want
+ ;; If we want to specifically allow altering the copies, this is
+ ;; a special case, like a component swap, in which case we want
;; to delete the old shape
(let [shape (get objects shape-id)]
(and (ctn/has-any-copy-parent? objects shape)
- (not component-swap))))
+ (not allow-altering-copies))))
[ids-to-delete ids-to-hide]
(loop [ids-seq (seq ids)
@@ -177,10 +185,15 @@
(d/ordered-set)
(concat ids-to-delete ids-to-hide))
- all-children
- (->> ids-to-delete ;; Children of deleted shapes must be also deleted.
+ ;; Descendants of deleted shapes must be also deleted,
+ ;; except the ignored ones by the function ignore-children-fn
+ descendants-to-delete
+ (->> ids-to-delete
(reduce (fn [res id]
- (into res (cfh/get-children-ids objects id)))
+ (into res (cfh/get-children-ids
+ objects
+ id
+ {:ignore-children-fn ignore-children-fn})))
[])
(reverse)
(into (d/ordered-set)))
@@ -200,9 +213,10 @@
empty-parents
;; Any parent whose children are all deleted, must be deleted too.
- ;; Unless we are during a component swap: in this case we are replacing a shape by
+ ;; If we want to specifically allow altering the copies, this is a special case,
+ ;; for example during a component swap. in this case we are replacing a shape by
;; other one, so must not delete empty parents.
- (if-not component-swap
+ (if-not allow-altering-copies
(into (d/ordered-set) (find-all-empty-parents #{}))
#{})
@@ -214,7 +228,7 @@
(conj components (:component-id shape))
components)))
[]
- (into ids-to-delete all-children))
+ (into ids-to-delete descendants-to-delete))
ids-set (set ids-to-delete)
@@ -241,7 +255,7 @@
changes (-> changes
(generate-update-shape-flags ids-to-hide objects {:hidden true})
- (pcb/remove-objects all-children {:ignore-touched true})
+ (pcb/remove-objects descendants-to-delete {:ignore-touched true})
(pcb/remove-objects ids-to-delete {:ignore-touched ignore-touched})
(pcb/remove-objects empty-parents)
(pcb/resize-parents all-parents)
diff --git a/common/src/app/common/logic/tokens.cljc b/common/src/app/common/logic/tokens.cljc
index 5121df8764..b594d3c0aa 100644
--- a/common/src/app/common/logic/tokens.cljc
+++ b/common/src/app/common/logic/tokens.cljc
@@ -41,7 +41,7 @@
[group-path tokens-lib tokens-lib-theme]
(let [deactivate? (contains? #{:all :partial} (ctob/sets-at-path-all-active? tokens-lib group-path))
sets-names (->> (ctob/get-sets-at-path tokens-lib group-path)
- (map :name)
+ (map ctob/get-name)
(into #{}))]
(if deactivate?
(ctob/disable-sets tokens-lib-theme sets-names)
diff --git a/common/src/app/common/logic/variant_properties.cljc b/common/src/app/common/logic/variant_properties.cljc
index 43938415c9..4a692702d9 100644
--- a/common/src/app/common/logic/variant_properties.cljc
+++ b/common/src/app/common/logic/variant_properties.cljc
@@ -18,7 +18,12 @@
[changes variant-id pos new-name]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
- related-components (cfv/find-variant-components data objects variant-id)]
+ related-components (cfv/find-variant-components data objects variant-id)
+
+ props (-> related-components last :variant-properties)
+ prop-names (mapv :name props)
+ prop-names (concat (subvec prop-names 0 pos) (subvec prop-names (inc pos)))
+ new-name (ctv/update-number-in-repeated-item prop-names new-name)]
(reduce (fn [changes component]
(pcb/update-component
changes (:id component)
@@ -81,6 +86,9 @@
next-prop-num (ctv/next-property-number props)
property-name (or property-name (str ctv/property-prefix next-prop-num))
+ prop-names (mapv :name props)
+ property-name (ctv/update-number-in-repeated-item prop-names property-name)
+
[_ changes]
(reduce (fn [[num changes] component]
(let [main-id (:main-instance-id component)
diff --git a/common/src/app/common/logic/variants.cljc b/common/src/app/common/logic/variants.cljc
index fce96410d4..12238a1b73 100644
--- a/common/src/app/common/logic/variants.cljc
+++ b/common/src/app/common/logic/variants.cljc
@@ -1,13 +1,17 @@
(ns app.common.logic.variants
(:require
+ [app.common.data :as d]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.variant :as cfv]
[app.common.logic.libraries :as cll]
+ [app.common.logic.shapes :as cls]
[app.common.logic.variant-properties :as clvp]
+ [app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
- [app.common.types.variant :as ctv]))
+ [app.common.types.variant :as ctv]
+ [app.common.uuid :as uuid]))
(defn generate-add-new-variant
[changes shape variant-id new-component-id new-shape-id prop-num]
@@ -62,29 +66,142 @@
shapes))))
+(defn- keep-swapped-item
+ "As part of the keep-touched process on a switch, given a child on the original
+ copy that was swapped (orig-swapped-child), and its related shape on the new copy
+ (related-shape-in-new), move the orig-swapped-child into the parent of
+ related-shape-in-new, fix its swap-slot if needed, and then delete
+ related-shape-in-new"
+ [changes related-shape-in-new orig-swapped-child ldata page swap-ref-id]
+ (let [;; Before to the swap, temporary move the previous
+ ;; shape to the root panel to avoid problems when
+ ;; the previous parent is deleted.
+ before-changes (-> (pcb/empty-changes)
+ (pcb/with-page page)
+ (pcb/with-objects (:objects page))
+ (pcb/change-parent uuid/zero [orig-swapped-child] 0 {:allow-altering-copies true}))
+
+ objects (pcb/get-objects changes)
+ prev-swap-slot (ctk/get-swap-slot orig-swapped-child)
+ current-parent (get objects (:parent-id related-shape-in-new))
+ pos (d/index-of (:shapes current-parent) (:id related-shape-in-new))]
+
+
+ (-> (pcb/concat-changes before-changes changes)
+
+ ;; Move the previous shape to the new parent
+ (pcb/change-parent (:parent-id related-shape-in-new) [orig-swapped-child] pos {:allow-altering-copies true})
+
+ ;; We need to update the swap slot only when it pointed
+ ;; to the swap-ref-id. Oterwise this is a swapped item
+ ;; inside a nested copy, so we need to keep it.
+ (cond->
+ (= prev-swap-slot swap-ref-id)
+ (pcb/update-shapes
+ [(:id orig-swapped-child)]
+ #(ctk/set-swap-slot % (:shape-ref related-shape-in-new))))
+
+ ;; Delete new non-swapped item
+ (cls/generate-delete-shapes ldata page objects (d/ordered-set (:id related-shape-in-new)) {:allow-altering-copies true})
+ second)))
+
+(defn- child-of-swapped?
+ "Check if any ancestor of a shape (between base-parent-id and shape) was swapped"
+ [shape objects base-parent-id]
+ (let [ancestors (->> (ctn/get-parent-heads objects shape)
+ ;; Ignore ancestors ahead of base-parent
+ (drop-while #(not= base-parent-id (:id %)))
+ seq)
+ num-ancestors (count ancestors)
+ ;; Ignore first and last (base-parent and shape)
+ ancestors (when (and ancestors (<= 3 num-ancestors))
+ (subvec (vec ancestors) 1 (dec num-ancestors)))]
+ (some ctk/get-swap-slot ancestors)))
+
+
(defn generate-keep-touched
- [changes new-shape original-shape original-shapes page libraries]
+ "This is used as part of the switch process, when you switch from
+ an original-shape to a new-shape. It generate changes to
+ copy the touched attributes on the shapes children of the original-shape
+ into the related children of the new-shape.
+ This relation is tricky. The shapes are related if:
+ * On the main components, both have the same name (the name on the copies are ignored)
+ * Both has the same type of ancestors, on the same order (see generate-path for the
+ translation of the types)"
+ [changes new-shape original-shape original-shapes page libraries ldata]
(let [objects (pcb/get-objects changes)
- orig-objects (into {} (map (juxt :id identity) original-shapes))
- orig-shapes-w-path (add-unique-path
- (reverse original-shapes)
- orig-objects
- (:id original-shape))
+ container (ctn/make-container page :page)
+ page-objects (:objects page)
+
+ ;; Get the touched children of the original-shape
+ ;; Ignore children of swapped items, because
+ ;; they will be moved without change when
+ ;; managing their swapped ancestor
+ orig-touched (->> (filter (comp seq :touched) original-shapes)
+ (remove
+ #(child-of-swapped? %
+ page-objects
+ (:id original-shape))))
+
+ ;; Adds a :shape-path attribute to the children of the new-shape,
+ ;; that contains the type of its ancestors and its name
new-shapes-w-path (add-unique-path
(reverse (cfh/get-children-with-self objects (:id new-shape)))
objects
(:id new-shape))
- new-shapes-map (into {} (map (juxt :shape-path identity) new-shapes-w-path))
- orig-touched (filter (comp seq :touched) orig-shapes-w-path)
+ ;; Creates a map to quickly find a child of the new-shape by its shape-path
+ new-shapes-map (into {} (map (juxt :shape-path identity)) new-shapes-w-path)
- container (ctn/make-container page :page)]
+ ;; The original-shape is in a copy. For the relation rules, we need the referenced
+ ;; shape on the main component
+ orig-ref-shape (ctf/find-ref-shape nil container libraries original-shape)
+
+ orig-ref-objects (-> (ctf/get-component-container-from-head orig-ref-shape libraries)
+ :objects)
+
+ ;; Adds a :shape-path attribute to the children of the orig-ref-shape,
+ ;; that contains the type of its ancestors and its name
+ o-ref-shapes-wp (add-unique-path
+ (reverse (cfh/get-children-with-self orig-ref-objects (:id orig-ref-shape)))
+ orig-ref-objects
+ (:id orig-ref-shape))
+
+ ;; Creates a map to quickly find a child of the orig-ref-shape by its shape-path
+ o-ref-shapes-p-map (into {} (map (juxt :id :shape-path)) o-ref-shapes-wp)]
+ ;; Process each touched children of the original-shape
(reduce
- (fn [changes touched-shape]
- (let [related-shape (get new-shapes-map (:shape-path touched-shape))
- orig-ref-shape (ctf/find-ref-shape nil container libraries touched-shape)]
- (if related-shape
- (cll/update-attrs-on-switch
- changes related-shape touched-shape new-shape original-shape orig-ref-shape container)
+ (fn [changes orig-child-touched]
+ (let [;; If the orig-child-touched was swapped, get its swap-slot
+ swap-slot (ctk/get-swap-slot orig-child-touched)
+
+ ;; orig-child-touched is in a copy. Get the referenced shape on the main component
+ ;; If there is a swap slot, we will get the referenced shape in another way
+ orig-ref-shape (when-not swap-slot
+ ;; TODO Maybe just get it from o-ref-shapes-wp
+ (ctf/find-ref-shape nil container libraries orig-child-touched))
+
+ orig-ref-id (if swap-slot
+ ;; If there is a swap slot, find the referenced shape id
+ (ctf/find-ref-id-for-swapped orig-child-touched container libraries)
+ ;; If there is not a swap slot, get the id from the orig-ref-shape
+ (:id orig-ref-shape))
+
+ ;; Get the shape path of the referenced main
+ shape-path (get o-ref-shapes-p-map orig-ref-id)
+ ;; Get its related shape in the children of new-shape: the one that
+ ;; has the same shape-path
+ related-shape-in-new (get new-shapes-map shape-path)]
+ ;; If there is a related shape, keep its data
+ (if related-shape-in-new
+ (if swap-slot
+ ;; If the orig-child-touched was swapped, keep it
+ (keep-swapped-item changes related-shape-in-new orig-child-touched
+ ldata page orig-ref-id)
+ ;; If the orig-child-touched wasn't swapped, copy
+ ;; the touched attributes into it
+ (cll/update-attrs-on-switch
+ changes related-shape-in-new orig-child-touched
+ new-shape original-shape orig-ref-shape container))
changes)))
changes
orig-touched)))
diff --git a/common/src/app/common/test_helpers/components.cljc b/common/src/app/common/test_helpers/components.cljc
index 687be91871..4e82acd647 100644
--- a/common/src/app/common/test_helpers/components.cljc
+++ b/common/src/app/common/test_helpers/components.cljc
@@ -156,7 +156,7 @@
[new_shape _ changes]
(-> (pcb/empty-changes nil (:id page))
- (cll/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values))
+ (cll/generate-component-swap objects shape (:data file) page libraries id-new-component 0 nil keep-props-values false))
file' (thf/apply-changes file changes)]
diff --git a/common/src/app/common/test_helpers/compositions.cljc b/common/src/app/common/test_helpers/compositions.cljc
index ca6bd064ba..bf7c64ae51 100644
--- a/common/src/app/common/test_helpers/compositions.cljc
+++ b/common/src/app/common/test_helpers/compositions.cljc
@@ -8,11 +8,14 @@
(:require
[app.common.data :as d]
[app.common.files.changes-builder :as pcb]
+ [app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
+ [app.common.logic.variants :as clv]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.files :as thf]
+ [app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.text :as txt]
[app.common.types.container :as ctn]
@@ -275,25 +278,36 @@
(defn swap-component
"Swap the specified shape by the component specified by component-tag"
- [file shape component-tag & {:keys [page-label propagate-fn]}]
+ [file shape component-tag & {:keys [page-label propagate-fn keep-touched? new-shape-label]}]
(let [page (if page-label
(thf/get-page file page-label)
(thf/current-page file))
+ libraries {(:id file) file}
- [_ _all-parents changes]
+ orig-shapes (when keep-touched? (cfh/get-children-with-self (:objects page) (:id shape)))
+
+ [new-shape _all-parents changes]
(cll/generate-component-swap (pcb/empty-changes)
(:objects page)
shape
(:data file)
page
- {(:id file) file}
+ libraries
(-> (thc/get-component file component-tag)
:id)
0
nil
- {})
+ {}
+ (true? keep-touched?))
+
+ changes (if keep-touched?
+ (clv/generate-keep-touched changes new-shape shape orig-shapes page libraries (:data file))
+ changes)
+
file' (thf/apply-changes file changes)]
+ (when new-shape-label
+ (thi/set-id! new-shape-label (:id new-shape)))
(if propagate-fn
(propagate-fn file')
file')))
diff --git a/common/src/app/common/test_helpers/variants.cljc b/common/src/app/common/test_helpers/variants.cljc
index e9a76d0628..7b5d928798 100644
--- a/common/src/app/common/test_helpers/variants.cljc
+++ b/common/src/app/common/test_helpers/variants.cljc
@@ -8,7 +8,9 @@
(:require
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.ids-map :as thi]
- [app.common.test-helpers.shapes :as ths]))
+ [app.common.test-helpers.shapes :as ths]
+ [app.common.text :as txt]
+ [app.common.types.shape :as cts]))
(defn add-variant
[file variant-label component1-label root1-label component2-label root2-label
@@ -37,3 +39,48 @@
(thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "p1v1"} {:name "Property2" :value "p2v1"}]})
(thc/make-component component2-label root2-label)
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "p1v2"} {:name "Property2" :value "p2v2"}]}))))
+
+(defn add-variant-with-child
+ [file variant-label component1-label root1-label component2-label root2-label child1-label child2-label
+ & {:keys [child1-params child2-params]}]
+ (let [file (ths/add-sample-shape file variant-label :type :frame :is-variant-container true)
+ variant-id (thi/id variant-label)]
+ (-> file
+ (ths/add-sample-shape root2-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value2")
+ (ths/add-sample-shape root1-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value1")
+ (ths/add-sample-shape child1-label (assoc child1-params :parent-label root1-label))
+ (ths/add-sample-shape child2-label (assoc child2-params :parent-label root2-label))
+ (thc/make-component component1-label root1-label)
+ (thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value1"}]})
+ (thc/make-component component2-label root2-label)
+ (thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value2"}]}))))
+
+
+(defn add-variant-with-text
+ [file variant-label component1-label root1-label component2-label root2-label child1-label child2-label text1 text2
+ & {:keys [text1-params text2-params]}]
+ (let [text1 (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
+ (txt/change-text text1)
+ (assoc :position-data nil
+ :parent-label root1-label))
+ text2 (-> (cts/setup-shape {:type :text :x 0 :y 0 :grow-type :auto-width})
+ (txt/change-text text2)
+ (assoc :position-data nil
+ :parent-label root2-label))
+
+ file (ths/add-sample-shape file variant-label :type :frame :is-variant-container true)
+ variant-id (thi/id variant-label)]
+ (-> file
+
+ (ths/add-sample-shape root2-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value2")
+ (ths/add-sample-shape root1-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value1")
+ (ths/add-sample-shape child1-label
+ (merge text1
+ text1-params))
+ (ths/add-sample-shape child2-label
+ (merge text2
+ text2-params))
+ (thc/make-component component1-label root1-label)
+ (thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value1"}]})
+ (thc/make-component component2-label root2-label)
+ (thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value2"}]}))))
diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc
index 395e421315..89e210d871 100644
--- a/common/src/app/common/types/container.cljc
+++ b/common/src/app/common/types/container.cljc
@@ -18,6 +18,7 @@
[app.common.types.plugins :as ctpg]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
+ [app.common.types.text :as cttx]
[app.common.types.token :as ctt]
[app.common.uuid :as uuid]
[clojure.set :as set]))
@@ -293,8 +294,8 @@
([page component library-data position]
(make-component-instance page component library-data position {}))
([page component library-data position
- {:keys [main-instance? force-id force-frame-id keep-ids?]
- :or {main-instance? false force-id nil force-frame-id nil keep-ids? false}}]
+ {:keys [main-instance? force-id force-frame-id keep-ids? force-parent-id]
+ :or {main-instance? false force-id nil force-frame-id nil keep-ids? false force-parent-id nil}}]
(let [component-page (ctpl/get-page library-data (:main-instance-page component))
component-shape (-> (get-shape component-page (:main-instance-id component))
@@ -302,7 +303,6 @@
(assoc :frame-id uuid/zero)
(remove-swap-keep-attrs))
-
orig-pos (gpt/point (:x component-shape) (:y component-shape))
delta (gpt/subtract position orig-pos)
@@ -367,7 +367,7 @@
[new-shape new-shapes _]
(ctst/clone-shape component-shape
- frame-id
+ (or force-parent-id frame-id)
(:objects component-page)
:update-new-shape update-new-shape
:force-id force-id
@@ -569,13 +569,16 @@
(not equal?)
(not (and ignore-geometry is-geometry?)))
+ content-diff-type (when (and (= (:type shape) :text) (= attr :content))
+ (cttx/get-diff-type (:content shape) val))
+
token-groups (if (= attr :applied-tokens)
(get-token-groups shape val)
#{})
groups (cond-> token-groups
(and group (not equal?))
- (set/union #{group}))]
+ (set/union #{group} content-diff-type))]
(cond-> shape
;; Depending on the origin of the attribute change, we need or not to
;; set the "touched" flag for the group the attribute belongs to.
diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc
index f4ecbd4acc..521915da96 100644
--- a/common/src/app/common/types/file.cljc
+++ b/common/src/app/common/types/file.cljc
@@ -242,6 +242,13 @@
(cfh/make-container component-page :page))
(cfh/make-container component :component)))
+(defn get-component-container-from-head
+ [instance-head libraries & {:keys [include-deleted?] :or {include-deleted? true}}]
+ (let [library-data (-> (get-component-library libraries instance-head)
+ :data)
+ component (ctkl/get-component library-data (:component-id instance-head) include-deleted?)]
+ (get-component-container library-data component)))
+
(defn get-component-root
"Retrieve the root shape of the component."
[file-data component]
@@ -390,6 +397,47 @@
(or (= slot-main slot-inst)
(= (:id shape-main) slot-inst)))))
+(defn- find-next-related-swap-shape-id
+ "Go up from the chain of references shapes that will eventually lead to the shape
+ with swap-slot-id as id. Returns the next shape on the chain"
+ [parent swap-slot-id libraries]
+ (let [container (get-component-container-from-head parent libraries)
+ objects (:objects container)
+
+ children (cfh/get-children objects (:id parent))
+ original-shape-id (->> children
+ (filter #(= swap-slot-id (:id %)))
+ first
+ :id)]
+ (if original-shape-id
+ ;; Return the children which id is the swap-slot-id
+ original-shape-id
+ ;; No children with swap-slot-id as id, go up
+ (let [referenced-shape (find-ref-shape nil container libraries parent)
+ ;; Recursive call that will get the id of the next shape on
+ ;; the chain that ends on a shape with swap-slot-id as id
+ next-shape-id (when referenced-shape
+ (find-next-related-swap-shape-id referenced-shape swap-slot-id libraries))]
+ ;; Return the children which shape-ref points to the next-shape-id
+ (->> children
+ (filter #(= next-shape-id (:shape-ref %)))
+ first
+ :id)))))
+
+(defn find-ref-id-for-swapped
+ "When a shape has been swapped, find the original ref-id that the shape had
+ before the swap"
+ [shape container libraries]
+ (let [swap-slot (ctk/get-swap-slot shape)
+ objects (:objects container)
+
+ parent (get objects (:parent-id shape))
+ parent-head (ctn/get-head-shape objects parent)
+ parent-ref (find-ref-shape nil container libraries parent-head)]
+
+ (when (and swap-slot parent-ref)
+ (find-next-related-swap-shape-id parent-ref swap-slot libraries))))
+
(defn get-component-shapes
"Retrieve all shapes of the component"
[file-data component]
diff --git a/common/src/app/common/types/text.cljc b/common/src/app/common/types/text.cljc
index 927c2ca36f..efc1df0bad 100644
--- a/common/src/app/common/types/text.cljc
+++ b/common/src/app/common/types/text.cljc
@@ -127,7 +127,8 @@
entries"
[a b]
(cond
- (not= (type a) (type b))
+ (and (not= (type a) (type b))
+ (not (and (map? a) (map? b)))) ;; Sometimes they are both maps but of different subtypes
false
(map? a)
@@ -148,7 +149,7 @@
(cond
(map? origin)
(into {}
- (for [k (keys origin) :when (not= k :key)] ;; We ignore :key because it is a draft artifact
+ (for [k (keys destiny) :when (not= k :key)] ;; We ignore :key because it is a draft artifact
(cond
(= :children k)
[k (vec (map #(copy-text-keys %1 %2) (get origin k) (get destiny k)))]
diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc
index b92b691acc..d3c1f27657 100644
--- a/common/src/app/common/types/token.cljc
+++ b/common/src/app/common/types/token.cljc
@@ -33,6 +33,8 @@
:border-radius "borderRadius"
:color "color"
:dimensions "dimension"
+ :font-size "fontSizes"
+ :letter-spacing "letterSpacing"
:number "number"
:opacity "opacity"
:other "other"
@@ -101,18 +103,15 @@
[:m1 {:optional true} token-name-ref]
[:m2 {:optional true} token-name-ref]
[:m3 {:optional true} token-name-ref]
- [:m4 {:optional true} token-name-ref]
- [:x {:optional true} token-name-ref]
- [:y {:optional true} token-name-ref]])
+ [:m4 {:optional true} token-name-ref]])
(def spacing-keys (schema-keys schema:spacing))
(def ^:private schema:dimensions
- [:merge
- schema:sizing
- schema:spacing
- schema:stroke-width
- schema:border-radius])
+ (reduce mu/union [schema:sizing
+ schema:spacing
+ schema:stroke-width
+ schema:border-radius]))
(def dimensions-keys (schema-keys schema:dimensions))
@@ -122,10 +121,27 @@
(def rotation-keys (schema-keys schema:rotation))
-(def ^:private schema:number
+(def ^:private schema:font-size
[:map
- [:rotation {:optional true} token-name-ref]
- [:line-height {:optional true} token-name-ref]])
+ [:font-size {:optional true} token-name-ref]])
+
+(def font-size-keys (schema-keys schema:font-size))
+
+(def ^:private schema:letter-spacing
+ [:map
+ [:letter-spacing {:optional true} token-name-ref]])
+
+(def letter-spacing-keys (schema-keys schema:letter-spacing))
+
+(def typography-keys (set/union font-size-keys letter-spacing-keys))
+
+;; TODO: Created to extract the font-size feature from the typography feature flag.
+;; Delete this once the typography feature flag is removed.
+(def ff-typography-keys (set/difference typography-keys font-size-keys))
+
+(def ^:private schema:number
+ (reduce mu/union [[:map [:line-height {:optional true} token-name-ref]]
+ schema:rotation]))
(def number-keys (schema-keys schema:number))
@@ -137,6 +153,7 @@
spacing-keys
dimensions-keys
rotation-keys
+ typography-keys
number-keys))
(def ^:private schema:tokens
@@ -150,6 +167,8 @@
schema:spacing
schema:rotation
schema:number
+ schema:font-size
+ schema:letter-spacing
schema:dimensions])
(defn shape-attr->token-attrs
@@ -177,6 +196,8 @@
changed-sub-attr
#{:m1 :m2 :m3 :m4})
+ (font-size-keys shape-attr) #{shape-attr}
+ (letter-spacing-keys shape-attr) #{shape-attr}
(border-radius-keys shape-attr) #{shape-attr}
(sizing-keys shape-attr) #{shape-attr}
(opacity-keys shape-attr) #{shape-attr}
@@ -192,6 +213,56 @@
:stroke-width :strokes
token-attr))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; TOKEN SHAPE ATTRIBUTES
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(def position-attributes #{:x :y})
+
+(def generic-attributes
+ (set/union color-keys
+ stroke-width-keys
+ rotation-keys
+ sizing-keys
+ opacity-keys
+ position-attributes))
+
+(def rect-attributes
+ (set/union generic-attributes
+ border-radius-keys))
+
+(def frame-attributes
+ (set/union rect-attributes
+ spacing-keys))
+
+(def text-attributes
+ (set/union generic-attributes
+ typography-keys
+ number-keys))
+
+(defn shape-type->attributes
+ [type]
+ (case type
+ :bool generic-attributes
+ :circle generic-attributes
+ :rect rect-attributes
+ :frame frame-attributes
+ :image rect-attributes
+ :path generic-attributes
+ :svg-raw generic-attributes
+ :text text-attributes
+ nil))
+
+(defn appliable-attrs
+ "Returns intersection of shape `attributes` for `token-type`."
+ [attributes token-type]
+ (set/intersection attributes (shape-type->attributes token-type)))
+
+(defn any-appliable-attr?
+ "Checks if `token-type` supports given shape `attributes`."
+ [attributes token-type]
+ (seq (appliable-attrs attributes token-type)))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TOKENS IN SHAPES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -218,13 +289,5 @@
:attributes attributes})]
(update shape :applied-tokens #(merge % applied-tokens))))
-(defn maybe-apply-token-to-shape
- "When the passed `:token` is non-nil apply it to the `:applied-tokens` on a shape."
- [{:keys [shape token _attributes] :as props}]
- (if token
- (apply-token-to-shape props)
- shape))
-
(defn unapply-token-id [shape attributes]
(update shape :applied-tokens d/without-keys attributes))
-
diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc
index dd64992312..ecfd92b5da 100644
--- a/common/src/app/common/types/tokens_lib.cljc
+++ b/common/src/app/common/types/tokens_lib.cljc
@@ -17,6 +17,7 @@
[app.common.transit :as t]
[app.common.types.token :as cto]
[app.common.uuid :as uuid]
+ [clojure.core.protocols :as protocols]
[clojure.set :as set]
[clojure.walk :as walk]
[cuerdas.core :as str]))
@@ -25,13 +26,6 @@
;; TODO: add again the removed functions and refactor the rest of the module to use them
-(def ^:private schema:groupable-item
- [:map {:title "Groupable item"}
- [:name :string]])
-
-(def ^:private valid-groupable-item?
- (sm/validator schema:groupable-item))
-
(def ^:private xf-map-trim
(comp
(map str/trim)
@@ -60,14 +54,38 @@
(defn get-path
"Get the path of object by specified separator (E.g. with '.' separator, the
'group.subgroup.name' -> ['group' 'subgroup'])"
- [item separator]
- (assert (valid-groupable-item? item) "expected groupable item")
- (->> (split-path (:name item) separator)
+ [name separator]
+ (->> (split-path name separator)
(not-empty)))
+;; === Common
+
+(defprotocol INamedItem
+ "Protocol for items that have a name, a description and a modified date."
+ (get-name [_] "Get the name of the item.")
+ (get-description [_] "Get the description of the item.")
+ (get-modified-at [_] "Get the description of the item.")
+ (rename [_ new-name] "Set the name of the item.")
+ (set-description [_ new-description] "Set the description of the item."))
+
;; === Token
-(defrecord Token [id name type value description modified-at])
+(defrecord Token [id name type value description modified-at]
+ INamedItem
+ (get-name [_]
+ name)
+
+ (get-description [_]
+ description)
+
+ (get-modified-at [_]
+ modified-at)
+
+ (rename [this new-name]
+ (assoc this :name new-name))
+
+ (set-description [this new-description]
+ (assoc this :description new-description)))
(defn token?
[o]
@@ -109,7 +127,7 @@
(defn get-token-path
[token]
- (get-path token token-separator))
+ (get-path (:name token) token-separator))
(defn find-token-value-references
"Returns set of token references found in `token-value`.
@@ -146,9 +164,53 @@
(update-token [_ token-name f] "update a token in the list")
(delete-token [_ token-name] "delete a token from the list")
(get-token [_ token-name] "return token by token-name")
- (get-tokens [_] "return an ordered sequence of all tokens in the set"))
+ (get-tokens [_] "return an ordered sequence of all tokens in the set")
+ (get-tokens-map [_] "return a map of tokens in the set, indexed by token-name"))
+
+(deftype TokenSet [id name description modified-at tokens]
+ #?@(:clj [clojure.lang.IDeref
+ (deref [_] {:id id
+ :name name
+ :description description
+ :modified-at modified-at
+ :tokens tokens})]
+ :cljs [cljs.core/IDeref
+ (-deref [_] {:id id
+ :name name
+ :description description
+ :modified-at modified-at
+ :tokens tokens})])
+
+ #?@(:cljs [cljs.core/IEncodeJS
+ (-clj->js [_] (js-obj "id" (clj->js id)
+ "name" (clj->js name)
+ "description" (clj->js description)
+ "modified-at" (clj->js modified-at)
+ "tokens" (clj->js tokens)))])
+ INamedItem
+ (get-name [_]
+ name)
+
+ (get-description [_]
+ description)
+
+ (get-modified-at [_]
+ modified-at)
+
+ (rename [_ new-name]
+ (TokenSet. id
+ new-name
+ description
+ (dt/now)
+ tokens))
+
+ (set-description [_ new-description]
+ (TokenSet. id
+ name
+ (d/nilv new-description "")
+ (dt/now)
+ tokens))
-(defrecord TokenSet [id name description modified-at tokens]
ITokenSet
(add-token [_ token]
(let [token (check-token token)]
@@ -184,7 +246,10 @@
(get tokens token-name))
(get-tokens [_]
- (vals tokens)))
+ (vals tokens))
+
+ (get-tokens-map [_]
+ tokens))
(defn token-set?
[o]
@@ -218,10 +283,7 @@
(declare make-token-set)
(def schema:token-set
- [:and {:gen/gen (->> (sg/generator schema:token-set-attrs)
- (sg/fmap #(make-token-set %)))}
- (sm/required-keys schema:token-set-attrs)
- [:fn token-set?]])
+ (sm/required-keys schema:token-set-attrs))
(sm/register! ::token-set schema:token-set) ;; need to register for the recursive schema of token-sets
@@ -233,13 +295,17 @@
(defn make-token-set
[& {:as attrs}]
- (-> attrs
- (update :id #(or % (uuid/next)))
- (update :modified-at #(or % (dt/now)))
- (update :tokens #(into (d/ordered-map) %))
- (update :description d/nilv "")
- (check-token-set-attrs)
- (map->TokenSet)))
+ (let [attrs (-> attrs
+ (update :id #(or % (uuid/next)))
+ (update :modified-at #(or % (dt/now)))
+ (update :tokens #(into (d/ordered-map) %))
+ (update :description d/nilv "")
+ (check-token-set-attrs))]
+ (TokenSet. (:id attrs)
+ (:name attrs)
+ (:description attrs)
+ (:modified-at attrs)
+ (:tokens attrs))))
(def ^:private set-prefix "S-")
@@ -291,7 +357,7 @@
(defn get-set-path
[token-set]
- (get-path token-set set-separator))
+ (get-path (get-name token-set) set-separator))
(defn split-set-name
[name]
@@ -315,7 +381,7 @@
(set-full-path->set-prefixed-full-path)))
(defn get-set-prefixed-path [token-set]
- (let [path (get-path token-set set-separator)]
+ (let [path (get-path (get-name token-set) set-separator)]
(set-full-path->set-prefixed-full-path path)))
(defn prefixed-set-path-string->set-name-string [path-str]
@@ -333,7 +399,7 @@
(conj name)))
(defn tokens-tree
- "Convert tokens into a nested tree with their `:name` as the path.
+ "Convert tokens into a nested tree with their name as the path.
Optionally use `update-token-fn` option to transform the token."
[tokens & {:keys [update-token-fn]
:or {update-token-fn identity}}]
@@ -343,7 +409,7 @@
{} tokens))
(defn backtrace-tokens-tree
- "Convert tokens into a nested tree with their `:name` as the path.
+ "Convert tokens into a nested tree with their name as the path.
Generates a uuid per token to backtrace a token from an external source (StyleDictionary).
The backtrace can't be the name as the name might not exist when the user is creating a token."
[tokens]
@@ -392,7 +458,7 @@
(get-set [_ set-name] "get one set looking for name"))
(def schema:token-set-node
- [:schema {:registry {::node [:or ::token-set
+ [:schema {:registry {::node [:or [:fn token-set?]
[:and
[:map-of {:gen/max 5} :string [:ref ::node]]
[:fn d/ordered-map?]]]}}
@@ -443,6 +509,22 @@
(hidden-theme? [_] "if a theme is the (from the user ui) hidden temporary theme"))
(defrecord TokenTheme [id name group description is-source external-id modified-at sets]
+ INamedItem
+ (get-name [_]
+ name)
+
+ (get-description [_]
+ description)
+
+ (get-modified-at [_]
+ modified-at)
+
+ (rename [this new-name]
+ (assoc this :name new-name))
+
+ (set-description [this new-description]
+ (assoc this :description new-description))
+
ITokenTheme
(set-sets [_ set-names]
(TokenTheme. id
@@ -528,13 +610,17 @@
(defn make-token-theme
[& {:as attrs}]
- (let [id (uuid/next)]
+ (let [new-id (uuid/next)]
(-> attrs
- (update :id d/nilv id)
+ (update :id (fn [id]
+ (-> (if (string? id) ;; TODO: probably this may be deleted in some time, when we may be sure
+ (uuid/parse* id) ;; that no file exists that has not been correctly migrated to
+ id) ;; convert :id into :external-id
+ (d/nilv new-id))))
(update :group d/nilv top-level-theme-group-name)
(update :description d/nilv "")
(update :is-source d/nilv false)
- (update :external-id #(or % (str id)))
+ (update :external-id #(or % (str new-id)))
(update :modified-at #(or % (dt/now)))
(update :sets set)
(check-token-theme-attrs)
@@ -618,7 +704,7 @@
;; Set
(and v (instance? TokenSet v))
[{:group? false
- :path (split-set-name (:name v))
+ :path (split-set-name (get-name v))
:parent-path parent
:depth depth
:set v}]
@@ -664,7 +750,7 @@
;; Set
(and v (instance? TokenSet v))
- (let [name (:name v)]
+ (let [name (get-name v)]
[{:is-group false
:path (split-set-name name)
:id name
@@ -725,8 +811,14 @@ Will return a value that matches this schema:
(declare export-dtcg-json)
(deftype TokensLib [sets themes active-themes]
- ;; NOTE: This is only for debug purposes, pending to properly
- ;; implement the toString and alternative printing.
+ ;; This is to convert the TokensLib to a plain map, for debugging or unit tests.
+ protocols/Datafiable
+ (datafy [_]
+ {:sets (d/update-vals sets deref)
+ :themes themes
+ :active-themes active-themes})
+
+ ;; TODO: this is used in serialization, but there should be a better way to do it
#?@(:clj [clojure.lang.IDeref
(deref [_] {:sets sets
:themes themes
@@ -746,8 +838,8 @@ Will return a value that matches this schema:
ITokenSets
(add-set [_ token-set]
- (let [path (get-set-prefixed-path token-set)
- token-set (check-token-set token-set)]
+ (assert (token-set? token-set) "expected valid token-set")
+ (let [path (get-set-prefixed-path token-set)]
(TokensLib. (d/oassoc-in sets path token-set)
themes
active-themes)))
@@ -756,10 +848,9 @@ Will return a value that matches this schema:
(let [prefixed-full-path (set-name->prefixed-full-path set-name)
set (get-in sets prefixed-full-path)]
(if set
- (let [set' (-> (make-token-set (f set))
- (assoc :modified-at (dt/now)))
+ (let [set' (f set)
prefixed-full-path' (get-set-prefixed-path set')
- name-changed? (not= (:name set) (:name set'))]
+ name-changed? (not= (get-name set) (get-name set'))]
(if name-changed?
(TokensLib. (-> sets
(d/oassoc-in-before prefixed-full-path prefixed-full-path' set')
@@ -767,7 +858,7 @@ Will return a value that matches this schema:
(walk/postwalk
(fn [form]
(if (instance? TokenTheme form)
- (update-set-name form (:name set) (:name set'))
+ (update-set-name form (get-name set) (get-name set'))
form))
themes)
active-themes)
@@ -791,7 +882,7 @@ Will return a value that matches this schema:
(let [path (split-set-name set-group-name)
prefixed-path (map add-set-group-prefix path)
child-set-names (->> (get-sets-at-path this path)
- (map :name)
+ (map get-name)
(into #{}))]
(TokensLib. (d/dissoc-in sets prefixed-path)
(walk/postwalk
@@ -833,7 +924,7 @@ Will return a value that matches this schema:
(set-full-path->set-prefixed-full-path before-path)))
set
- (assoc prev-set :name (join-set-path to-path))
+ (rename prev-set (join-set-path to-path))
reorder?
(= prefixed-from-path prefixed-to-path)
@@ -856,7 +947,7 @@ Will return a value that matches this schema:
(walk/postwalk
(fn [form]
(if (instance? TokenTheme form)
- (update-set-name form (:name prev-set) (:name set))
+ (update-set-name form (get-name prev-set) (get-name set))
form))
themes))
active-themes))
@@ -888,15 +979,15 @@ Will return a value that matches this schema:
(d/oupdate-in prefixed-to-path (fn [sets]
(walk/prewalk
(fn [form]
- (if (instance? TokenSet form)
- (update form :name #(str to-path-str (str/strip-prefix % from-path-str)))
+ (if (token-set? form)
+ (rename form (str to-path-str (str/strip-prefix (get-name form) from-path-str)))
form))
sets)))))
themes' (if reorder?
themes
(let [rename-sets-map (->> (get-sets-at-path this from-path)
(map (fn [set]
- [(:name set) (str to-path-str (str/strip-prefix (:name set) from-path-str))]))
+ [(get-name set) (str to-path-str (str/strip-prefix (get-name set) from-path-str))]))
(into {}))]
(walk/postwalk
(fn [form]
@@ -934,12 +1025,12 @@ Will return a value that matches this schema:
sets (get-sets-at-path this path)]
(reduce
(fn [lib set]
- (update-set lib (:name set) (fn [set']
- (update set' :name #(str to-path-str (str/strip-prefix % from-path-str))))))
+ (update-set lib (get-name set) (fn [set']
+ (rename set' (str to-path-str (str/strip-prefix (get-name set') from-path-str))))))
this sets)))
(get-ordered-set-names [this]
- (map :name (get-sets this)))
+ (map get-name (get-sets this)))
(set-count [this]
(count (get-sets this)))
@@ -1080,7 +1171,7 @@ Will return a value that matches this schema:
prefixed-path-str (set-group-path->set-group-prefixed-path-str group-path)]
(if (seq active-set-names)
(let [path-active-set-names (->> (get-sets-at-prefix-path this prefixed-path-str)
- (map :name)
+ (map get-name)
(into #{}))
difference (set/difference path-active-set-names active-set-names)]
(cond
@@ -1095,7 +1186,7 @@ Will return a value that matches this schema:
active-set-names (filter theme-set-names all-set-names)
tokens (reduce (fn [tokens set-name]
(let [set (get-set this set-name)]
- (merge tokens (:tokens set))))
+ (merge tokens (get-tokens-map set))))
(d/ordered-map)
active-set-names)]
tokens))
@@ -1160,11 +1251,10 @@ Will return a value that matches this schema:
(defn duplicate-set [set-name lib & {:keys [suffix]}]
(let [sets (get-sets lib)
- unames (map :name sets)
+ unames (map get-name sets)
copy-name (cfh/generate-unique-name set-name unames :suffix suffix)]
(some-> (get-set lib set-name)
- (assoc :name copy-name)
- (assoc :modified-at (dt/now)))))
+ (rename copy-name))))
;; === Import / Export from JSON format
@@ -1473,8 +1563,10 @@ Will return a value that matches this schema:
[tokens-lib]
(let [{:keys [themes active-themes]} (dtcg-export-themes tokens-lib)
sets (->> (get-sets tokens-lib)
- (map (fn [{:keys [name tokens]}]
- [(str name ".json") (tokens-tree tokens :update-token-fn token->dtcg-token)]))
+ (map (fn [token-set]
+ (let [name (get-name token-set)
+ tokens (get-tokens-map token-set)]
+ [(str name ".json") (tokens-tree tokens :update-token-fn token->dtcg-token)])))
(into {}))]
(-> sets
(assoc "$themes.json" themes)
@@ -1491,8 +1583,9 @@ Will return a value that matches this schema:
(->> (get-set-tree tokens-lib)
(tree-seq d/ordered-map? vals)
(filter (partial instance? TokenSet))
- (map (fn [{:keys [name tokens]}]
- [name (tokens-tree tokens :update-token-fn token->dtcg-token)])))
+ (map (fn [set]
+ [(get-name set)
+ (tokens-tree (get-tokens-map set) :update-token-fn token->dtcg-token)])))
ordered-set-names
(mapv first name-set-tuples)
@@ -1512,28 +1605,31 @@ Will return a value that matches this schema:
(defn get-tokens-of-unknown-type
"Search for all tokens in the decoded json file that have a type that is not currently
supported by Penpot. Returns a map token-path -> token type."
- ([decoded-json]
- (get-tokens-of-unknown-type decoded-json "" (get-json-format decoded-json)))
- ([decoded-json parent-path json-format]
- (let [type-key (if (= json-format :json-format/dtcg) "$type" "type")]
- (reduce-kv
- (fn [unknown-tokens k v]
- (let [child-path (if (empty? parent-path)
- (name k)
- (str parent-path "." k))]
- (if (and (map? v)
- (not (contains? v type-key)))
- (let [nested-unknown-tokens (get-tokens-of-unknown-type v child-path json-format)]
- (merge unknown-tokens nested-unknown-tokens))
- (let [token-type-str (get v type-key)
- token-type (cto/dtcg-token-type->token-type token-type-str)]
- (if (and (not (some? token-type)) (some? token-type-str))
- (assoc unknown-tokens child-path token-type-str)
- unknown-tokens)))))
- nil
- decoded-json))))
+ [decoded-json {:keys [json-format parent-path process-token-type]
+ :or {json-format (get-json-format decoded-json)
+ parent-path ""
+ process-token-type identity}
+ :as opts}]
+ (let [type-key (if (= json-format :json-format/dtcg) "$type" "type")]
+ (reduce-kv
+ (fn [unknown-tokens k v]
+ (let [child-path (if (empty? parent-path)
+ (name k)
+ (str parent-path "." k))]
+ (if (and (map? v)
+ (not (contains? v type-key)))
+ (let [nested-unknown-tokens (get-tokens-of-unknown-type v (assoc opts :parent-path child-path))]
+ (merge unknown-tokens nested-unknown-tokens))
+ (let [token-type-str (get v type-key)
+ token-type (-> (cto/dtcg-token-type->token-type token-type-str)
+ (process-token-type))]
+ (if (and (not (some? token-type)) (some? token-type-str))
+ (assoc unknown-tokens child-path token-type-str)
+ unknown-tokens)))))
+ nil
+ decoded-json)))
-;; === Serialization handlers for RPC API (transit) and database (fressian)
+;; === Serialization handlers for RPC API (transit)
(t/add-handlers!
{:id "penpot/tokens-lib"
@@ -1543,8 +1639,8 @@ Will return a value that matches this schema:
{:id "penpot/token-set"
:class TokenSet
- :wfn #(into {} %)
- :rfn #(map->TokenSet %)}
+ :wfn deref
+ :rfn #(make-token-set %)}
{:id "penpot/token-theme"
:class TokenTheme
@@ -1556,6 +1652,8 @@ Will return a value that matches this schema:
:wfn #(into {} %)
:rfn #(map->Token %)})
+;; === Serialization handlers for database (fressian)
+
#?(:clj
(defn- read-tokens-lib-v1-0
"Reads the first version of tokens lib, now completly obsolete"
@@ -1675,16 +1773,16 @@ Will return a value that matches this schema:
(fres/write-object! w (into {} o)))
:rfn (fn [r]
(let [obj (fres/read-object! r)]
- (map->Token obj)))}
+ (make-token obj)))}
{:name "penpot/token-set/v1"
:class TokenSet
:wfn (fn [n w o]
(fres/write-tag! w n 1)
- (fres/write-object! w (into {} o)))
+ (fres/write-object! w (into {} (deref o))))
:rfn (fn [r]
(let [obj (fres/read-object! r)]
- (map->TokenSet obj)))}
+ (make-token-set obj)))}
{:name "penpot/token-theme/v1"
:class TokenTheme
@@ -1693,7 +1791,7 @@ Will return a value that matches this schema:
(fres/write-object! w (into {} o)))
:rfn (fn [r]
(let [obj (fres/read-object! r)]
- (map->TokenTheme obj)))}
+ (make-token-theme obj)))}
;; LEGACY TOKENS LIB READERS (with migrations)
{:name "penpot/tokens-lib/v1"
diff --git a/common/src/app/common/types/typography.cljc b/common/src/app/common/types/typography.cljc
index 155c958b10..fd172a8e4a 100644
--- a/common/src/app/common/types/typography.cljc
+++ b/common/src/app/common/types/typography.cljc
@@ -93,13 +93,16 @@
remap-typography
content)))))
+(defn remove-typography-from-node
+ "Remove the typography reference from a node."
+ [node]
+ (dissoc node :typography-ref-file :typography-ref-id))
+
(defn remove-external-typographies
"Change the shape so that any use of an external typography now is removed"
[shape file-id]
- (let [remove-ref-file #(dissoc % :typography-ref-file :typography-ref-id)]
-
- (update shape :content
- (fn [content]
- (txt/transform-nodes #(not= (:typography-ref-file %) file-id)
- remove-ref-file
- content)))))
+ (update shape :content
+ (fn [content]
+ (txt/transform-nodes #(not= (:typography-ref-file %) file-id)
+ remove-typography-from-node
+ content))))
diff --git a/common/src/app/common/types/variant.cljc b/common/src/app/common/types/variant.cljc
index 9849a30e1a..2d6e1a2ce8 100644
--- a/common/src/app/common/types/variant.cljc
+++ b/common/src/app/common/types/variant.cljc
@@ -139,7 +139,6 @@
(< (count (first %)) property-max-length)
(< (count (second %)) property-max-length)))))
-
(defn find-properties-to-remove
"Compares two property maps to find which properties should be removed"
[prev-props upd-props]
@@ -161,6 +160,46 @@
(filterv #(not (contains? prev-names (:name %))) upd-props)))
+(defn- split-base-name-and-number
+ "Extract the number in parentheses from an item, if present, and return both the base name and the number"
+ [item]
+ (let [pattern-num-parens #"\(\d+\)$"
+ pattern-num #"\d+"
+ base (-> item (str/replace pattern-num-parens "") (str/trim))
+ num (some->> item (re-find pattern-num-parens) (re-find pattern-num) (d/parse-integer))]
+ [base (d/nilv num 0)]))
+
+(defn- group-numbers-by-base-name
+ "Return a map with a set of numbers associated to each base name"
+ [items]
+ (reduce (fn [acc item]
+ (let [[base num] (split-base-name-and-number item)]
+ (update acc base (fnil conj #{}) num)))
+ {}
+ items))
+
+(defn update-number-in-repeated-item
+ "Add, keep or update a number in parentheses for a given item, if necessary, depending on the items
+ already present in a list, to avoid repetitions"
+ [items item]
+ (let [names (group-numbers-by-base-name items)
+ [base num] (split-base-name-and-number item)
+ nums-taken (get names base #{})]
+ (loop [n num]
+ (if (nums-taken n)
+ (recur (inc n))
+ (str base (when (pos? n) (str " (" n ")")))))))
+
+(defn update-number-in-repeated-prop-names
+ "Add, keep or update a number for each prop name depending on the previous ones"
+ [props]
+ (->> props
+ (reduce (fn [acc prop]
+ (conj acc {:name (update-number-in-repeated-item (mapv :name acc) (:name prop))
+ :value (:value prop)}))
+ [])))
+
+
(defn find-index-for-property-name
"Finds the index of a name in a property map"
[props name]
diff --git a/common/test/common_tests/colors_test.cljc b/common/test/common_tests/colors_test.cljc
index 89308415df..8c088b9dee 100644
--- a/common/test/common_tests/colors_test.cljc
+++ b/common/test/common_tests/colors_test.cljc
@@ -8,7 +8,6 @@
(:require
#?(:cljs [goog.color :as gcolors])
[app.common.colors :as colors]
- [app.common.data :as d]
[clojure.test :as t]))
(t/deftest valid-hex-color
@@ -52,8 +51,8 @@
(t/is (= [1 2 3] (colors/hex->rgb "#010203"))))
(t/deftest format-hsla
- (t/is (= "210, 50%, 0.78%, 1" (colors/format-hsla [210.0 0.5 0.00784313725490196 1])))
- (t/is (= "220, 5%, 30%, 0.8" (colors/format-hsla [220.0 0.05 0.3 0.8]))))
+ (t/is (= "210 50% 0.78% / 1" (colors/format-hsla [210.0 0.5 0.00784313725490196 1])))
+ (t/is (= "220 5% 30% / 0.8" (colors/format-hsla [220.0 0.05 0.3 0.8]))))
(t/deftest format-rgba
(t/is (= "210, 199, 12, 0.08" (colors/format-rgba [210 199 12 0.08])))
diff --git a/common/test/common_tests/logic/text_sync_test.cljc b/common/test/common_tests/logic/text_sync_test.cljc
new file mode 100644
index 0000000000..3f1fb18c68
--- /dev/null
+++ b/common/test/common_tests/logic/text_sync_test.cljc
@@ -0,0 +1,881 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns common-tests.logic.text-sync-test
+ (:require
+ [app.common.files.changes-builder :as pcb]
+ [app.common.logic.libraries :as cll]
+ [app.common.logic.shapes :as cls]
+ [app.common.test-helpers.components :as thc]
+ [app.common.test-helpers.compositions :as tho]
+ [app.common.test-helpers.files :as thf]
+ [app.common.test-helpers.ids-map :as thi]
+ [app.common.test-helpers.shapes :as ths]
+ [clojure.test :as t]))
+
+(t/use-fixtures :each thi/test-fixture)
+
+
+(t/deftest test-sync-unchanged-copy-when-changed-attribute
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page (thf/current-page file)
+ main-child (ths/get-shape file :main-child)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32"))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ (t/is (= "32" (:font-size line)))
+ (t/is (= "hello world" (:text line)))))
+
+(t/deftest test-sync-unchanged-copy-when-changed-text
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page (thf/current-page file)
+ main-child (ths/get-shape file :main-child)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ (t/is (= "14" (:font-size line)))
+ (t/is (= "Bye" (:text line)))))
+
+(t/deftest test-sync-unchanged-copy-when-changed-both
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page (thf/current-page file)
+ main-child (ths/get-shape file :main-child)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ (t/is (= "32" (:font-size line)))
+ (t/is (= "Bye" (:text line)))))
+
+(t/deftest test-sync-updated-attr-copy-when-changed-attribute
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :font-weight] "700"))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32"))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ ;; The attr doesn't change, because it was touched
+ (t/is (= "14" (:font-size line)))
+ (t/is (= "hello world" (:text line)))))
+
+(t/deftest test-sync-updated-attr-copy-when-changed-text
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :font-weight] "700"))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ (t/is (= "14" (:font-size line)))
+ ;; The text is updated because only attrs were touched
+ (t/is (= "Bye" (:text line)))))
+
+(t/deftest test-sync-updated-attr-copy-when-changed-both
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :font-weight] "700"))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ ;; The attr doesn't change, because it was touched
+ (t/is (= "14" (:font-size line)))
+ ;; The text is updated because only attrs were touched
+ (t/is (= "Bye" (:text line)))))
+
+(t/deftest test-sync-updated-text-copy-when-changed-attribute
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Hi"))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32"))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ ;; The attr is updated because only text were touched
+ (t/is (= "32" (:font-size line)))
+ (t/is (= "Hi" (:text line)))))
+
+(t/deftest test-sync-updated-text-copy-when-changed-text
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Hi"))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ (t/is (= "14" (:font-size line)))
+ ;; The text doesn't change, because it was touched
+ (t/is (= "Hi" (:text line)))))
+
+(t/deftest test-sync-updated-text-copy-when-changed-both
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Hi"))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ ;; The attr is updated because only text were touched
+ (t/is (= "32" (:font-size line)))
+ ;; The text doesn't change, because it was touched
+ (t/is (= "Hi" (:text line)))))
+
+(t/deftest test-sync-updated-both-copy-when-changed-attribute
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-weight] "700")
+ (assoc-in [:content :children 0 :children 0 :children 0 :text] "Hi")))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :font-size] "32"))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ ;; The attr doesn't change, because it was touched
+ (t/is (= "14" (:font-size line)))
+ (t/is (= "Hi" (:text line)))))
+
+(t/deftest test-sync-updated-both-copy-when-changed-text
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-weight] "700")
+ (assoc-in [:content :children 0 :children 0 :children 0 :text] "Hi")))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ (t/is (= "14" (:font-size line)))
+ ;; The text doesn't change, because it was touched
+ (t/is (= "Hi" (:text line)))))
+
+(t/deftest test-sync-updated-both-copy-when-changed-both
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-weight] "700")
+ (assoc-in [:content :children 0 :children 0 :children 0 :text] "Hi")))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ ;; The attr doesn't change, because it was touched
+ (t/is (= "14" (:font-size line)))
+ ;; The text doesn't change, because it was touched
+ (t/is (= "Hi" (:text line)))))
+
+(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-attribute
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (let [line (get-in shape [:content :children 0 :children 0 :children 0])]
+ (update-in shape [:content :children 0 :children 0 :children]
+ #(conj % line))))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ ;; Update the attrs on all the content tree
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :font-size] "32")))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ ;; The attr is updated because all the attrs on the structure are equal
+ (t/is (= "32" (:font-size line)))
+ (t/is (= "hello world" (:text line)))))
+
+(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-text
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (let [line (get-in shape [:content :children 0 :children 0 :children 0])]
+ (update-in shape [:content :children 0 :children 0 :children]
+ #(conj % line))))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ (t/is (= "14" (:font-size line)))
+ ;; The text doesn't change, because the structure was touched
+ (t/is (= "hello world" (:text line)))))
+
+(t/deftest test-sync-updated-structure-same-attrs-copy-when-changed-both
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (let [line (get-in shape [:content :children 0 :children 0 :children 0])]
+ (update-in shape [:content :children 0 :children 0 :children]
+ #(conj % line))))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ ;; Update the attrs on all the content tree
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ ;; The attr is updated because all the attrs on the structure are equal
+ (t/is (= "32" (:font-size line)))
+ ;; The text doesn't change, because the structure was touched
+ (t/is (= "hello world" (:text line)))))
+
+(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-attribute
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (let [line (-> (get-in shape [:content :children 0 :children 0 :children 0])
+ (assoc :font-weight "700"))]
+ (update-in shape [:content :children 0 :children 0 :children]
+ #(conj % line))))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ ;; Update the attrs on all the content tree
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :font-size] "32")))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ ;; The attr doesn't change, because not all the attrs on the structure are equal
+ (t/is (= "14" (:font-size line)))
+ (t/is (= "hello world" (:text line)))))
+
+(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-text
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (let [line (-> (get-in shape [:content :children 0 :children 0 :children 0])
+ (assoc :font-weight "700"))]
+ (update-in shape [:content :children 0 :children 0 :children]
+ #(conj % line))))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :children 0 :text] "Bye"))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ (t/is (= "14" (:font-size line)))
+ ;; The text doesn't change, because the structure was touched
+ (t/is (= "hello world" (:text line)))))
+
+(t/deftest test-sync-updated-structure-diff-attrs-copy-when-changed-both
+ (let [;; ==== Setup
+ file0 (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page0 (thf/current-page file0)
+ copy-child (ths/get-shape file0 :copy-child)
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page0))
+ #{(:id copy-child)}
+ (fn [shape]
+ (let [line (-> (get-in shape [:content :children 0 :children 0 :children 0])
+ (assoc :font-weight "700"))]
+ (update-in shape [:content :children 0 :children 0 :children]
+ #(conj % line))))
+ (:objects (thf/current-page file0))
+ {})
+ file (thf/apply-changes file0 changes)
+ main-child (ths/get-shape file :main-child)
+ page (thf/current-page file)
+
+ ;; ==== Action
+ changes1 (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id main-child)}
+ (fn [shape]
+ ;; Update the attrs on all the content tree
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :children 0 :text] "Bye")))
+ (:objects page)
+ {})
+
+ updated-file (thf/apply-changes file changes1)
+
+ changes2 (cll/generate-sync-file-changes (pcb/empty-changes)
+ nil
+ :components
+ (:id updated-file)
+ (thi/id :component1)
+ (:id updated-file)
+ {(:id updated-file) updated-file}
+ (:id updated-file))
+
+ file' (thf/apply-changes updated-file changes2)
+
+ ;; ==== Get
+ copy-child' (ths/get-shape file' :copy-child)
+ line (get-in copy-child' [:content :children 0 :children 0 :children 0])]
+ ;; The attr doesn't change, because not all the attrs on the structure are equal
+ (t/is (= "14" (:font-size line)))
+ ;; The text doesn't change, because the structure was touched
+ (t/is (= "hello world" (:text line)))))
\ No newline at end of file
diff --git a/common/test/common_tests/logic/text_touched_test.cljc b/common/test/common_tests/logic/text_touched_test.cljc
new file mode 100644
index 0000000000..7aacf4fe52
--- /dev/null
+++ b/common/test/common_tests/logic/text_touched_test.cljc
@@ -0,0 +1,132 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns common-tests.logic.text-touched-test
+ (:require
+ [app.common.files.changes-builder :as pcb]
+ [app.common.logic.shapes :as cls]
+ [app.common.test-helpers.components :as thc]
+ [app.common.test-helpers.compositions :as tho]
+ [app.common.test-helpers.files :as thf]
+ [app.common.test-helpers.ids-map :as thi]
+ [app.common.test-helpers.shapes :as ths]
+ [clojure.test :as t]))
+
+(t/use-fixtures :each thi/test-fixture)
+
+(t/deftest test-text-copy-changed-attribute
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page (thf/current-page file)
+ copy-child (ths/get-shape file :copy-child)
+
+ ;; ==== Action
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id copy-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :font-size] "32"))
+ (:objects page)
+ {})
+
+ file' (thf/apply-changes file changes)
+ copy-child' (ths/get-shape file' :copy-child)]
+ (t/is (= #{:content-group :text-content-attribute} (:touched copy-child')))))
+
+(t/deftest test-text-copy-changed-text
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page (thf/current-page file)
+ copy-child (ths/get-shape file :copy-child)
+
+ ;; ==== Action
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id copy-child)}
+ (fn [shape]
+ (assoc-in shape [:content :children 0 :children 0 :text] "Bye"))
+ (:objects page)
+ {})
+
+ file' (thf/apply-changes file changes)
+ copy-child' (ths/get-shape file' :copy-child)]
+ (t/is (= #{:content-group :text-content-text} (:touched copy-child')))))
+
+(t/deftest test-text-copy-changed-both
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page (thf/current-page file)
+ copy-child (ths/get-shape file :copy-child)
+
+ ;; ==== Action
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id copy-child)}
+ (fn [shape]
+ (-> shape
+ (assoc-in [:content :children 0 :children 0 :font-size] "32")
+ (assoc-in [:content :children 0 :children 0 :text] "Bye")))
+ (:objects page)
+ {})
+
+ file' (thf/apply-changes file changes)
+ copy-child' (ths/get-shape file' :copy-child)]
+ (t/is (= #{:content-group :text-content-attribute :text-content-text} (:touched copy-child')))))
+
+(t/deftest test-text-copy-changed-structure-same-attrs
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page (thf/current-page file)
+ copy-child (ths/get-shape file :copy-child)
+
+ ;; ==== Action
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id copy-child)}
+ (fn [shape]
+ (let [line (get-in shape [:content :children 0 :children 0])]
+ (update-in shape [:content :children 0 :children]
+ #(conj % line))))
+ (:objects page)
+ {})
+
+ file' (thf/apply-changes file changes)
+ copy-child' (ths/get-shape file' :copy-child)]
+ (t/is (= #{:content-group :text-content-structure :text-content-structure-same-attrs} (:touched copy-child')))))
+
+(t/deftest test-text-copy-changed-structure-diff-attrs
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (tho/add-frame-with-text :main-root :main-child "hello world")
+ (thc/make-component :component1 :main-root)
+ (thc/instantiate-component :component1 :copy-root {:children-labels [:copy-child]}))
+ page (thf/current-page file)
+ copy-child (ths/get-shape file :copy-child)
+
+ ;; ==== Action
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id copy-child)}
+ (fn [shape]
+ (let [line (-> shape
+ (get-in [:content :children 0 :children 0])
+ (assoc :font-size "32"))]
+ (update-in shape [:content :children 0 :children]
+ #(conj % line))))
+ (:objects page)
+ {})
+
+ file' (thf/apply-changes file changes)
+ copy-child' (ths/get-shape file' :copy-child)]
+ (t/is (= #{:content-group :text-content-structure} (:touched copy-child')))))
+
diff --git a/common/test/common_tests/logic/token_apply_test.cljc b/common/test/common_tests/logic/token_apply_test.cljc
index 8b028e5ebf..384f534e8a 100644
--- a/common/test/common_tests/logic/token_apply_test.cljc
+++ b/common/test/common_tests/logic/token_apply_test.cljc
@@ -54,9 +54,17 @@
(ctob/add-token-in-set "test-token-set"
(ctob/make-token :name "token-dimensions"
:type :dimensions
- :value 100))))
+ :value 100))
+ (ctob/add-token-in-set "test-token-set"
+ (ctob/make-token :name "token-font-size"
+ :type :font-size
+ :value 24))
+ (ctob/add-token-in-set "test-token-set"
+ (ctob/make-token :name "token-letter-spacing"
+ :type :letter-spacing
+ :value 2))))
(tho/add-frame :frame1)
- (tho/add-text :text1 "Hello World")))
+ (tho/add-text :text1 "Hello World!")))
(defn- apply-all-tokens
[file]
@@ -68,19 +76,23 @@
(tht/apply-token-to-shape :frame1 "token-color" [:stroke-color] [:stroke-color] "#00ff00")
(tht/apply-token-to-shape :frame1 "token-color" [:fill] [:fill] "#00ff00")
(tht/apply-token-to-shape :frame1 "token-dimensions" [:width :height] [:width :height] 100)
- (tht/apply-token-to-shape :text1 "token-color" [:fill] [:fill] "#00ff00")))
+ (tht/apply-token-to-shape :text1 "token-font-size" [:font-size] [:font-size] 24)
+ (tht/apply-token-to-shape :text1 "token-letter-spacing" [:letter-spacing] [:letter-spacing] 2)))
(t/deftest apply-tokens-to-shape
(let [;; ==== Setup
file (setup-file)
page (thf/current-page file)
frame1 (ths/get-shape file :frame1)
+ text1 (ths/get-shape file :text1)
token-radius (tht/get-token file "test-token-set" "token-radius")
token-rotation (tht/get-token file "test-token-set" "token-rotation")
token-opacity (tht/get-token file "test-token-set" "token-opacity")
token-stroke-width (tht/get-token file "test-token-set" "token-stroke-width")
token-color (tht/get-token file "test-token-set" "token-color")
token-dimensions (tht/get-token file "test-token-set" "token-dimensions")
+ token-font-size (tht/get-token file "test-token-set" "token-font-size")
+ token-letter-spacing (tht/get-token file "test-token-set" "token-letter-spacing")
;; ==== Action
changes (-> (-> (pcb/empty-changes nil)
@@ -89,38 +101,48 @@
(cls/generate-update-shapes [(:id frame1)]
(fn [shape]
(as-> shape $
- (cto/maybe-apply-token-to-shape {:token nil ; test nil case
- :shape $
- :attributes []})
- (cto/maybe-apply-token-to-shape {:token token-radius
- :shape $
- :attributes [:r1 :r2 :r3 :r4]})
- (cto/maybe-apply-token-to-shape {:token token-rotation
- :shape $
- :attributes [:rotation]})
- (cto/maybe-apply-token-to-shape {:token token-opacity
- :shape $
- :attributes [:opacity]})
- (cto/maybe-apply-token-to-shape {:token token-stroke-width
- :shape $
- :attributes [:stroke-width]})
- (cto/maybe-apply-token-to-shape {:token token-color
- :shape $
- :attributes [:stroke-color]})
- (cto/maybe-apply-token-to-shape {:token token-color
- :shape $
- :attributes [:fill]})
- (cto/maybe-apply-token-to-shape {:token token-dimensions
- :shape $
- :attributes [:width :height]})))
+ (cto/apply-token-to-shape {:token token-radius
+ :shape $
+ :attributes [:r1 :r2 :r3 :r4]})
+ (cto/apply-token-to-shape {:token token-rotation
+ :shape $
+ :attributes [:rotation]})
+ (cto/apply-token-to-shape {:token token-opacity
+ :shape $
+ :attributes [:opacity]})
+ (cto/apply-token-to-shape {:token token-stroke-width
+ :shape $
+ :attributes [:stroke-width]})
+ (cto/apply-token-to-shape {:token token-color
+ :shape $
+ :attributes [:stroke-color]})
+ (cto/apply-token-to-shape {:token token-color
+ :shape $
+ :attributes [:fill]})
+ (cto/apply-token-to-shape {:token token-dimensions
+ :shape $
+ :attributes [:width :height]})))
+ (:objects page)
+ {})
+ (cls/generate-update-shapes [(:id text1)]
+ (fn [shape]
+ (as-> shape $
+ (cto/apply-token-to-shape {:token token-font-size
+ :shape $
+ :attributes [:font-size]})
+ (cto/apply-token-to-shape {:token token-letter-spacing
+ :shape $
+ :attributes [:letter-spacing]})))
(:objects page)
{}))
file' (thf/apply-changes file changes)
;; ==== Get
- frame1' (ths/get-shape file' :frame1)
- applied-tokens' (:applied-tokens frame1')]
+ frame1' (ths/get-shape file' :frame1)
+ applied-tokens' (:applied-tokens frame1')
+ text1' (ths/get-shape file' :text1)
+ text1-applied-tokens (:applied-tokens text1')]
;; ==== Check
(t/is (= (count applied-tokens') 11))
@@ -134,7 +156,10 @@
(t/is (= (:stroke-color applied-tokens') "token-color"))
(t/is (= (:fill applied-tokens') "token-color"))
(t/is (= (:width applied-tokens') "token-dimensions"))
- (t/is (= (:height applied-tokens') "token-dimensions"))))
+ (t/is (= (:height applied-tokens') "token-dimensions"))
+ (t/is (= (count text1-applied-tokens) 2))
+ (t/is (= (:font-size text1-applied-tokens) "token-font-size"))
+ (t/is (= (:letter-spacing text1-applied-tokens) "token-letter-spacing"))))
(t/deftest unapply-tokens-from-shape
(let [;; ==== Setup
@@ -142,6 +167,7 @@
(apply-all-tokens))
page (thf/current-page file)
frame1 (ths/get-shape file :frame1)
+ text1 (ths/get-shape file :text1)
;; ==== Action
changes (-> (-> (pcb/empty-changes nil)
@@ -158,16 +184,26 @@
(cto/unapply-token-id [:fill])
(cto/unapply-token-id [:width :height])))
(:objects page)
+ {})
+ (cls/generate-update-shapes [(:id text1)]
+ (fn [shape]
+ (-> shape
+ (cto/unapply-token-id [:font-size])
+ (cto/unapply-token-id [:letter-spacing])))
+ (:objects page)
{}))
file' (thf/apply-changes file changes)
;; ==== Get
- frame1' (ths/get-shape file' :frame1)
- applied-tokens' (:applied-tokens frame1')]
+ frame1' (ths/get-shape file' :frame1)
+ applied-tokens' (:applied-tokens frame1')
+ text1' (ths/get-shape file' :text1)
+ text1-applied-tokens (:applied-tokens text1')]
;; ==== Check
- (t/is (= (count applied-tokens') 0))))
+ (t/is (= (count applied-tokens') 0))
+ (t/is (= (count text1-applied-tokens) 0))))
(t/deftest unapply-tokens-automatic
(let [;; ==== Setup
@@ -202,7 +238,9 @@
shape
txt/is-content-node?
d/txt-merge
- {:fills (ths/sample-fills-color :fill-color "#fabada")}))
+ {:fills (ths/sample-fills-color :fill-color "#fabada")
+ :font-size "1"
+ :letter-spacing "0"}))
(:objects page)
{}))
@@ -216,4 +254,4 @@
;; ==== Check
(t/is (= (count applied-tokens-frame') 0))
- (t/is (= (count applied-tokens-text') 0))))
\ No newline at end of file
+ (t/is (= (count applied-tokens-text') 0))))
diff --git a/common/test/common_tests/logic/token_test.cljc b/common/test/common_tests/logic/token_test.cljc
index 800ad4111f..e4cec8becf 100644
--- a/common/test/common_tests/logic/token_test.cljc
+++ b/common/test/common_tests/logic/token_test.cljc
@@ -269,8 +269,7 @@
new-set-name "foo1"
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
- (pcb/set-token-set set-name false (assoc prev-token-set
- :name new-set-name)))
+ (pcb/set-token-set set-name false (ctob/rename prev-token-set new-set-name)))
redo (thf/apply-changes file changes)
redo-lib (tht/get-tokens-lib redo)
undo (thf/apply-undo-changes redo changes)
diff --git a/common/test/common_tests/logic/variants_switch_test.cljc b/common/test/common_tests/logic/variants_switch_test.cljc
new file mode 100644
index 0000000000..bd06cbde6d
--- /dev/null
+++ b/common/test/common_tests/logic/variants_switch_test.cljc
@@ -0,0 +1,1125 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns common-tests.logic.variants-switch-test
+ (:require
+ [app.common.files.changes-builder :as pcb]
+ [app.common.logic.shapes :as cls]
+ [app.common.test-helpers.components :as thc]
+ [app.common.test-helpers.compositions :as tho]
+ [app.common.test-helpers.files :as thf]
+ [app.common.test-helpers.ids-map :as thi]
+ [app.common.test-helpers.shapes :as ths]
+ [app.common.test-helpers.variants :as thv]
+ [clojure.test :as t]))
+
+(t/use-fixtures :each thi/test-fixture)
+
+(t/deftest test-simple-switch
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (thv/add-variant-with-child
+ :v01 :c01 :m01 :c02 :m02 :r01 :r02
+ {:child1-params {:width 5}
+ :child2-params {:width 15}})
+
+ (thc/instantiate-component :c01
+ :copy01
+ :children-labels [:copy-r01]))
+
+ page (thf/current-page file)
+ copy01 (ths/get-shape file :copy01)
+ rect01 (get-in page [:objects (-> copy01 :shapes first)])
+
+ ;; ==== Action
+ file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
+
+ page' (thf/current-page file')
+ copy02' (ths/get-shape file' :copy02)
+ rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
+ ;; The rect had width 5 before the switch
+ (t/is (= (:width rect01) 5))
+ ;; The rect has width 15 after the switch
+ (t/is (= (:width rect02') 15))))
+
+
+(t/deftest test-switch-with-override
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (thv/add-variant-with-child
+ :v01 :c01 :m01 :c02 :m02 :r01 :r02
+ {:child1-params {:width 5}
+ :child2-params {:width 5}})
+
+ (thc/instantiate-component :c01
+ :copy01
+ :children-labels [:copy-r01]))
+
+ page (thf/current-page file)
+ copy01 (ths/get-shape file :copy01)
+ rect01 (get-in page [:objects (-> copy01 :shapes first)])
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id rect01)}
+ (fn [shape]
+ (assoc shape :width 25))
+ (:objects page)
+ {})
+
+ file (thf/apply-changes file changes)
+ page (thf/current-page file)
+ rect01 (get-in page [:objects (:id rect01)])
+
+ ;; ==== Action
+ file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
+
+ page' (thf/current-page file')
+ copy02' (ths/get-shape file' :copy02)
+ rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
+
+ ;; The rect had width 25 before the switch
+ (t/is (= (:width rect01) 25))
+ ;; The override is keept: The rect still has width 25 after the switch
+ (t/is (= (:width rect02') 25))))
+
+(t/deftest test-switch-with-no-override
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ (thv/add-variant-with-child
+ :v01 :c01 :m01 :c02 :m02 :r01 :r02
+ {:child1-params {:width 5}
+ :child2-params {:width 15}})
+
+ (thc/instantiate-component :c01
+ :copy01
+ :children-labels [:copy-r01]))
+
+ page (thf/current-page file)
+ copy01 (ths/get-shape file :copy01)
+ rect01 (get-in page [:objects (-> copy01 :shapes first)])
+
+ changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
+ #{(:id rect01)}
+ (fn [shape]
+ (assoc shape :width 25))
+ (:objects page)
+ {})
+
+ file (thf/apply-changes file changes)
+ page (thf/current-page file)
+ rect01 (get-in page [:objects (:id rect01)])
+
+ ;; ==== Action
+ file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
+
+ page' (thf/current-page file')
+ copy02' (ths/get-shape file' :copy02)
+ rect02' (get-in page' [:objects (-> copy02' :shapes first)])]
+
+ ;; The rect had width 25 before the switch
+ (t/is (= (:width rect01) 25))
+ ;; The override isn't keept, because the property is different in the mains
+ ;; The rect has width 15 after the switch
+ (t/is (= (:width rect02') 15))))
+
+
+(def font-size-path-paragraph [:content :children 0 :children 0 :font-size])
+(def font-size-path-0 [:content :children 0 :children 0 :children 0 :font-size])
+(def font-size-path-1 [:content :children 0 :children 0 :children 1 :font-size])
+
+
+(def text-path-0 [:content :children 0 :children 0 :children 0 :text])
+(def text-path-1 [:content :children 0 :children 0 :children 1 :text])
+(def text-lines-path [:content :children 0 :children 0 :children])
+
+(defn- update-attr
+ [file label path value]
+ (let [page (thf/current-page file)
+ shape (ths/get-shape file label)
+ changes (cls/generate-update-shapes
+ (pcb/empty-changes nil (:id page))
+ #{(:id shape)}
+ (fn [shape]
+ (cond-> (assoc-in shape path value)
+ (or (= path font-size-path-0) (= path font-size-path-1))
+ (assoc-in font-size-path-paragraph value)))
+ (:objects page)
+ {})]
+ (thf/apply-changes file changes)))
+
+(defn- change-structure
+ [file label]
+ (let [page (thf/current-page file)
+ shape (ths/get-shape file label)
+ line1 (-> (get-in shape text-lines-path)
+ first
+ (assoc :text "new line 1"))
+ line2 (assoc line1 :text "new line 2")
+ changes (cls/generate-update-shapes
+ (pcb/empty-changes nil (:id page))
+ #{(:id shape)}
+ (fn [shape]
+ (assoc-in shape text-lines-path [line1 line2]))
+ (:objects page)
+ {})]
+ (thf/apply-changes file changes)))
+
+(t/deftest test-switch-with-identical-text-override
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ ;; Both components are identical: have the same text and props
+ (thv/add-variant-with-text
+ :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "hello world")
+ (thc/instantiate-component :c01
+ :copy-clean
+ :children-labels [:copy-clean-t])
+ (thc/instantiate-component :c01
+ :copy-font-size
+ :children-labels [:copy-font-size-t])
+ (thc/instantiate-component :c01
+ :copy-text
+ :children-labels [:copy-text-t])
+ (thc/instantiate-component :c01
+ :copy-both
+ :children-labels [:copy-both-t]))
+
+
+ ;; The copy clean has no overrides
+ copy-clean (ths/get-shape file :copy-clean)
+ copy-clean-t (ths/get-shape file :copy-clean-t)
+
+ ;; Override font size on copy-font-size
+ file (update-attr file :copy-font-size-t font-size-path-0 "25")
+ copy-font-size (ths/get-shape file :copy-font-size)
+ copy-font-size-t (ths/get-shape file :copy-font-size-t)
+
+ ;; Override text on copy-text
+ file (update-attr file :copy-text-t text-path-0 "text overriden")
+ copy-text (ths/get-shape file :copy-text)
+ copy-text-t (ths/get-shape file :copy-text-t)
+
+ ;; Override both on copy-both
+ file (update-attr file :copy-both-t font-size-path-0 "25")
+ file (update-attr file :copy-both-t text-path-0 "text overriden")
+ copy-both (ths/get-shape file :copy-both)
+ copy-both-t (ths/get-shape file :copy-both-t)
+
+
+ ;; ==== Action: Switch all the copies
+ file' (-> file
+ (tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
+ (tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
+ (tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
+ (tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
+ page' (thf/current-page file')
+ copy-clean' (ths/get-shape file' :copy-clean-2)
+ copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
+
+ copy-font-size' (ths/get-shape file' :copy-font-size-2)
+ copy-font-size-t' (get-in page' [:objects (-> copy-font-size' :shapes first)])
+
+ copy-text' (ths/get-shape file' :copy-text-2)
+ copy-text-t' (get-in page' [:objects (-> copy-text' :shapes first)])
+
+ copy-both' (ths/get-shape file' :copy-both-2)
+ copy-both-t' (get-in page' [:objects (-> copy-both' :shapes first)])]
+
+ (thf/dump-file file' {:keys [:name #_:content]})
+
+
+ ;;;;;;;;;;; Clean copy
+ ;; Before the switch:
+ ;; * font size 14
+ ;; * text "hello world"
+ (t/is (= (get-in copy-clean-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-clean-t text-path-0) "hello world"))
+
+ ;; After the switch:
+ ;; * font size 25 (value of c02, because there was no override)
+ ;; * text "hello world" (value of c02, because there was no override)
+ (t/is (= (get-in copy-clean-t' font-size-path-0) "14"))
+ (t/is (= (get-in copy-clean-t' text-path-0) "hello world"))
+
+
+ ;;;;;;;;;;; Font size copy
+ ;; Before the switch:
+ ;; * font size 25
+ ;; * text "hello world"
+ (t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-font-size-t text-path-0) "hello world"))
+
+ ;; After the switch:
+ ;; * font size 25 (the override is preserved)
+ ;; * text "hello world" (value of c02, because there was no override)
+ (t/is (= (get-in copy-font-size-t' font-size-path-0) "25"))
+ (t/is (= (get-in copy-font-size-t' text-path-0) "hello world"))
+
+ ;;;;;;;;;;; Text copy
+ ;; Before the switch:
+ ;; * font size 14
+ ;; * text "text overriden"
+ (t/is (= (get-in copy-text-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-text-t text-path-0) "text overriden"))
+
+ ;; After the switch:
+ ;; * font size 14 (value of c02, because there was no override)
+ ;; * text "text overriden" (the override is preserved)
+ (t/is (= (get-in copy-text-t' font-size-path-0) "14"))
+ (t/is (= (get-in copy-text-t' text-path-0) "text overriden"))
+
+ ;;;;;;;;;;; Both copy
+ ;; Before the switch:
+ ;; * font size 25
+ ;; * text "text overriden"
+ (t/is (= (get-in copy-both-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-both-t text-path-0) "text overriden"))
+
+ ;; After the switch:
+ ;; * font size 25 (the override is preserved)
+ ;; * text "text overriden" (the override is preserved)
+ (t/is (= (get-in copy-both-t' font-size-path-0) "25"))
+ (t/is (= (get-in copy-both-t' text-path-0) "text overriden"))))
+
+(t/deftest test-switch-with-different-prop-text-override
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ ;; The second component has a different prop
+ (thv/add-variant-with-text
+ :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "hello world")
+ (update-attr :t02 font-size-path-0 "50")
+
+ (thc/instantiate-component :c01
+ :copy-clean
+ :children-labels [:copy-clean-t])
+ (thc/instantiate-component :c01
+ :copy-font-size
+ :children-labels [:copy-font-size-t])
+ (thc/instantiate-component :c01
+ :copy-text
+ :children-labels [:copy-text-t])
+ (thc/instantiate-component :c01
+ :copy-both
+ :children-labels [:copy-both-t]))
+
+
+ ;; The copy clean has no overrides
+ copy-clean (ths/get-shape file :copy-clean)
+ copy-clean-t (ths/get-shape file :copy-clean-t)
+
+ ;; Override font size on copy-font-size
+ file (update-attr file :copy-font-size-t font-size-path-0 "25")
+ copy-font-size (ths/get-shape file :copy-font-size)
+ copy-font-size-t (ths/get-shape file :copy-font-size-t)
+
+ ;; Override text on copy-text
+ file (update-attr file :copy-text-t text-path-0 "text overriden")
+ copy-text (ths/get-shape file :copy-text)
+ copy-text-t (ths/get-shape file :copy-text-t)
+
+ ;; Override both on copy-both
+ file (update-attr file :copy-both-t font-size-path-0 "25")
+ file (update-attr file :copy-both-t text-path-0 "text overriden")
+ copy-both (ths/get-shape file :copy-both)
+ copy-both-t (ths/get-shape file :copy-both-t)
+
+
+ ;; ==== Action: Switch all the copies
+ file' (-> file
+ (tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
+ (tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
+ (tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
+ (tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
+ page' (thf/current-page file')
+ copy-clean' (ths/get-shape file' :copy-clean-2)
+ copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
+
+ copy-font-size' (ths/get-shape file' :copy-font-size-2)
+ copy-font-size-t' (get-in page' [:objects (-> copy-font-size' :shapes first)])
+
+ copy-text' (ths/get-shape file' :copy-text-2)
+ copy-text-t' (get-in page' [:objects (-> copy-text' :shapes first)])
+
+ copy-both' (ths/get-shape file' :copy-both-2)
+ copy-both-t' (get-in page' [:objects (-> copy-both' :shapes first)])]
+
+ (thf/dump-file file' {:keys [:name #_:content]})
+
+
+ ;;;;;;;;;;; Clean copy
+ ;; Before the switch:
+ ;; * font size 14
+ ;; * text "hello world"
+ (t/is (= (get-in copy-clean-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-clean-t text-path-0) "hello world"))
+
+ ;; After the switch:
+ ;; * font size 50 (value of c02, because there was no override)
+ ;; * text "hello world" (value of c02, because there was no override)
+ (t/is (= (get-in copy-clean-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-clean-t' text-path-0) "hello world"))
+
+
+ ;;;;;;;;;;; Font size copy
+ ;; Before the switch:
+ ;; * font size 25
+ ;; * text "hello world"
+ (t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-font-size-t text-path-0) "hello world"))
+
+ ;; After the switch:
+ ;; * font size 50 (value of c02: the override is not preserved)
+ ;; * text "hello world" (value of c02, because there was no override)
+ (t/is (= (get-in copy-font-size-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-font-size-t' text-path-0) "hello world"))
+
+ ;;;;;;;;;;; Text copy
+ ;; Before the switch:
+ ;; * font size 14
+ ;; * text "text overriden"
+ (t/is (= (get-in copy-text-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-text-t text-path-0) "text overriden"))
+
+ ;; After the switch:
+ ;; * font size 50 (value of c02, because there was no override)
+ ;; * text "text overriden" (the override is preserved)
+ (t/is (= (get-in copy-text-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-text-t' text-path-0) "text overriden"))
+
+ ;;;;;;;;;;; Both copy
+ ;; Before the switch:
+ ;; * font size 25
+ ;; * text "text overriden"
+ (t/is (= (get-in copy-both-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-both-t text-path-0) "text overriden"))
+
+ ;; After the switch:
+ ;; * font size 50 (value of c02: the override is not preserved)
+ ;; * text "text overriden" (the override is preserved)
+ (t/is (= (get-in copy-both-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-both-t' text-path-0) "text overriden"))))
+
+
+(t/deftest test-switch-with-different-text-text-override
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ ;; Second comp has different text
+ (thv/add-variant-with-text
+ :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "bye")
+ (thc/instantiate-component :c01
+ :copy-clean
+ :children-labels [:copy-clean-t])
+ (thc/instantiate-component :c01
+ :copy-font-size
+ :children-labels [:copy-font-size-t])
+ (thc/instantiate-component :c01
+ :copy-text
+ :children-labels [:copy-text-t])
+ (thc/instantiate-component :c01
+ :copy-both
+ :children-labels [:copy-both-t]))
+
+
+ ;; The copy clean has no overrides
+ copy-clean (ths/get-shape file :copy-clean)
+ copy-clean-t (ths/get-shape file :copy-clean-t)
+
+ ;; Override font size on copy-font-size
+ file (update-attr file :copy-font-size-t font-size-path-0 "25")
+ copy-font-size (ths/get-shape file :copy-font-size)
+ copy-font-size-t (ths/get-shape file :copy-font-size-t)
+
+ ;; Override text on copy-text
+ file (update-attr file :copy-text-t text-path-0 "text overriden")
+ copy-text (ths/get-shape file :copy-text)
+ copy-text-t (ths/get-shape file :copy-text-t)
+
+ ;; Override both on copy-both
+ file (update-attr file :copy-both-t font-size-path-0 "25")
+ file (update-attr file :copy-both-t text-path-0 "text overriden")
+ copy-both (ths/get-shape file :copy-both)
+ copy-both-t (ths/get-shape file :copy-both-t)
+
+
+ ;; ==== Action: Switch all the copies
+ file' (-> file
+ (tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
+ (tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
+ (tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
+ (tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
+ page' (thf/current-page file')
+ copy-clean' (ths/get-shape file' :copy-clean-2)
+ copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
+
+ copy-font-size' (ths/get-shape file' :copy-font-size-2)
+ copy-font-size-t' (get-in page' [:objects (-> copy-font-size' :shapes first)])
+
+ copy-text' (ths/get-shape file' :copy-text-2)
+ copy-text-t' (get-in page' [:objects (-> copy-text' :shapes first)])
+
+ copy-both' (ths/get-shape file' :copy-both-2)
+ copy-both-t' (get-in page' [:objects (-> copy-both' :shapes first)])]
+
+ (thf/dump-file file' {:keys [:name #_:content]})
+
+
+ ;;;;;;;;;;; Clean copy
+ ;; Before the switch:
+ ;; * font size 14
+ ;; * text "hello world"
+ (t/is (= (get-in copy-clean-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-clean-t text-path-0) "hello world"))
+
+ ;; After the switch:
+ ;; * font size 25 (value of c02, because there was no override)
+ ;; * text "bye" (value of c02, because there was no override)
+ (t/is (= (get-in copy-clean-t' font-size-path-0) "14"))
+ (t/is (= (get-in copy-clean-t' text-path-0) "bye"))
+
+
+ ;;;;;;;;;;; Font size copy
+ ;; Before the switch:
+ ;; * font size 25
+ ;; * text "hello world"
+ (t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-font-size-t text-path-0) "hello world"))
+
+ ;; After the switch:
+ ;; * font size 25 (the override is preserved)
+ ;; * text "bye" (value of c02, because there was no override)
+ (t/is (= (get-in copy-font-size-t' font-size-path-0) "25"))
+ (t/is (= (get-in copy-font-size-t' text-path-0) "bye"))
+
+ ;;;;;;;;;;; Text copy
+ ;; Before the switch:
+ ;; * font size 14
+ ;; * text "text overriden"
+ (t/is (= (get-in copy-text-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-text-t text-path-0) "text overriden"))
+
+ ;; After the switch:
+ ;; * font size 14 (value of c02, because there was no override)
+ ;; * text "text overriden" (value of c02: the override is not preserved)
+ (t/is (= (get-in copy-text-t' font-size-path-0) "14"))
+ (t/is (= (get-in copy-text-t' text-path-0) "bye"))
+
+ ;;;;;;;;;;; Both copy
+ ;; Before the switch:
+ ;; * font size 25
+ ;; * text "text overriden"
+ (t/is (= (get-in copy-both-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-both-t text-path-0) "text overriden"))
+
+ ;; After the switch:
+ ;; * font size 25 (the override is preserved)
+ ;; * text "text overriden" (value of c02: the override is not preserved)
+ (t/is (= (get-in copy-both-t' font-size-path-0) "25"))
+ (t/is (= (get-in copy-both-t' text-path-0) "bye"))))
+
+
+(t/deftest test-switch-with-different-text-and-prop-text-override
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ ;; The second component has a different text and prop
+ (thv/add-variant-with-text
+ :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "bye")
+ (update-attr :t02 font-size-path-0 "50")
+
+ (thc/instantiate-component :c01
+ :copy-clean
+ :children-labels [:copy-clean-t])
+ (thc/instantiate-component :c01
+ :copy-font-size
+ :children-labels [:copy-font-size-t])
+ (thc/instantiate-component :c01
+ :copy-text
+ :children-labels [:copy-text-t])
+ (thc/instantiate-component :c01
+ :copy-both
+ :children-labels [:copy-both-t]))
+
+
+ ;; The copy clean has no overrides
+ copy-clean (ths/get-shape file :copy-clean)
+ copy-clean-t (ths/get-shape file :copy-clean-t)
+
+ ;; Override font size on copy-font-size
+ file (update-attr file :copy-font-size-t font-size-path-0 "25")
+ copy-font-size (ths/get-shape file :copy-font-size)
+ copy-font-size-t (ths/get-shape file :copy-font-size-t)
+
+ ;; Override text on copy-text
+ file (update-attr file :copy-text-t text-path-0 "text overriden")
+ copy-text (ths/get-shape file :copy-text)
+ copy-text-t (ths/get-shape file :copy-text-t)
+
+ ;; Override both on copy-both
+ file (update-attr file :copy-both-t font-size-path-0 "25")
+ file (update-attr file :copy-both-t text-path-0 "text overriden")
+ copy-both (ths/get-shape file :copy-both)
+ copy-both-t (ths/get-shape file :copy-both-t)
+
+
+ ;; ==== Action: Switch all the copies
+ file' (-> file
+ (tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
+ (tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
+ (tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
+ (tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
+ page' (thf/current-page file')
+ copy-clean' (ths/get-shape file' :copy-clean-2)
+ copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
+
+ copy-font-size' (ths/get-shape file' :copy-font-size-2)
+ copy-font-size-t' (get-in page' [:objects (-> copy-font-size' :shapes first)])
+
+ copy-text' (ths/get-shape file' :copy-text-2)
+ copy-text-t' (get-in page' [:objects (-> copy-text' :shapes first)])
+
+ copy-both' (ths/get-shape file' :copy-both-2)
+ copy-both-t' (get-in page' [:objects (-> copy-both' :shapes first)])]
+
+ (thf/dump-file file' {:keys [:name #_:content]})
+
+
+ ;;;;;;;;;;; Clean copy
+ ;; Before the switch:
+ ;; * font size 14
+ ;; * text "hello world"
+ (t/is (= (get-in copy-clean-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-clean-t text-path-0) "hello world"))
+
+ ;; After the switch:
+ ;; * font size 50 (value of c02, because there was no override)
+ ;; * text "bye" (value of c02, because there was no override)
+ (t/is (= (get-in copy-clean-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-clean-t' text-path-0) "bye"))
+
+
+ ;;;;;;;;;;; Font size copy
+ ;; Before the switch:
+ ;; * font size 25
+ ;; * text "hello world"
+ (t/is (= (get-in copy-font-size-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-font-size-t text-path-0) "hello world"))
+
+ ;; After the switch:
+ ;; * font size 50 (value of c02: the override is not preserved)
+ ;; * text "bye" (value of c02, because there was no override)
+ (t/is (= (get-in copy-font-size-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-font-size-t' text-path-0) "bye"))
+
+ ;;;;;;;;;;; Text copy
+ ;; Before the switch:
+ ;; * font size 14
+ ;; * text "text overriden"
+ (t/is (= (get-in copy-text-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-text-t text-path-0) "text overriden"))
+
+ ;; After the switch:
+ ;; * font size 50 (value of c02, because there was no override)
+ ;; * text "bye" (value of c02: the override is not preserved)
+ (t/is (= (get-in copy-text-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-text-t' text-path-0) "bye"))
+
+ ;;;;;;;;;;; Both copy
+ ;; Before the switch:
+ ;; * font size 25
+ ;; * text "text overriden"
+ (t/is (= (get-in copy-both-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-both-t text-path-0) "text overriden"))
+
+ ;; After the switch:
+ ;; * font size 50 (value of c02: the override is not preserved)
+ ;; * text "bye" (value of c02: the override is not preserved)
+ (t/is (= (get-in copy-both-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-both-t' text-path-0) "bye"))))
+
+
+(t/deftest test-switch-with-identical-structure-text-override
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ ;; Both components are identical: have the same text and props
+ (thv/add-variant-with-text
+ :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "hello world")
+ (thc/instantiate-component :c01
+ :copy-structure-clean
+ :children-labels [:copy-structure-clean-t])
+ (thc/instantiate-component :c01
+ :copy-structure-unif
+ :children-labels [:copy-structure-unif-t])
+ (thc/instantiate-component :c01
+ :copy-structure-mixed
+ :children-labels [:copy-structure-mixed-t]))
+
+
+
+ ;; Duplicate a text line in copy-structure-clean
+ file (change-structure file :copy-structure-clean-t)
+ copy-structure-clean (ths/get-shape file :copy-structure-clean)
+ copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
+
+ ;; Duplicate a text line in copy-structure-clean, updating
+ ;; both lines with the same attrs
+ file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
+ (change-structure :copy-structure-unif-t))
+ copy-structure-unif (ths/get-shape file :copy-structure-unif)
+ copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
+
+ ;; Duplicate a text line in copy-structure-clean, updating
+ ;; each line with a different attr
+ file (-> (change-structure file :copy-structure-mixed-t)
+ (update-attr :copy-structure-mixed-t font-size-path-0 "35")
+ (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
+ copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
+ copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
+
+
+ ;; ==== Action: Switch all the copies
+ file' (-> file
+ (tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
+ (tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
+ (tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
+ page' (thf/current-page file')
+ copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
+ copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
+
+ copy-structure-unif' (ths/get-shape file' :copy-structure-unif-2)
+ copy-structure-unif-t' (get-in page' [:objects (-> copy-structure-unif' :shapes first)])
+
+ copy-structure-mixed' (ths/get-shape file' :copy-structure-mixed-2)
+ copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]
+
+ (thf/dump-file file' {:keys [:name #_:content]})
+
+ ;;;;;;;;;;; Copy structure clean
+ ;; Before the switch, first line:
+ ;; * font size 14
+ ;; * text "new line 1"
+ ;; Second line:
+ ;; * font size 14
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-clean-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-structure-clean-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-clean-t font-size-path-1) "14"))
+ (t/is (= (get-in copy-structure-clean-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 14 (value of c02, because there was no override)
+ ;; * text "new line 1" (the override is preserved)
+ ;; Second line:
+ ;; * font size 14 (value of c02, because there was no override)
+ ;; * text "new line 2" (the override is preserved)
+ (t/is (= (get-in copy-structure-clean-t' font-size-path-0) "14"))
+ (t/is (= (get-in copy-structure-clean-t' text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-clean-t' font-size-path-1) "14"))
+ (t/is (= (get-in copy-structure-clean-t' text-path-1) "new line 2"))
+
+ ;;;;;;;;;;; Copy structure unif
+ ;; Before the switch, first line:
+ ;; * font size 25
+ ;; * text "new line 1"
+ ;; Second line:
+ ;; * font size 25
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-unif-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-structure-unif-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-unif-t font-size-path-1) "25"))
+ (t/is (= (get-in copy-structure-unif-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 25 (the override is preserved)
+ ;; * text "new line 1" (the override is preserved)
+ ;; Second line:
+ ;; * font size 25 (the override is preserved)
+ ;; * text "new line 2" (the override is preserved)
+ (t/is (= (get-in copy-structure-unif-t' font-size-path-0) "25"))
+ (t/is (= (get-in copy-structure-unif-t' text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-unif-t' font-size-path-1) "25"))
+ (t/is (= (get-in copy-structure-unif-t' text-path-1) "new line 2"))
+
+ ;;;;;;;;;;; Copy structure mixed
+ ;; Before the switch, first line:
+ ;; * font size 35
+ ;; * text "new line 1"
+ ;; Before the switch, second line:
+ ;; * font size 40
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-mixed-t font-size-path-0) "35"))
+ (t/is (= (get-in copy-structure-mixed-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-mixed-t font-size-path-1) "40"))
+ (t/is (= (get-in copy-structure-mixed-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 35 (the override is preserved)
+ ;; * text "new line 1" (the override is preserved)
+ ;; Second line:
+ ;; * font size 40 (the override is preserved)
+ ;; * text "new line 2" (the override is preserved)
+ (t/is (= (get-in copy-structure-mixed-t' font-size-path-0) "35"))
+ (t/is (= (get-in copy-structure-mixed-t' text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-mixed-t' font-size-path-1) "40"))
+ (t/is (= (get-in copy-structure-mixed-t' text-path-1) "new line 2"))))
+
+
+(t/deftest test-switch-with-different-prop-structure-text-override
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ ;; The second component has a different prop
+ (thv/add-variant-with-text
+ :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "hello world")
+ (update-attr :t02 font-size-path-0 "50")
+ (thc/instantiate-component :c01
+ :copy-structure-clean
+ :children-labels [:copy-structure-clean-t])
+ (thc/instantiate-component :c01
+ :copy-structure-unif
+ :children-labels [:copy-structure-unif-t])
+ (thc/instantiate-component :c01
+ :copy-structure-mixed
+ :children-labels [:copy-structure-mixed-t]))
+
+
+
+ ;; Duplicate a text line in copy-structure-clean
+ file (change-structure file :copy-structure-clean-t)
+ copy-structure-clean (ths/get-shape file :copy-structure-clean)
+ copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
+
+ ;; Duplicate a text line in copy-structure-clean, updating
+ ;; both lines with the same attrs
+ file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
+ (change-structure :copy-structure-unif-t))
+ copy-structure-unif (ths/get-shape file :copy-structure-unif)
+ copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
+
+ ;; Duplicate a text line in copy-structure-clean, updating
+ ;; each line with a different attr
+ file (-> (change-structure file :copy-structure-mixed-t)
+ (update-attr :copy-structure-mixed-t font-size-path-0 "35")
+ (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
+ copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
+ copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
+
+
+ ;; ==== Action: Switch all the copies
+ file' (-> file
+ (tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
+ (tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
+ (tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
+ page' (thf/current-page file')
+ copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
+ copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
+
+ copy-structure-unif' (ths/get-shape file' :copy-structure-unif-2)
+ copy-structure-unif-t' (get-in page' [:objects (-> copy-structure-unif' :shapes first)])
+
+ copy-structure-mixed' (ths/get-shape file' :copy-structure-mixed-2)
+ copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]
+
+ (thf/dump-file file' {:keys [:name #_:content]})
+
+ ;;;;;;;;;;; Copy structure clean
+ ;; Before the switch, first line:
+ ;; * font size 14
+ ;; * text "new line 1"
+ ;; Second line:
+ ;; * font size 14
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-clean-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-structure-clean-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-clean-t font-size-path-1) "14"))
+ (t/is (= (get-in copy-structure-clean-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 50 (value of c02, because there was no override)
+ ;; * text "new line 1" (the override is preserved)
+ ;; Second line:
+ ;; * font size 50 (value of c02, because there was no override)
+ ;; * text "new line 2" (the override is preserved)
+ (t/is (= (get-in copy-structure-clean-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-structure-clean-t' text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-clean-t' font-size-path-1) "50"))
+ (t/is (= (get-in copy-structure-clean-t' text-path-1) "new line 2"))
+
+ ;;;;;;;;;;; Copy structure unif
+ ;; Before the switch, first line:
+ ;; * font size 25
+ ;; * text "new line 1"
+ ;; Second line:
+ ;; * font size 25
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-unif-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-structure-unif-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-unif-t font-size-path-1) "25"))
+ (t/is (= (get-in copy-structure-unif-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 50 (the override is not preserved)
+ ;; * text "new line 1" (the override is preserved)
+ ;; Second line:
+ ;; * font size 50 (the override is not preserved)
+ ;; * text "new line 2" (the override is preserved)
+ (t/is (= (get-in copy-structure-unif-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-structure-unif-t' text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-unif-t' font-size-path-1) "50"))
+ (t/is (= (get-in copy-structure-unif-t' text-path-1) "new line 2"))
+
+ ;;;;;;;;;;; Copy structure mixed
+ ;; Before the switch, first line:
+ ;; * font size 35
+ ;; * text "new line 1"
+ ;; Before the switch, second line:
+ ;; * font size 40
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-mixed-t font-size-path-0) "35"))
+ (t/is (= (get-in copy-structure-mixed-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-mixed-t font-size-path-1) "40"))
+ (t/is (= (get-in copy-structure-mixed-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 50 (the override is not preserved)
+ ;; * text "hello world" (the override is not preserved)
+ ;; No second line
+ (t/is (= (get-in copy-structure-mixed-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-structure-mixed-t' text-path-0) "hello world"))
+ (t/is (nil? (get-in copy-structure-mixed-t' font-size-path-1)))))
+
+(t/deftest test-switch-with-different-text-structure-text-override
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ ;; Second comp has different text
+ (thv/add-variant-with-text
+ :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "bye")
+ (thc/instantiate-component :c01
+ :copy-structure-clean
+ :children-labels [:copy-structure-clean-t])
+ (thc/instantiate-component :c01
+ :copy-structure-unif
+ :children-labels [:copy-structure-unif-t])
+ (thc/instantiate-component :c01
+ :copy-structure-mixed
+ :children-labels [:copy-structure-mixed-t]))
+
+
+
+ ;; Duplicate a text line in copy-structure-clean
+ file (change-structure file :copy-structure-clean-t)
+ copy-structure-clean (ths/get-shape file :copy-structure-clean)
+ copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
+
+ ;; Duplicate a text line in copy-structure-clean, updating
+ ;; both lines with the same attrs
+ file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
+ (change-structure :copy-structure-unif-t))
+ copy-structure-unif (ths/get-shape file :copy-structure-unif)
+ copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
+
+ ;; Duplicate a text line in copy-structure-clean, updating
+ ;; each line with a different attr
+ file (-> (change-structure file :copy-structure-mixed-t)
+ (update-attr :copy-structure-mixed-t font-size-path-0 "35")
+ (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
+ copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
+ copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
+
+
+ ;; ==== Action: Switch all the copies
+ file' (-> file
+ (tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
+ (tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
+ (tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
+ page' (thf/current-page file')
+ copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
+ copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
+
+ copy-structure-unif' (ths/get-shape file' :copy-structure-unif-2)
+ copy-structure-unif-t' (get-in page' [:objects (-> copy-structure-unif' :shapes first)])
+
+ copy-structure-mixed' (ths/get-shape file' :copy-structure-mixed-2)
+ copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]
+
+ (thf/dump-file file' {:keys [:name #_:content]})
+
+ ;;;;;;;;;;; Copy structure clean
+ ;; Before the switch, first line:
+ ;; * font size 14
+ ;; * text "new line 1"
+ ;; Second line:
+ ;; * font size 14
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-clean-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-structure-clean-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-clean-t font-size-path-1) "14"))
+ (t/is (= (get-in copy-structure-clean-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 14 (value of c02, because there was no override)
+ ;; * text "bye" (the override is not preserved)
+ ;; No second line
+ (t/is (= (get-in copy-structure-clean-t' font-size-path-0) "14"))
+ (t/is (= (get-in copy-structure-clean-t' text-path-0) "bye"))
+ (t/is (nil? (get-in copy-structure-clean-t' font-size-path-1)))
+
+
+ ;;;;;;;;;;; Copy structure unif
+ ;; Before the switch, first line:
+ ;; * font size 25
+ ;; * text "new line 1"
+ ;; Second line:
+ ;; * font size 25
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-unif-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-structure-unif-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-unif-t font-size-path-1) "25"))
+ (t/is (= (get-in copy-structure-unif-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 25 (the override is preserved)
+ ;; * text "bye" (the override is not preserved)
+ ;; No second line
+ (t/is (= (get-in copy-structure-unif-t' font-size-path-0) "25"))
+ (t/is (= (get-in copy-structure-unif-t' text-path-0) "bye"))
+ (t/is (nil? (get-in copy-structure-unif-t' font-size-path-1)))
+
+
+ ;;;;;;;;;;; Copy structure mixed
+ ;; Before the switch, first line:
+ ;; * font size 35
+ ;; * text "new line 1"
+ ;; Before the switch, second line:
+ ;; * font size 40
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-mixed-t font-size-path-0) "35"))
+ (t/is (= (get-in copy-structure-mixed-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-mixed-t font-size-path-1) "40"))
+ (t/is (= (get-in copy-structure-mixed-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 14 (the override is not preserved)
+ ;; * text "bye" (the override is not preserved)
+ ;; No second line
+ (t/is (= (get-in copy-structure-mixed-t' font-size-path-0) "14"))
+ (t/is (= (get-in copy-structure-mixed-t' text-path-0) "bye"))
+ (t/is (nil? (get-in copy-structure-mixed-t' font-size-path-1)))))
+
+(t/deftest test-switch-with-different-text-and-prop-structure-text-override
+ (let [;; ==== Setup
+ file (-> (thf/sample-file :file1)
+ ;; The second component has a different text and prop
+ (thv/add-variant-with-text
+ :v01 :c01 :m01 :c02 :m02 :t01 :t02 "hello world" "bye")
+ (update-attr :t02 font-size-path-0 "50")
+ (thc/instantiate-component :c01
+ :copy-structure-clean
+ :children-labels [:copy-structure-clean-t])
+ (thc/instantiate-component :c01
+ :copy-structure-unif
+ :children-labels [:copy-structure-unif-t])
+ (thc/instantiate-component :c01
+ :copy-structure-mixed
+ :children-labels [:copy-structure-mixed-t]))
+
+
+
+ ;; Duplicate a text line in copy-structure-clean
+ file (change-structure file :copy-structure-clean-t)
+ copy-structure-clean (ths/get-shape file :copy-structure-clean)
+ copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
+
+ ;; Duplicate a text line in copy-structure-clean, updating
+ ;; both lines with the same attrs
+ file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
+ (change-structure :copy-structure-unif-t))
+ copy-structure-unif (ths/get-shape file :copy-structure-unif)
+ copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
+
+ ;; Duplicate a text line in copy-structure-clean, updating
+ ;; each line with a different attr
+ file (-> (change-structure file :copy-structure-mixed-t)
+ (update-attr :copy-structure-mixed-t font-size-path-0 "35")
+ (update-attr :copy-structure-mixed-t font-size-path-1 "40"))
+ copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
+ copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
+
+
+ ;; ==== Action: Switch all the copies
+ file' (-> file
+ (tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
+ (tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
+ (tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
+ page' (thf/current-page file')
+ copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
+ copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
+
+ copy-structure-unif' (ths/get-shape file' :copy-structure-unif-2)
+ copy-structure-unif-t' (get-in page' [:objects (-> copy-structure-unif' :shapes first)])
+
+ copy-structure-mixed' (ths/get-shape file' :copy-structure-mixed-2)
+ copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]
+
+ (thf/dump-file file' {:keys [:name #_:content]})
+
+ ;;;;;;;;;;; Copy structure clean
+ ;; Before the switch, first line:
+ ;; * font size 14
+ ;; * text "new line 1"
+ ;; Second line:
+ ;; * font size 14
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-clean-t font-size-path-0) "14"))
+ (t/is (= (get-in copy-structure-clean-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-clean-t font-size-path-1) "14"))
+ (t/is (= (get-in copy-structure-clean-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 50 (value of c02, because there was no override)
+ ;; * text "bye" (the override is not preserved)
+ ;; No second line
+ (t/is (= (get-in copy-structure-clean-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-structure-clean-t' text-path-0) "bye"))
+ (t/is (nil? (get-in copy-structure-clean-t' font-size-path-1)))
+
+
+ ;;;;;;;;;;; Copy structure unif
+ ;; Before the switch, first line:
+ ;; * font size 25
+ ;; * text "new line 1"
+ ;; Second line:
+ ;; * font size 25
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-unif-t font-size-path-0) "25"))
+ (t/is (= (get-in copy-structure-unif-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-unif-t font-size-path-1) "25"))
+ (t/is (= (get-in copy-structure-unif-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 50 (the override is not preserved)
+ ;; * text "bye" (the override is not preserved)
+ ;; No second line
+ (t/is (= (get-in copy-structure-unif-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-structure-unif-t' text-path-0) "bye"))
+ (t/is (nil? (get-in copy-structure-unif-t' font-size-path-1)))
+
+
+ ;;;;;;;;;;; Copy structure mixed
+ ;; Before the switch, first line:
+ ;; * font size 35
+ ;; * text "new line 1"
+ ;; Before the switch, second line:
+ ;; * font size 40
+ ;; * text "new line 2"
+ (t/is (= (get-in copy-structure-mixed-t font-size-path-0) "35"))
+ (t/is (= (get-in copy-structure-mixed-t text-path-0) "new line 1"))
+ (t/is (= (get-in copy-structure-mixed-t font-size-path-1) "40"))
+ (t/is (= (get-in copy-structure-mixed-t text-path-1) "new line 2"))
+
+ ;; After the switch, first line:
+ ;; * font size 50 (the override is not preserved)
+ ;; * text "bye" (the override is not preserved)
+ ;; No second line
+ (t/is (= (get-in copy-structure-mixed-t' font-size-path-0) "50"))
+ (t/is (= (get-in copy-structure-mixed-t' text-path-0) "bye"))
+ (t/is (nil? (get-in copy-structure-mixed-t' font-size-path-1)))))
\ No newline at end of file
diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc
index 095c044048..e2ac474139 100644
--- a/common/test/common_tests/types/tokens_lib_test.cljc
+++ b/common/test/common_tests/types/tokens_lib_test.cljc
@@ -14,6 +14,7 @@
[app.common.time :as dt]
[app.common.transit :as tr]
[app.common.types.tokens-lib :as ctob]
+ [clojure.datafy :refer [datafy]]
[clojure.test :as t]))
(defn setup-virtual-time
@@ -72,14 +73,14 @@
:modified-at now
:tokens [])]
- (t/is (= (:name token-set1) "test-token-set-1"))
- (t/is (= (:description token-set1) ""))
- (t/is (some? (:modified-at token-set1)))
- (t/is (empty? (:tokens token-set1)))
- (t/is (= (:name token-set2) "test-token-set-2"))
- (t/is (= (:description token-set2) "test description"))
- (t/is (= (:modified-at token-set2) now))
- (t/is (empty? (:tokens token-set2)))))
+ (t/is (= (ctob/get-name token-set1) "test-token-set-1"))
+ (t/is (= (ctob/get-description token-set1) ""))
+ (t/is (some? (ctob/get-modified-at token-set1)))
+ (t/is (empty? (ctob/get-tokens-map token-set1)))
+ (t/is (= (ctob/get-name token-set2) "test-token-set-2"))
+ (t/is (= (ctob/get-description token-set2) "test description"))
+ (t/is (= (ctob/get-modified-at token-set2) now))
+ (t/is (empty? (ctob/get-tokens-map token-set2)))))
(t/deftest make-invalid-token-set
(let [params {:name 777 :description 999}]
@@ -183,7 +184,7 @@
:type :boolean
:value true)})))
expected (-> (ctob/get-set tokens-lib "A")
- (get :tokens)
+ (ctob/get-tokens-map)
(ctob/tokens-tree))]
(t/is (= (get-in expected ["foo" "bar" "baz" :name]) "foo.bar.baz"))
(t/is (= (get-in expected ["foo" "bar" "bam" :name]) "foo.bar.bam"))
@@ -249,20 +250,18 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "test-token-set"
(fn [token-set]
- (assoc token-set
- :description "some description")))
+ (ctob/set-description token-set "some description")))
(ctob/update-set "not-existing-set"
(fn [token-set]
- (assoc token-set
- :description "no-effect"))))
+ (ctob/set-description token-set "no-effect"))))
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")]
(t/is (= (ctob/set-count tokens-lib') 1))
- (t/is (= (:name token-set') "test-token-set"))
- (t/is (= (:description token-set') "some description"))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+ (t/is (= (ctob/get-name token-set') "test-token-set"))
+ (t/is (= (ctob/get-description token-set') "some description"))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest rename-token-set
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -271,15 +270,14 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "test-token-set"
(fn [token-set]
- (assoc token-set
- :name "updated-name"))))
+ (ctob/rename token-set "updated-name"))))
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "updated-name")]
(t/is (= (ctob/set-count tokens-lib') 1))
- (t/is (= (:name token-set') "updated-name"))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+ (t/is (= (ctob/get-name token-set') "updated-name"))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest rename-token-set-group
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -323,11 +321,11 @@
:type :boolean
:value true)})))
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
- token (get-in token-set-copy [:tokens "test-token"])]
+ token (ctob/get-token token-set-copy "test-token")]
(t/is (some? token-set-copy))
- (t/is (= (:name token-set-copy) "test-token-set-copy"))
- (t/is (= (count (:tokens token-set-copy)) 1))
+ (t/is (= (ctob/get-name token-set-copy) "test-token-set-copy"))
+ (t/is (= (count (ctob/get-tokens-map token-set-copy)) 1))
(t/is (= (:name token) "test-token"))))
(t/deftest duplicate-token-set-twice
@@ -341,11 +339,11 @@
tokens-lib (ctob/add-set tokens-lib (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"}))
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
- token (get-in token-set-copy [:tokens "test-token"])]
+ token (ctob/get-token token-set-copy "test-token")]
(t/is (some? token-set-copy))
- (t/is (= (:name token-set-copy) "test-token-set-copy-2"))
- (t/is (= (count (:tokens token-set-copy)) 1))
+ (t/is (= (ctob/get-name token-set-copy) "test-token-set-copy-2"))
+ (t/is (= (count (ctob/get-tokens-map token-set-copy)) 1))
(t/is (= (:name token) "test-token"))))
(t/deftest duplicate-empty-token-set
@@ -353,11 +351,11 @@
(ctob/add-set (ctob/make-token-set :name "test-token-set")))
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
- tokens (get token-set-copy :tokens)]
+ tokens (ctob/get-tokens-map token-set-copy)]
(t/is (some? token-set-copy))
- (t/is (= (:name token-set-copy) "test-token-set-copy"))
- (t/is (= (count (:tokens token-set-copy)) 0))
+ (t/is (= (ctob/get-name token-set-copy) "test-token-set-copy"))
+ (t/is (= (count (ctob/get-tokens-map token-set-copy)) 0))
(t/is (= (count tokens) 0))))
(t/deftest duplicate-not-existing-token-set
@@ -392,12 +390,12 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
- token' (get-in token-set' [:tokens "test-token"])]
+ token' (ctob/get-token token-set' "test-token")]
(t/is (= (ctob/set-count tokens-lib') 1))
- (t/is (= (count (:tokens token-set')) 1))
+ (t/is (= (count (ctob/get-tokens-map token-set')) 1))
(t/is (= (:name token') "test-token"))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest update-token
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -428,16 +426,16 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
- token (get-in token-set [:tokens "test-token-1"])
- token' (get-in token-set' [:tokens "test-token-1"])]
+ token (ctob/get-token token-set "test-token-1")
+ token' (ctob/get-token token-set' "test-token-1")]
(t/is (= (ctob/set-count tokens-lib') 1))
- (t/is (= (count (:tokens token-set')) 2))
- (t/is (= (d/index-of (keys (:tokens token-set')) "test-token-1") 0))
+ (t/is (= (count (ctob/get-tokens-map token-set')) 2))
+ (t/is (= (d/index-of (keys (ctob/get-tokens-map token-set')) "test-token-1") 0))
(t/is (= (:name token') "test-token-1"))
(t/is (= (:description token') "some description"))
(t/is (= (:value token') false))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest rename-token
@@ -460,16 +458,16 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
- token (get-in token-set [:tokens "test-token-1"])
- token' (get-in token-set' [:tokens "updated-name"])]
+ token (ctob/get-token token-set "test-token-1")
+ token' (ctob/get-token token-set' "updated-name")]
(t/is (= (ctob/set-count tokens-lib') 1))
- (t/is (= (count (:tokens token-set')) 2))
- (t/is (= (d/index-of (keys (:tokens token-set')) "updated-name") 0))
+ (t/is (= (count (ctob/get-tokens-map token-set')) 2))
+ (t/is (= (d/index-of (keys (ctob/get-tokens-map token-set')) "updated-name") 0))
(t/is (= (:name token') "updated-name"))
(t/is (= (:description token') ""))
(t/is (= (:value token') true))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest delete-token
@@ -486,12 +484,12 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
- token' (get-in token-set' [:tokens "test-token"])]
+ token' (ctob/get-token token-set' "test-token")]
(t/is (= (ctob/set-count tokens-lib') 1))
- (t/is (= (count (:tokens token-set')) 0))
+ (t/is (= (count (ctob/get-tokens-map token-set')) 0))
(t/is (nil? token'))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest get-ordered-sets
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -897,7 +895,7 @@
:value true)))
set (ctob/get-set tokens-lib "test-token-set")
- tokens-list (vals (:tokens set))]
+ tokens-list (ctob/get-tokens set)]
(t/is (= (count tokens-list) 5))
(t/is (= (:name (nth tokens-list 0)) "token1"))
@@ -931,14 +929,14 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
- token (get-in token-set [:tokens "group1.test-token-2"])
- token' (get-in token-set' [:tokens "group1.test-token-2"])]
+ token (ctob/get-token token-set "group1.test-token-2")
+ token' (ctob/get-token token-set' "group1.test-token-2")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (:name token') "group1.test-token-2"))
(t/is (= (:description token') "some description"))
(t/is (= (:value token') false))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest update-token-in-sets-rename
@@ -965,14 +963,14 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
- token (get-in token-set [:tokens "group1.test-token-2"])
- token' (get-in token-set' [:tokens "group1.updated-name"])]
+ token (ctob/get-token token-set "group1.test-token-2")
+ token' (ctob/get-token token-set' "group1.updated-name")]
(t/is (= (ctob/set-count tokens-lib') 1))
(t/is (= (:name token') "group1.updated-name"))
(t/is (= (:description token') ""))
(t/is (= (:value token') true))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (:ctob/get-modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest move-token-of-group
@@ -999,15 +997,15 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
- token (get-in token-set [:tokens "group1.test-token-2"])
- token' (get-in token-set' [:tokens "group2.updated-name"])]
+ token (ctob/get-token token-set "group1.test-token-2")
+ token' (ctob/get-token token-set' "group2.updated-name")]
(t/is (= (ctob/set-count tokens-lib') 1))
- (t/is (= (d/index-of (keys (:tokens token-set')) "group2.updated-name") 1))
+ (t/is (= (d/index-of (keys (ctob/get-tokens-map token-set')) "group2.updated-name") 1))
(t/is (= (:name token') "group2.updated-name"))
(t/is (= (:description token') ""))
(t/is (= (:value token') true))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))
(t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
(t/deftest delete-token-in-group
@@ -1026,12 +1024,12 @@
token-set (ctob/get-set tokens-lib "test-token-set")
token-set' (ctob/get-set tokens-lib' "test-token-set")
- token' (get-in token-set' [:tokens "group1.test-token-2"])]
+ token' (ctob/get-token token-set' "group1.test-token-2")]
(t/is (= (ctob/set-count tokens-lib') 1))
- (t/is (= (count (:tokens token-set')) 1))
+ (t/is (= (count (ctob/get-tokens-map token-set')) 1))
(t/is (nil? token'))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest update-token-set-in-groups
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -1044,7 +1042,7 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "group1/token-set-2"
(fn [token-set]
- (assoc token-set :description "some description"))))
+ (ctob/set-description token-set "some description"))))
sets-tree (ctob/get-set-tree tokens-lib)
sets-tree' (ctob/get-set-tree tokens-lib')
@@ -1055,9 +1053,9 @@
(t/is (= (ctob/set-count tokens-lib') 5))
(t/is (= (count group1') 3))
(t/is (= (d/index-of (keys group1') "S-token-set-2") 0))
- (t/is (= (:name token-set') "group1/token-set-2"))
- (t/is (= (:description token-set') "some description"))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+ (t/is (= (ctob/get-name token-set') "group1/token-set-2"))
+ (t/is (= (ctob/get-description token-set') "some description"))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest rename-token-set-in-groups
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -1070,8 +1068,7 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "group1/token-set-2"
(fn [token-set]
- (assoc token-set
- :name "group1/updated-name"))))
+ (ctob/rename token-set "group1/updated-name"))))
sets-tree (ctob/get-set-tree tokens-lib)
sets-tree' (ctob/get-set-tree tokens-lib')
@@ -1082,9 +1079,9 @@
(t/is (= (ctob/set-count tokens-lib') 5))
(t/is (= (count group1') 3))
(t/is (= (d/index-of (keys group1') "S-updated-name") 0))
- (t/is (= (:name token-set') "group1/updated-name"))
- (t/is (= (:description token-set') ""))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+ (t/is (= (ctob/get-name token-set') "group1/updated-name"))
+ (t/is (= (ctob/get-description token-set') ""))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest move-token-set-of-group
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -1097,8 +1094,7 @@
tokens-lib' (-> tokens-lib
(ctob/update-set "group1/token-set-2"
(fn [token-set]
- (assoc token-set
- :name "group2/updated-name"))))
+ (ctob/rename token-set "group2/updated-name"))))
sets-tree (ctob/get-set-tree tokens-lib)
sets-tree' (ctob/get-set-tree tokens-lib')
@@ -1111,9 +1107,9 @@
(t/is (= (count group1') 2))
(t/is (= (count group2') 1))
(t/is (nil? (get group1' "S-updated-name")))
- (t/is (= (:name token-set') "group2/updated-name"))
- (t/is (= (:description token-set') ""))
- (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+ (t/is (= (ctob/get-name token-set') "group2/updated-name"))
+ (t/is (= (ctob/get-description token-set') ""))
+ (t/is (dt/is-after? (ctob/get-modified-at token-set') (ctob/get-modified-at token-set)))))
(t/deftest delete-token-set-in-group
(let [tokens-lib (-> (ctob/make-tokens-lib)
@@ -1413,7 +1409,7 @@
tokens-lib' (ctob/parse-decoded-json encoded "")]
(t/testing "library got updated but data is equal"
(t/is (not= tokens-lib' tokens-lib))
- (t/is (= @tokens-lib' @tokens-lib)))))))
+ (t/is (= (datafy tokens-lib') (datafy tokens-lib))))))))
#?(:clj
(t/deftest export-dtcg-json-with-default-theme
diff --git a/common/test/common_tests/variant_test.cljc b/common/test/common_tests/variant_test.cljc
index 433f008573..05bc748703 100644
--- a/common/test/common_tests/variant_test.cljc
+++ b/common/test/common_tests/variant_test.cljc
@@ -15,13 +15,13 @@
map-with-two-props-dashes [{:name "border" :value "no"} {:name "color" :value "--"}]
map-with-one-prop [{:name "border" :value "no"}]
map-with-equal [{:name "border" :value "yes color=yes"}]
- map-with-spaces [{:name "border 1" :value "of course"}
- {:name "color 2" :value "dark gray"}
- {:name "background 3" :value "anoth€r co-lor"}]
+ map-with-spaces [{:name "border (1)" :value "of course"}
+ {:name "color (2)" :value "dark gray"}
+ {:name "background (3)" :value "anoth€r co-lor"}]
string-valid-with-two-props "border=yes, color=gray"
string-valid-with-one-prop "border=no"
- string-valid-with-spaces "border 1=of course, color 2=dark gray, background 3=anoth€r co-lor"
+ string-valid-with-spaces "border (1)=of course, color (2)=dark gray, background (3)=anoth€r co-lor"
string-valid-with-no-value "border=no, color="
string-valid-with-dashes "border=no, color=--"
string-valid-with-equal "border=yes color=yes"
@@ -131,3 +131,31 @@
(t/is (= (ctv/same-variant? components-2) false))
(t/is (= (ctv/same-variant? components-3) false))
(t/is (= (ctv/same-variant? components-4) false)))))
+
+
+(t/deftest update-number-in-repeated-item
+ (let [names ["border" "color" "color 1" "color 2" "color (1)" "color (7)" "area 51"]]
+
+ (t/testing "update-number-in-repeated-item"
+ (t/is (= (ctv/update-number-in-repeated-item names "background") "background"))
+ (t/is (= (ctv/update-number-in-repeated-item names "border") "border (1)"))
+ (t/is (= (ctv/update-number-in-repeated-item names "color") "color (2)"))
+ (t/is (= (ctv/update-number-in-repeated-item names "color 1") "color 1 (1)"))
+ (t/is (= (ctv/update-number-in-repeated-item names "color (1)") "color (2)"))
+ (t/is (= (ctv/update-number-in-repeated-item names "area 51") "area 51 (1)")))))
+
+
+(t/deftest update-number-in-repeated-prop-names
+ (let [props [{:name "color" :value "yellow"}
+ {:name "color" :value "blue"}
+ {:name "color" :value "red"}
+ {:name "border (1)" :value "no"}
+ {:name "border (1)" :value "yes"}]
+ numbered-props [{:name "color" :value "yellow"}
+ {:name "color (1)" :value "blue"}
+ {:name "color (2)" :value "red"}
+ {:name "border (1)" :value "no"}
+ {:name "border (2)" :value "yes"}]]
+
+ (t/testing "update-number-in-repeated-prop-names"
+ (t/is (= (ctv/update-number-in-repeated-prop-names props) numbered-props)))))
diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile
index a7c5ff8265..c938bc9a58 100644
--- a/docker/devenv/Dockerfile
+++ b/docker/devenv/Dockerfile
@@ -26,8 +26,6 @@ RUN set -ex; \
build-essential autoconf libtool pkg-config
-COPY files/apt.sources /etc/apt/sources.list.d/ubuntu.sources
-
################################################################################
## IMAGE MAGICK
################################################################################
diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml
index 6de50d6684..b546301643 100644
--- a/docker/devenv/docker-compose.yaml
+++ b/docker/devenv/docker-compose.yaml
@@ -96,6 +96,10 @@ services:
- ./files/postgresql.conf:/etc/postgresql.conf:z
- ./files/postgresql_init.sql:/docker-entrypoint-initdb.d/init.sql:z
- postgres_data_pg16:/var/lib/postgresql/data
+ networks:
+ default:
+ aliases:
+ - postgres
redis:
image: valkey/valkey:8.1
diff --git a/docker/devenv/files/entrypoint.sh b/docker/devenv/files/entrypoint.sh
index a1cabc2aaf..1427b19148 100755
--- a/docker/devenv/files/entrypoint.sh
+++ b/docker/devenv/files/entrypoint.sh
@@ -10,7 +10,7 @@ cp /root/.bashrc /home/penpot/.bashrc
cp /root/.vimrc /home/penpot/.vimrc
cp /root/.tmux.conf /home/penpot/.tmux.conf
-chown -R penpot:users /home/penpot
+chown penpot:users /home/penpot
rsync -ar --chown=penpot:users /opt/cargo/ /home/penpot/.cargo/
export PATH="/home/penpot/.cargo/bin:$PATH"
diff --git a/docs/technical-guide/developer/backend.md b/docs/technical-guide/developer/backend.md
index 1a0c69d013..0f60c928d7 100644
--- a/docs/technical-guide/developer/backend.md
+++ b/docs/technical-guide/developer/backend.md
@@ -4,14 +4,22 @@ title: 3.06. Backend Guide
# Backend guide #
-This guide intends to explain the essential details of the backend
-application.
-
+This guide collects some basic information on the backend application.
## REPL ##
-In the devenv environment you can execute scripts/repl to open a
-Clojure interactive shell ([REPL](https://codewith.mu/en/tutorials/1.0/repl)).
+_Note:_ When in development mode, the backend spins up a traditional nREPL socket on port 6064.
+If you are experimenting locally, you can connect to it using your Clojure editor or
+with `backend/scripts/nrepl`, which starts a [REPLy client](https://github.com/trptcolin/reply),
+[see here][1] for more information.
+
+[1]: /technical-guide/developer/devenv/#backend
+
+In the devenv environment you can execute `backend/scripts/repl` to open a
+Clojure interactive shell ([REPL](https://codewith.mu/en/tutorials/1.0/repl)) (this is not a socket-based
+REPL, but a local, in-process console (over stdin/stdout) with some fancy line-editing and colors). Note
+that the backend must be stopped before executing this script, otherwise it will fail with `Port already
+in use: 9090`.
Once there, you can execute (restart) to load and execute the backend
process, or to reload it after making changes to the source code.
@@ -39,11 +47,11 @@ For example:
## Fixtures ##
-This is a development feature that allows populate the database with a
-good amount of content (usually used for just test the application or
-perform performance tweaks on queries).
+This is a development feature that allows populating the database with a
+good amount of content (typically used to test the application or to run
+performance tweaks on queries).
-In order to load fixtures, enter to the REPL environment with the scripts/repl
+In order to load fixtures, enter the REPL environment with the backend/scripts/repl
script, and then execute (app.cli.fixtures/run {:preset :small}).
You also can execute this as a standalone script with:
@@ -52,11 +60,11 @@ You also can execute this as a standalone script with:
clojure -Adev -X:fn-fixtures
```
-NOTE: It is an optional step because the application can start with an
+_NOTE:_ This is an optional step because the application can start with an
empty database.
-This by default will create a bunch of users that can be used to login
-in the application. All users uses the following pattern:
+The above will create several users that can be used to login
+into the application. All of them follow the pattern:
- Username: profileN@example.com
- Password: 123123
diff --git a/docs/technical-guide/developer/devenv.md b/docs/technical-guide/developer/devenv.md
index 7e572c50fe..3c98df3484 100644
--- a/docs/technical-guide/developer/devenv.md
+++ b/docs/technical-guide/developer/devenv.md
@@ -170,6 +170,23 @@ similar to a webmail client. Simply navigate to:
[http://localhost:1080](http://localhost:1080)
+## Create user
+
+You can register a new user manually, or create new users automatically with this script. From your tmux instance, run:
+
+
+```sh
+cd penpot/backend/scripts
+python3 manage.py create-profile
+```
+
+You can also skip tutorial and walkthrough steps:
+
+```sh
+python3 manage.py create-profile --skip-tutorial --skip-walkthrough
+python3 manage.py create-profile -n "Jane Doe" -e jane@example.com -p secretpassword --skip-tutorial --skip-walkthrough
+```
+
## Team Feature Flags
To test a Feature Flag, you can enable or disable them by team through the `dbg` page:
diff --git a/docs/technical-guide/getting-started/docker.md b/docs/technical-guide/getting-started/docker.md
index 5608c6882d..2e82d8d629 100644
--- a/docs/technical-guide/getting-started/docker.md
+++ b/docs/technical-guide/getting-started/docker.md
@@ -89,6 +89,13 @@ For instance, if the registration is disabled, the only way to create a new use
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile
```
+or
+
+```bash
+docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile --skip-tutorial --skip-walkthrough
+```
+
+
**NOTE:** the exact container name depends on your docker version and platform.
For example it could be penpot-penpot-backend-1 or penpot_penpot-backend-1.
You can check the correct name executing docker ps.
diff --git a/docs/technical-guide/getting-started/index.md b/docs/technical-guide/getting-started/index.md
index 175cf99195..38f8f939bc 100644
--- a/docs/technical-guide/getting-started/index.md
+++ b/docs/technical-guide/getting-started/index.md
@@ -28,5 +28,6 @@ Use Docker if you already know the tool, if need full control of the process or
and do not want to depend on any external provider, or need to do any special customization.
-Or you can try other options ,
-offered by Penpot community.
+Or you can try [other options][1], offered by Penpot community.
+
+[1]: /technical-guide/getting-started/unofficial-options/
diff --git a/docs/user-guide/layer-basics/index.njk b/docs/user-guide/layer-basics/index.njk
index ff17a77738..317857423c 100644
--- a/docs/user-guide/layer-basics/index.njk
+++ b/docs/user-guide/layer-basics/index.njk
@@ -281,12 +281,15 @@ press Shift/⇧ + left click over the right arrow of a group or a boa
Focus mode
-Select the elements of a page you want to work with in a specific moment hiding the rest so they don’t get in the way of your attention. This option is also useful to improve the performance in cases where the page has a large number of elements.
+Focus mode zooms into the elements of a page you want to work with in a specific moment, and hides the rest so that they don’t get in the way. When the page has many elements, focus mode can also improve performance.
To activate focus mode:
-
+
Select one or more elements.
- Right click to show the menu and select the option "Focus on" or press F .
+ Right click on the selection to show the menu and select the option “Focus on” or press F .
+Notice that the layer panel will now only show the focused layers. A focus mode status line will also appear at the top.
+To exit focus mode and return to the original viewport and selection, right click anywhere and select “Focus off” or just press F again. You can also click anywhere on the focus mode status line at the top of the layer panel.
+
diff --git a/frontend/.gitignore b/frontend/.gitignore
index dd3776ebd3..e3da84e51a 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -11,3 +11,4 @@ node_modules/
/blob-report/
/playwright/.cache/
/playwright/**/visual-specs/**/*.png
+
diff --git a/frontend/deps.edn b/frontend/deps.edn
index 2fc983a630..86ced3d563 100644
--- a/frontend/deps.edn
+++ b/frontend/deps.edn
@@ -20,8 +20,8 @@
:git/url "https://github.com/funcool/beicon.git"}
funcool/rumext
- {:git/tag "v2.22"
- :git/sha "92879b6"
+ {:git/tag "v2.24"
+ :git/sha "17a0c94"
:git/url "https://github.com/funcool/rumext.git"}
instaparse/instaparse {:mvn/version "1.5.0"}
@@ -42,7 +42,7 @@
:dev
{:extra-paths ["dev"]
:extra-deps
- {thheller/shadow-cljs {:mvn/version "3.1.5"}
+ {thheller/shadow-cljs {:mvn/version "3.1.7"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "RELEASE"}
diff --git a/frontend/package.json b/frontend/package.json
index f067bbef1e..e78b0932cb 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -103,6 +103,7 @@
"@penpot/draft-js": "portal:./vendor/draft-js",
"@penpot/hljs": "portal:./vendor/hljs",
"@penpot/mousetrap": "portal:./vendor/mousetrap",
+ "@penpot/plugins-runtime": "1.3.2",
"@penpot/svgo": "penpot/svgo#v3.1",
"@penpot/text-editor": "portal:./text-editor",
"@tokens-studio/sd-transforms": "1.2.11",
diff --git a/frontend/playwright.config.js b/frontend/playwright.config.js
index 8360cb4561..7496f7f4c8 100644
--- a/frontend/playwright.config.js
+++ b/frontend/playwright.config.js
@@ -53,6 +53,21 @@ export default defineConfig({
toHaveScreenshot: { maxDiffPixelRatio: 0.005 },
},
},
+ {
+ name: "render-wasm",
+ use: {
+ ...devices["Desktop Chrome"],
+ viewport: { width: 1920, height: 1080 }, // Add custom viewport size
+ deviceScaleFactor: 2,
+ },
+ testDir: "./playwright/ui/render-wasm-specs",
+ snapshotPathTemplate: "{testDir}/{testFilePath}-snapshots/{arg}.png",
+ expect: {
+ toHaveScreenshot: {
+ maxDiffPixelRatio: 0.001,
+ },
+ },
+ },
],
/* Run your local dev server before starting the tests */
diff --git a/frontend/playwright/data/register/prepare-register-profile-email-mismatch.json b/frontend/playwright/data/register/prepare-register-profile-email-mismatch.json
new file mode 100644
index 0000000000..d35c0ea932
--- /dev/null
+++ b/frontend/playwright/data/register/prepare-register-profile-email-mismatch.json
@@ -0,0 +1,5 @@
+{
+ "~:type": "~:restriction",
+ "~:code": "~:email-does-not-match-invitation",
+ "~:hint": "email should match the invitation"
+}
diff --git a/frontend/playwright/data/render-wasm/assets/ebgaramond.ttf b/frontend/playwright/data/render-wasm/assets/ebgaramond.ttf
new file mode 100644
index 0000000000..f8c490b19d
Binary files /dev/null and b/frontend/playwright/data/render-wasm/assets/ebgaramond.ttf differ
diff --git a/frontend/playwright/data/render-wasm/assets/firacode.ttf b/frontend/playwright/data/render-wasm/assets/firacode.ttf
new file mode 100644
index 0000000000..3a57209a97
Binary files /dev/null and b/frontend/playwright/data/render-wasm/assets/firacode.ttf differ
diff --git a/frontend/playwright/data/render-wasm/assets/landscape.jpg b/frontend/playwright/data/render-wasm/assets/landscape.jpg
new file mode 100644
index 0000000000..b579b7f9ab
Binary files /dev/null and b/frontend/playwright/data/render-wasm/assets/landscape.jpg differ
diff --git a/frontend/playwright/data/render-wasm/assets/mreaves.ttf b/frontend/playwright/data/render-wasm/assets/mreaves.ttf
new file mode 100644
index 0000000000..d787d46701
Binary files /dev/null and b/frontend/playwright/data/render-wasm/assets/mreaves.ttf differ
diff --git a/frontend/playwright/data/render-wasm/assets/nodesto-condensed.ttf b/frontend/playwright/data/render-wasm/assets/nodesto-condensed.ttf
new file mode 100644
index 0000000000..5ba2824b49
Binary files /dev/null and b/frontend/playwright/data/render-wasm/assets/nodesto-condensed.ttf differ
diff --git a/frontend/playwright/data/render-wasm/assets/notocoloremojisubset.ttf b/frontend/playwright/data/render-wasm/assets/notocoloremojisubset.ttf
new file mode 100644
index 0000000000..652020e18f
Binary files /dev/null and b/frontend/playwright/data/render-wasm/assets/notocoloremojisubset.ttf differ
diff --git a/frontend/playwright/data/render-wasm/assets/notosansjpsubset.ttf b/frontend/playwright/data/render-wasm/assets/notosansjpsubset.ttf
new file mode 100644
index 0000000000..093152f386
Binary files /dev/null and b/frontend/playwright/data/render-wasm/assets/notosansjpsubset.ttf differ
diff --git a/frontend/playwright/data/render-wasm/assets/pattern.png b/frontend/playwright/data/render-wasm/assets/pattern.png
new file mode 100644
index 0000000000..01e7c84b82
Binary files /dev/null and b/frontend/playwright/data/render-wasm/assets/pattern.png differ
diff --git a/frontend/playwright/data/render-wasm/assets/penguins.jpg b/frontend/playwright/data/render-wasm/assets/penguins.jpg
new file mode 100644
index 0000000000..96b69f1336
Binary files /dev/null and b/frontend/playwright/data/render-wasm/assets/penguins.jpg differ
diff --git a/frontend/playwright/data/render-wasm/get-file-blend-modes.json b/frontend/playwright/data/render-wasm/get-file-blend-modes.json
new file mode 100644
index 0000000000..5db8b41763
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-blend-modes.json
@@ -0,0 +1,4595 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "variants/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~: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": "Blend Modes",
+ "~:revn": 77,
+ "~:modified-at": "~m1749564878395",
+ "~:vern": 0,
+ "~:id": "~uc0939f58-37bc-805d-8006-51cdf8e18e76",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-add-partial-text-touched-flags",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0004-clean-shadow-and-colors",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u53a7ff09-2228-81d3-8006-4b5ea964593b",
+ "~:created-at": "~m1749564227465",
+ "~:data": {
+ "~:pages": [
+ "~uc0939f58-37bc-805d-8006-51cdf8e18e77"
+ ],
+ "~:pages-index": {
+ "~uc0939f58-37bc-805d-8006-51cdf8e18e77": {
+ "~:objects": {
+ "~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
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~: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,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~ua40b06a7-8291-8016-8006-51cdff29090d",
+ "~ua40b06a7-8291-8016-8006-51ce15316f2f",
+ "~ua40b06a7-8291-8016-8006-51ce29981ae5",
+ "~ua40b06a7-8291-8016-8006-51ce95ceb45c",
+ "~ua40b06a7-8291-8016-8006-51cec0b7d886",
+ "~ua40b06a7-8291-8016-8006-51cf011ebdaa",
+ "~ua40b06a7-8291-8016-8006-51cfc7775b96",
+ "~ua40b06a7-8291-8016-8006-51d018fe482c",
+ "~ua40b06a7-8291-8016-8006-51cfe77a3ba8",
+ "~ua40b06a7-8291-8016-8006-51cfff4ec08a",
+ "~ua40b06a7-8291-8016-8006-51cf294c69ae",
+ "~ua40b06a7-8291-8016-8006-51cf38fe0cfe",
+ "~ua40b06a7-8291-8016-8006-51ced0237745",
+ "~ua40b06a7-8291-8016-8006-51cedcaa3495",
+ "~ua40b06a7-8291-8016-8006-51cea3bdf924",
+ "~ua40b06a7-8291-8016-8006-51ceaec37289",
+ "~ua40b06a7-8291-8016-8006-51ce041bd6ac",
+ "~ua40b06a7-8291-8016-8006-51cfb7043b7a",
+ "~ua40b06a7-8291-8016-8006-51ce15316f30",
+ "~ua40b06a7-8291-8016-8006-51cfb1a9abf2",
+ "~ua40b06a7-8291-8016-8006-51ce29981ae4",
+ "~ua40b06a7-8291-8016-8006-51cfa96e2dbb",
+ "~ua40b06a7-8291-8016-8006-51ce95ceb45d",
+ "~ua40b06a7-8291-8016-8006-51cf95b40639",
+ "~ua40b06a7-8291-8016-8006-51cec0b7d887",
+ "~ua40b06a7-8291-8016-8006-51cf8dad1fe8",
+ "~ua40b06a7-8291-8016-8006-51cf011ebdab",
+ "~ua40b06a7-8291-8016-8006-51cf5060dd29",
+ "~ua40b06a7-8291-8016-8006-51cfc7775b95",
+ "~ua40b06a7-8291-8016-8006-51cfe77a3ba9",
+ "~ua40b06a7-8291-8016-8006-51d018fe482a",
+ "~ua40b06a7-8291-8016-8006-51cfff4f2739",
+ "~ua40b06a7-8291-8016-8006-51cf294c69af",
+ "~ua40b06a7-8291-8016-8006-51cf498a8953",
+ "~ua40b06a7-8291-8016-8006-51cfc7775b94",
+ "~ua40b06a7-8291-8016-8006-51cfe77a3baa",
+ "~ua40b06a7-8291-8016-8006-51d018fe482b",
+ "~ua40b06a7-8291-8016-8006-51cfff4f273a",
+ "~ua40b06a7-8291-8016-8006-51cf38fe0cff",
+ "~ua40b06a7-8291-8016-8006-51cf64d90820",
+ "~ua40b06a7-8291-8016-8006-51ced0237746",
+ "~ua40b06a7-8291-8016-8006-51cf565b4e8f",
+ "~ua40b06a7-8291-8016-8006-51cedcaa3496",
+ "~ua40b06a7-8291-8016-8006-51cf5cb4b66b",
+ "~ua40b06a7-8291-8016-8006-51cea3bdf925",
+ "~ua40b06a7-8291-8016-8006-51cf9e2d56d9",
+ "~ua40b06a7-8291-8016-8006-51ceaec3728a",
+ "~ua40b06a7-8291-8016-8006-51cfa3d9a62a"
+ ]
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf38fe0cfe": {
+ "~#shape": {
+ "~:y": 620,
+ "~: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": 132.99996829032898,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1479.9999783039075,
+ "~:y": 620
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1612.9999465942365,
+ "~:y": 620
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1612.9999465942365,
+ "~:y": 753
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1479.9999783039075,
+ "~:y": 753
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf38fe0cfe",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1479.9999783039075,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1479.9999783039075,
+ "~:y": 620,
+ "~:width": 132.99996829032898,
+ "~:height": 133,
+ "~:x1": 1479.9999783039075,
+ "~:y1": 620,
+ "~:x2": 1612.9999465942365,
+ "~:y2": 753
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf38fe0cff": {
+ "~#shape": {
+ "~:y": 687,
+ "~: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,
+ "~:blend-mode": "~:exclusion",
+ "~:name": "Rectangle",
+ "~:width": 132.99991279836104,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1546.9999744296538,
+ "~:y": 687
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1679.9998872280148,
+ "~:y": 687
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1679.9998872280148,
+ "~:y": 820
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1546.9999744296538,
+ "~:y": 820
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf38fe0cff",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1546.9999744296538,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1546.9999744296538,
+ "~:y": 687,
+ "~:width": 132.99991279836104,
+ "~:height": 133,
+ "~:x1": 1546.9999744296538,
+ "~:y1": 687,
+ "~:x2": 1679.9998872280148,
+ "~:y2": 820
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ce95ceb45d": {
+ "~#shape": {
+ "~:y": 363.00001335144043,
+ "~: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,
+ "~:blend-mode": "~:color-burn",
+ "~:name": "Rectangle",
+ "~:width": 132.99993658065796,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1000.0000162124634,
+ "~:y": 363.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1132.9999527931213,
+ "~:y": 363.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1132.9999527931213,
+ "~:y": 496.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1000.0000162124634,
+ "~:y": 496.00001335144043
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ce95ceb45d",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1000.0000162124634,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1000.0000162124634,
+ "~:y": 363.00001335144043,
+ "~:width": 132.99993658065796,
+ "~:height": 133,
+ "~:x1": 1000.0000162124634,
+ "~:y1": 363.00001335144043,
+ "~:x2": 1132.9999527931213,
+ "~:y2": 496.00001335144043
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ce95ceb45c": {
+ "~#shape": {
+ "~:y": 296.00001335144043,
+ "~: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": 132.99993658065796,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 932.9999871253967,
+ "~:y": 296.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1065.9999237060547,
+ "~:y": 296.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1065.9999237060547,
+ "~:y": 429.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 932.9999871253967,
+ "~:y": 429.00001335144043
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ce95ceb45c",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 932.9999871253967,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 932.9999871253967,
+ "~:y": 296.00001335144043,
+ "~:width": 132.99993658065796,
+ "~:height": 133,
+ "~:x1": 932.9999871253967,
+ "~:y1": 296.00001335144043,
+ "~:x2": 1065.9999237060547,
+ "~:y2": 429.00001335144043
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfb7043b7a": {
+ "~#shape": {
+ "~:y": 381.00000381469727,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:hide-in-viewer": false,
+ "~:name": "Rectangle",
+ "~:width": 132.9999841451645,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 208.0000078678131,
+ "~:y": 381.00000381469727
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 340.9999920129776,
+ "~:y": 381.00000381469727
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 340.9999920129776,
+ "~:y": 514.0000038146973
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 208.0000078678131,
+ "~:y": 514.0000038146973
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 0.5,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfb7043b7a",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 208.0000078678131,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 208.0000078678131,
+ "~:y": 381.00000381469727,
+ "~:width": 132.9999841451645,
+ "~:height": 133,
+ "~:x1": 208.0000078678131,
+ "~:y1": 381.00000381469727,
+ "~:x2": 340.9999920129776,
+ "~:y2": 514.0000038146973
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfff4f273a": {
+ "~#shape": {
+ "~:y": 1054.9999718368053,
+ "~: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,
+ "~:blend-mode": "~:color",
+ "~:name": "Rectangle",
+ "~:width": 132.99996829032898,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 755.000010490419,
+ "~:y": 1054.9999718368053
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 887.9999787807479,
+ "~:y": 1054.9999718368053
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 887.9999787807479,
+ "~:y": 1187.9999718368053
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 755.000010490419,
+ "~:y": 1187.9999718368053
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfff4f273a",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 755.000010490419,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 755.000010490419,
+ "~:y": 1054.9999718368053,
+ "~:width": 132.99996829032898,
+ "~:height": 133,
+ "~:x1": 755.000010490419,
+ "~:y1": 1054.9999718368053,
+ "~:x2": 887.9999787807479,
+ "~:y2": 1187.9999718368053
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfa96e2dbb": {
+ "~#shape": {
+ "~:y": 380.9999885559082,
+ "~: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,
+ "~:blend-mode": "~:multiply",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 748.0000076293945,
+ "~:y": 380.9999885559082
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 881.0000076293945,
+ "~:y": 380.9999885559082
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 881.0000076293945,
+ "~:y": 513.9999885559082
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 748.0000076293945,
+ "~:y": 513.9999885559082
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfa96e2dbb",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 748.0000076293945,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 748.0000076293945,
+ "~:y": 380.9999885559082,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 748.0000076293945,
+ "~:y1": 380.9999885559082,
+ "~:x2": 881.0000076293945,
+ "~:y2": 513.9999885559082
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfff4f2739": {
+ "~#shape": {
+ "~:y": 1028.9999394118786,
+ "~: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,
+ "~:blend-mode": "~:color",
+ "~:name": "Rectangle",
+ "~:width": 132.99996829032898,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 733.000019550324,
+ "~:y": 1028.9999394118786
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 865.999987840653,
+ "~:y": 1028.9999394118786
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 865.999987840653,
+ "~:y": 1161.9999394118786
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 733.000019550324,
+ "~:y": 1161.9999394118786
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfff4f2739",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 733.000019550324,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 733.000019550324,
+ "~:y": 1028.9999394118786,
+ "~:width": 132.99996829032898,
+ "~:height": 133,
+ "~:x1": 733.000019550324,
+ "~:y1": 1028.9999394118786,
+ "~:x2": 865.999987840653,
+ "~:y2": 1161.9999394118786
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf95b40639": {
+ "~#shape": {
+ "~:y": 380.99999809265137,
+ "~: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,
+ "~:blend-mode": "~:color-burn",
+ "~:name": "Rectangle",
+ "~:width": 132.99992072583007,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1016.0000183582259,
+ "~:y": 380.99999809265137
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1148.999939084056,
+ "~:y": 380.99999809265137
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1148.999939084056,
+ "~:y": 513.9999980926514
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1016.0000183582259,
+ "~:y": 513.9999980926514
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf95b40639",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1016.0000183582259,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1016.0000183582259,
+ "~:y": 380.99999809265137,
+ "~:width": 132.99992072583007,
+ "~:height": 133,
+ "~:x1": 1016.0000183582259,
+ "~:y1": 380.99999809265137,
+ "~:x2": 1148.999939084056,
+ "~:y2": 513.9999980926514
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf9e2d56d9": {
+ "~#shape": {
+ "~:y": 376.0000059604704,
+ "~: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,
+ "~:blend-mode": "~:lighten",
+ "~:name": "Rectangle",
+ "~:width": 132.99996829032898,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1288.9999544620514,
+ "~:y": 376.0000059604704
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1421.9999227523804,
+ "~:y": 376.0000059604704
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1421.9999227523804,
+ "~:y": 509.0000376701414
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1288.9999544620514,
+ "~:y": 509.0000376701414
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf9e2d56d9",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1288.9999544620514,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1288.9999544620514,
+ "~:y": 376.0000059604704,
+ "~:width": 132.99996829032898,
+ "~:height": 133.00003170967102,
+ "~:x1": 1288.9999544620514,
+ "~:y1": 376.0000059604704,
+ "~:x2": 1421.9999227523804,
+ "~:y2": 509.0000376701414
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133.00003170967102,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfc7775b96": {
+ "~#shape": {
+ "~:y": 962,
+ "~: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": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 121.99999380111694,
+ "~:y": 962
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 254.99999380111694,
+ "~:y": 962
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 254.99999380111694,
+ "~:y": 1095
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 121.99999380111694,
+ "~:y": 1095
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfc7775b96",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 121.99999380111694,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 121.99999380111694,
+ "~:y": 962,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 121.99999380111694,
+ "~:y1": 962,
+ "~:x2": 254.99999380111694,
+ "~:y2": 1095
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cedcaa3496": {
+ "~#shape": {
+ "~:y": 687.0000152587891,
+ "~: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,
+ "~:blend-mode": "~:soft-light",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 724.000011920929,
+ "~:y": 687.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 857.000011920929,
+ "~:y": 687.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 857.000011920929,
+ "~:y": 820.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 724.000011920929,
+ "~:y": 820.0000152587891
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cedcaa3496",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 724.000011920929,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 724.000011920929,
+ "~:y": 687.0000152587891,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 724.000011920929,
+ "~:y1": 687.0000152587891,
+ "~:x2": 857.000011920929,
+ "~:y2": 820.0000152587891
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfc7775b94": {
+ "~#shape": {
+ "~:y": 1055.0000324249268,
+ "~: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,
+ "~:blend-mode": "~:hue",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 210.99998998641968,
+ "~:y": 1055.0000324249268
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 343.9999899864197,
+ "~:y": 1055.0000324249268
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 343.9999899864197,
+ "~:y": 1188.0000324249268
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 210.99998998641968,
+ "~:y": 1188.0000324249268
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfc7775b94",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 210.99998998641968,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 210.99998998641968,
+ "~:y": 1055.0000324249268,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 210.99998998641968,
+ "~:y1": 1055.0000324249268,
+ "~:x2": 343.9999899864197,
+ "~:y2": 1188.0000324249268
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51d018fe482b": {
+ "~#shape": {
+ "~:y": 1046.0000324249268,
+ "~: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,
+ "~:blend-mode": "~:luminosity",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1028.9999327659607,
+ "~:y": 1046.0000324249268
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1161.9999327659607,
+ "~:y": 1046.0000324249268
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1161.9999327659607,
+ "~:y": 1179.0000324249268
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1028.9999327659607,
+ "~:y": 1179.0000324249268
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51d018fe482b",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1028.9999327659607,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1028.9999327659607,
+ "~:y": 1046.0000324249268,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 1028.9999327659607,
+ "~:y1": 1046.0000324249268,
+ "~:x2": 1161.9999327659607,
+ "~:y2": 1179.0000324249268
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cedcaa3495": {
+ "~#shape": {
+ "~:y": 620.0000152587891,
+ "~: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": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 657.000011920929,
+ "~:y": 620.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 790.000011920929,
+ "~:y": 620.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 790.000011920929,
+ "~:y": 753.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 657.000011920929,
+ "~:y": 753.0000152587891
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cedcaa3495",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 657.000011920929,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 657.000011920929,
+ "~:y": 620.0000152587891,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 657.000011920929,
+ "~:y1": 620.0000152587891,
+ "~:x2": 790.000011920929,
+ "~:y2": 753.0000152587891
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfc7775b95": {
+ "~#shape": {
+ "~:y": 1029,
+ "~: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,
+ "~:blend-mode": "~:hue",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 188.99999380111694,
+ "~:y": 1029
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 321.99999380111694,
+ "~:y": 1029
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 321.99999380111694,
+ "~:y": 1162
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 188.99999380111694,
+ "~:y": 1162
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfc7775b95",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 188.99999380111694,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 188.99999380111694,
+ "~:y": 1029,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 188.99999380111694,
+ "~:y1": 1029,
+ "~:x2": 321.99999380111694,
+ "~:y2": 1162
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51d018fe482a": {
+ "~#shape": {
+ "~:y": 1020,
+ "~: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,
+ "~:blend-mode": "~:luminosity",
+ "~:name": "Rectangle",
+ "~:width": 132.99993658065796,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1007.0000319480907,
+ "~:y": 1020
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1139.9999685287487,
+ "~:y": 1020
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1139.9999685287487,
+ "~:y": 1153
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1007.0000319480907,
+ "~:y": 1153
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51d018fe482a",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1007.0000319480907,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1007.0000319480907,
+ "~:y": 1020,
+ "~:width": 132.99993658065796,
+ "~:height": 133,
+ "~:x1": 1007.0000319480907,
+ "~:y1": 1020,
+ "~:x2": 1139.9999685287487,
+ "~:y2": 1153
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfb1a9abf2": {
+ "~#shape": {
+ "~:y": 377.99999237060547,
+ "~: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,
+ "~:blend-mode": "~:darken",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 475.99999237060547,
+ "~:y": 377.99999237060547
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 608.9999923706055,
+ "~:y": 377.99999237060547
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 608.9999923706055,
+ "~:y": 510.99999237060547
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 475.99999237060547,
+ "~:y": 510.99999237060547
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfb1a9abf2",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 475.99999237060547,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 475.99999237060547,
+ "~:y": 377.99999237060547,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 475.99999237060547,
+ "~:y1": 377.99999237060547,
+ "~:x2": 608.9999923706055,
+ "~:y2": 510.99999237060547
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf498a8953": {
+ "~#shape": {
+ "~:y": 712.9999523162842,
+ "~: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,
+ "~:blend-mode": "~:difference",
+ "~:name": "Rectangle",
+ "~:width": 132.99988108872617,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1290.9999830126974,
+ "~:y": 712.9999523162842
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1423.9998641014236,
+ "~:y": 712.9999523162842
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1423.9998641014236,
+ "~:y": 845.9999523162842
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1290.9999830126974,
+ "~:y": 845.9999523162842
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf498a8953",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1290.9999830126974,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1290.9999830126974,
+ "~:y": 712.9999523162842,
+ "~:width": 132.99988108872617,
+ "~:height": 133,
+ "~:x1": 1290.9999830126974,
+ "~:y1": 712.9999523162842,
+ "~:x2": 1423.9998641014236,
+ "~:y2": 845.9999523162842
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51d018fe482c": {
+ "~#shape": {
+ "~:y": 953,
+ "~: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": 132.99993658065796,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 940.0000028610241,
+ "~:y": 953
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1072.999939441682,
+ "~:y": 953
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1072.999939441682,
+ "~:y": 1086
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 940.0000028610241,
+ "~:y": 1086
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51d018fe482c",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 940.0000028610241,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 940.0000028610241,
+ "~:y": 953,
+ "~:width": 132.99993658065796,
+ "~:height": 133,
+ "~:x1": 940.0000028610241,
+ "~:y1": 953,
+ "~:x2": 1072.999939441682,
+ "~:y2": 1086
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ce15316f30": {
+ "~#shape": {
+ "~:y": 364,
+ "~: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,
+ "~:blend-mode": "~:darken",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 459,
+ "~:y": 364
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 592,
+ "~:y": 364
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 592,
+ "~:y": 497
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 459,
+ "~:y": 497
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ce15316f30",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 459,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 459,
+ "~:y": 364,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 459,
+ "~:y1": 364,
+ "~:x2": 592,
+ "~:y2": 497
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ce15316f2f": {
+ "~#shape": {
+ "~:y": 297,
+ "~: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": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 392,
+ "~:y": 297
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 525,
+ "~:y": 297
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 525,
+ "~:y": 430
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 392,
+ "~:y": 430
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ce15316f2f",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 392,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 392,
+ "~:y": 297,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 392,
+ "~:y1": 297,
+ "~:x2": 525,
+ "~:y2": 430
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf294c69ae": {
+ "~#shape": {
+ "~:y": 620,
+ "~: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": 132.99996829032898,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1200.9999477863294,
+ "~:y": 620
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1333.9999160766583,
+ "~:y": 620
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1333.9999160766583,
+ "~:y": 753
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1200.9999477863294,
+ "~:y": 753
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf294c69ae",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1200.9999477863294,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1200.9999477863294,
+ "~:y": 620,
+ "~:width": 132.99996829032898,
+ "~:height": 133,
+ "~:x1": 1200.9999477863294,
+ "~:y1": 620,
+ "~:x2": 1333.9999160766583,
+ "~:y2": 753
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf565b4e8f": {
+ "~#shape": {
+ "~:y": 705.0000066757202,
+ "~: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,
+ "~:blend-mode": "~:overlay",
+ "~:name": "Rectangle",
+ "~:width": 132.99999999999244,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 471.00003004074625,
+ "~:y": 705.0000066757202
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 604.0000300407387,
+ "~:y": 705.0000066757202
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 604.0000300407387,
+ "~:y": 838.0000700950623
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 471.00003004074625,
+ "~:y": 838.0000700950623
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf565b4e8f",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 471.00003004074625,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 471.00003004074625,
+ "~:y": 705.0000066757202,
+ "~:width": 132.99999999999244,
+ "~:height": 133.00006341934204,
+ "~:x1": 471.00003004074625,
+ "~:y1": 705.0000066757202,
+ "~:x2": 604.0000300407387,
+ "~:y2": 838.0000700950623
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133.00006341934204,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cdff29090d": {
+ "~#shape": {
+ "~:y": 297,
+ "~: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": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 123,
+ "~:y": 297
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 256,
+ "~:y": 297
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 256,
+ "~:y": 430
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 123,
+ "~:y": 430
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cdff29090d",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 123,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 123,
+ "~:y": 297,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 123,
+ "~:y1": 297,
+ "~:x2": 256,
+ "~:y2": 430
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf294c69af": {
+ "~#shape": {
+ "~:y": 687,
+ "~: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,
+ "~:blend-mode": "~:difference",
+ "~:name": "Rectangle",
+ "~:width": 132.99996829032898,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1267.99995470047,
+ "~:y": 687
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1400.999922990799,
+ "~:y": 687
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1400.999922990799,
+ "~:y": 820
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1267.99995470047,
+ "~:y": 820
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf294c69af",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1267.99995470047,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1267.99995470047,
+ "~:y": 687,
+ "~:width": 132.99996829032898,
+ "~:height": 133,
+ "~:x1": 1267.99995470047,
+ "~:y1": 687,
+ "~:x2": 1400.999922990799,
+ "~:y2": 820
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ce041bd6ac": {
+ "~#shape": {
+ "~:y": 364,
+ "~: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": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 190,
+ "~:y": 364
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 323,
+ "~:y": 364
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 323,
+ "~:y": 497
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 190,
+ "~:y": 497
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 0.5,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ce041bd6ac",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 190,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 190,
+ "~:y": 364,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 190,
+ "~:y1": 364,
+ "~:x2": 323,
+ "~:y2": 497
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfe77a3baa": {
+ "~#shape": {
+ "~:y": 1054.9999537467957,
+ "~: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,
+ "~:blend-mode": "~:saturation",
+ "~:name": "Rectangle",
+ "~:width": 132.99996829032898,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 479.00001049041896,
+ "~:y": 1054.9999537467957
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 611.9999787807479,
+ "~:y": 1054.9999537467957
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 611.9999787807479,
+ "~:y": 1187.9999537467957
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 479.00001049041896,
+ "~:y": 1187.9999537467957
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfe77a3baa",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 479.00001049041896,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 479.00001049041896,
+ "~:y": 1054.9999537467957,
+ "~:width": 132.99996829032898,
+ "~:height": 133,
+ "~:x1": 479.00001049041896,
+ "~:y1": 1054.9999537467957,
+ "~:x2": 611.9999787807479,
+ "~:y2": 1187.9999537467957
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfa3d9a62a": {
+ "~#shape": {
+ "~:y": 383.00002670288086,
+ "~: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,
+ "~:blend-mode": "~:screen",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1559.9999475479126,
+ "~:y": 383.00002670288086
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1692.9999475479126,
+ "~:y": 383.00002670288086
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1692.9999475479126,
+ "~:y": 516.0000267028809
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1559.9999475479126,
+ "~:y": 516.0000267028809
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfa3d9a62a",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1559.9999475479126,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1559.9999475479126,
+ "~:y": 383.00002670288086,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 1559.9999475479126,
+ "~:y1": 383.00002670288086,
+ "~:x2": 1692.9999475479126,
+ "~:y2": 516.0000267028809
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf011ebdaa": {
+ "~#shape": {
+ "~:y": 620,
+ "~: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": 132.99993658065796,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 939.0000100135767,
+ "~:y": 620
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1071.9999465942346,
+ "~:y": 620
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1071.9999465942346,
+ "~:y": 753
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 939.0000100135767,
+ "~:y": 753
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf011ebdaa",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 939.0000100135767,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 939.0000100135767,
+ "~:y": 620,
+ "~:width": 132.99993658065796,
+ "~:height": 133,
+ "~:x1": 939.0000100135767,
+ "~:y1": 620,
+ "~:x2": 1071.9999465942346,
+ "~:y2": 753
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfff4ec08a": {
+ "~#shape": {
+ "~:y": 961.9999394118786,
+ "~: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": 132.99996829029874,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 666.0000050067864,
+ "~:y": 961.9999394118786
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 798.9999732970851,
+ "~:y": 961.9999394118786
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 798.9999732970851,
+ "~:y": 1094.9999394118786
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 666.0000050067864,
+ "~:y": 1094.9999394118786
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfff4ec08a",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 666.0000050067864,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 666.0000050067864,
+ "~:y": 961.9999394118786,
+ "~:width": 132.99996829029874,
+ "~:height": 133,
+ "~:x1": 666.0000050067864,
+ "~:y1": 961.9999394118786,
+ "~:x2": 798.9999732970851,
+ "~:y2": 1094.9999394118786
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf5cb4b66b": {
+ "~#shape": {
+ "~:y": 704.0000343322754,
+ "~: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,
+ "~:blend-mode": "~:soft-light",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 740.0000348091125,
+ "~:y": 704.0000343322754
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 873.0000348091125,
+ "~:y": 704.0000343322754
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 873.0000348091125,
+ "~:y": 837.0000343322754
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 740.0000348091125,
+ "~:y": 837.0000343322754
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf5cb4b66b",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 740.0000348091125,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 740.0000348091125,
+ "~:y": 704.0000343322754,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 740.0000348091125,
+ "~:y1": 704.0000343322754,
+ "~:x2": 873.0000348091125,
+ "~:y2": 837.0000343322754
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ceaec3728a": {
+ "~#shape": {
+ "~:y": 363.00001335144043,
+ "~: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,
+ "~:blend-mode": "~:screen",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1537.9999437332153,
+ "~:y": 363.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1670.9999437332153,
+ "~:y": 363.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1670.9999437332153,
+ "~:y": 496.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1537.9999437332153,
+ "~:y": 496.00001335144043
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ceaec3728a",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1537.9999437332153,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1537.9999437332153,
+ "~:y": 363.00001335144043,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 1537.9999437332153,
+ "~:y1": 363.00001335144043,
+ "~:x2": 1670.9999437332153,
+ "~:y2": 496.00001335144043
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf011ebdab": {
+ "~#shape": {
+ "~:y": 687,
+ "~: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,
+ "~:blend-mode": "~:hard-light",
+ "~:name": "Rectangle",
+ "~:width": 132.99993658065796,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1006.0000314712524,
+ "~:y": 687
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1138.9999680519104,
+ "~:y": 687
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1138.9999680519104,
+ "~:y": 820
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1006.0000314712524,
+ "~:y": 820
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf011ebdab",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1006.0000314712524,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1006.0000314712524,
+ "~:y": 687,
+ "~:width": 132.99993658065796,
+ "~:height": 133,
+ "~:x1": 1006.0000314712524,
+ "~:y1": 687,
+ "~:x2": 1138.9999680519104,
+ "~:y2": 820
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf8dad1fe8": {
+ "~#shape": {
+ "~:y": 704.0000438690186,
+ "~: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,
+ "~:blend-mode": "~:color-dodge",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 208.00000619888306,
+ "~:y": 704.0000438690186
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 341.00000619888306,
+ "~:y": 704.0000438690186
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 341.00000619888306,
+ "~:y": 837.0000438690186
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 208.00000619888306,
+ "~:y": 837.0000438690186
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf8dad1fe8",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 208.00000619888306,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 208.00000619888306,
+ "~:y": 704.0000438690186,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 208.00000619888306,
+ "~:y1": 704.0000438690186,
+ "~:x2": 341.00000619888306,
+ "~:y2": 837.0000438690186
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfe77a3ba8": {
+ "~#shape": {
+ "~:y": 962.0000433921814,
+ "~: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": 132.99996829029874,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 390.0000026226006,
+ "~:y": 962.0000433921814
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 522.9999709128994,
+ "~:y": 962.0000433921814
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 522.9999709128994,
+ "~:y": 1095.0000433921814
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 390.0000026226006,
+ "~:y": 1095.0000433921814
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfe77a3ba8",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 390.0000026226006,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 390.0000026226006,
+ "~:y": 962.0000433921814,
+ "~:width": 132.99996829029874,
+ "~:height": 133,
+ "~:x1": 390.0000026226006,
+ "~:y1": 962.0000433921814,
+ "~:x2": 522.9999709128994,
+ "~:y2": 1095.0000433921814
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ceaec37289": {
+ "~#shape": {
+ "~:y": 296.00001335144043,
+ "~: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": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1470.9999437332153,
+ "~:y": 296.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1603.9999437332153,
+ "~:y": 296.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1603.9999437332153,
+ "~:y": 429.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1470.9999437332153,
+ "~:y": 429.00001335144043
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ceaec37289",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1470.9999437332153,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1470.9999437332153,
+ "~:y": 296.00001335144043,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 1470.9999437332153,
+ "~:y1": 296.00001335144043,
+ "~:x2": 1603.9999437332153,
+ "~:y2": 429.00001335144043
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cfe77a3ba9": {
+ "~#shape": {
+ "~:y": 1029.0000433921814,
+ "~: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,
+ "~:blend-mode": "~:saturation",
+ "~:name": "Rectangle",
+ "~:width": 132.9999999999925,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 456.99997615814823,
+ "~:y": 1029.0000433921814
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 589.9999761581407,
+ "~:y": 1029.0000433921814
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 589.9999761581407,
+ "~:y": 1162.0000433921814
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 456.99997615814823,
+ "~:y": 1162.0000433921814
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cfe77a3ba9",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 456.99997615814823,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 456.99997615814823,
+ "~:y": 1029.0000433921814,
+ "~:width": 132.9999999999925,
+ "~:height": 133,
+ "~:x1": 456.99997615814823,
+ "~:y1": 1029.0000433921814,
+ "~:x2": 589.9999761581407,
+ "~:y2": 1162.0000433921814
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf5060dd29": {
+ "~#shape": {
+ "~:y": 713.0000324249268,
+ "~: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,
+ "~:blend-mode": "~:hard-light",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1027.9999351501465,
+ "~:y": 713.0000324249268
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1160.9999351501465,
+ "~:y": 713.0000324249268
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1160.9999351501465,
+ "~:y": 846.0000324249268
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1027.9999351501465,
+ "~:y": 846.0000324249268
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf5060dd29",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1027.9999351501465,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1027.9999351501465,
+ "~:y": 713.0000324249268,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 1027.9999351501465,
+ "~:y1": 713.0000324249268,
+ "~:x2": 1160.9999351501465,
+ "~:y2": 846.0000324249268
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cec0b7d887": {
+ "~#shape": {
+ "~:y": 687.0000152587891,
+ "~: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,
+ "~:blend-mode": "~:color-dodge",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 191.00001192092896,
+ "~:y": 687.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 324.00001192092896,
+ "~:y": 687.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 324.00001192092896,
+ "~:y": 820.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 191.00001192092896,
+ "~:y": 820.0000152587891
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cec0b7d887",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 191.00001192092896,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 191.00001192092896,
+ "~:y": 687.0000152587891,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 191.00001192092896,
+ "~:y1": 687.0000152587891,
+ "~:x2": 324.00001192092896,
+ "~:y2": 820.0000152587891
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ced0237746": {
+ "~#shape": {
+ "~:y": 687.0000152587891,
+ "~: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,
+ "~:blend-mode": "~:overlay",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 455.00001192092896,
+ "~:y": 687.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 588.000011920929,
+ "~:y": 687.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 588.000011920929,
+ "~:y": 820.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 455.00001192092896,
+ "~:y": 820.0000152587891
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ced0237746",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 455.00001192092896,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 455.00001192092896,
+ "~:y": 687.0000152587891,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 455.00001192092896,
+ "~:y1": 687.0000152587891,
+ "~:x2": 588.000011920929,
+ "~:y2": 820.0000152587891
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cec0b7d886": {
+ "~#shape": {
+ "~:y": 620.0000152587891,
+ "~: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": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 124.00000429153442,
+ "~:y": 620.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 257.0000042915344,
+ "~:y": 620.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 257.0000042915344,
+ "~:y": 753.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 124.00000429153442,
+ "~:y": 753.0000152587891
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cec0b7d886",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 124.00000429153442,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 124.00000429153442,
+ "~:y": 620.0000152587891,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 124.00000429153442,
+ "~:y1": 620.0000152587891,
+ "~:x2": 257.0000042915344,
+ "~:y2": 753.0000152587891
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ced0237745": {
+ "~#shape": {
+ "~:y": 620.0000152587891,
+ "~: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": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 388.00001192092896,
+ "~:y": 620.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 521.000011920929,
+ "~:y": 620.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 521.000011920929,
+ "~:y": 753.0000152587891
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 388.00001192092896,
+ "~:y": 753.0000152587891
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ced0237745",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 388.00001192092896,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 388.00001192092896,
+ "~:y": 620.0000152587891,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 388.00001192092896,
+ "~:y1": 620.0000152587891,
+ "~:x2": 521.000011920929,
+ "~:y2": 753.0000152587891
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ce29981ae5": {
+ "~#shape": {
+ "~:y": 297,
+ "~: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": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 661,
+ "~:y": 297
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 794,
+ "~:y": 297
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 794,
+ "~:y": 430
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 661,
+ "~:y": 430
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ce29981ae5",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 661,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 661,
+ "~:y": 297,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 661,
+ "~:y1": 297,
+ "~:x2": 794,
+ "~:y2": 430
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cea3bdf925": {
+ "~#shape": {
+ "~:y": 363.00001335144043,
+ "~: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,
+ "~:blend-mode": "~:lighten",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1268.9999437332153,
+ "~:y": 363.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1401.9999437332153,
+ "~:y": 363.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1401.9999437332153,
+ "~:y": 496.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1268.9999437332153,
+ "~:y": 496.00001335144043
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cea3bdf925",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1268.9999437332153,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1268.9999437332153,
+ "~:y": 363.00001335144043,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 1268.9999437332153,
+ "~:y1": 363.00001335144043,
+ "~:x2": 1401.9999437332153,
+ "~:y2": 496.00001335144043
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51ce29981ae4": {
+ "~#shape": {
+ "~:y": 364,
+ "~: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,
+ "~:blend-mode": "~:multiply",
+ "~:name": "Rectangle",
+ "~:width": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 728,
+ "~:y": 364
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 861,
+ "~:y": 364
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 861,
+ "~:y": 497
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 728,
+ "~:y": 497
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51ce29981ae4",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 728,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 728,
+ "~:y": 364,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 728,
+ "~:y1": 364,
+ "~:x2": 861,
+ "~:y2": 497
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cea3bdf924": {
+ "~#shape": {
+ "~:y": 296.00001335144043,
+ "~: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": 133,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1201.9999437332153,
+ "~:y": 296.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1334.9999437332153,
+ "~:y": 296.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1334.9999437332153,
+ "~:y": 429.00001335144043
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1201.9999437332153,
+ "~:y": 429.00001335144043
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cea3bdf924",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1201.9999437332153,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1201.9999437332153,
+ "~:y": 296.00001335144043,
+ "~:width": 133,
+ "~:height": 133,
+ "~:x1": 1201.9999437332153,
+ "~:y1": 296.00001335144043,
+ "~:x2": 1334.9999437332153,
+ "~:y2": 429.00001335144043
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ },
+ "~ua40b06a7-8291-8016-8006-51cf64d90820": {
+ "~#shape": {
+ "~:y": 710.9999618530273,
+ "~: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,
+ "~:blend-mode": "~:exclusion",
+ "~:name": "Rectangle",
+ "~:width": 132.99996829029874,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1565.999957084684,
+ "~:y": 710.9999618530273
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1698.9999253749827,
+ "~:y": 710.9999618530273
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1698.9999253749827,
+ "~:y": 843.9999618530273
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1565.999957084684,
+ "~:y": 843.9999618530273
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:opacity": 1,
+ "~:id": "~ua40b06a7-8291-8016-8006-51cf64d90820",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1565.999957084684,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1565.999957084684,
+ "~:y": 710.9999618530273,
+ "~:width": 132.99996829029874,
+ "~:height": 133,
+ "~:x1": 1565.999957084684,
+ "~:y1": 710.9999618530273,
+ "~:x2": 1698.9999253749827,
+ "~:y2": 843.9999618530273
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 133,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~uc0939f58-37bc-805d-8006-51cdf8e18e77",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~uc0939f58-37bc-805d-8006-51cdf8e18e76",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-multiple-fills.json b/frontend/playwright/data/render-wasm/get-file-multiple-fills.json
new file mode 100644
index 0000000000..a182967562
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-multiple-fills.json
@@ -0,0 +1,633 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "variants/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~: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": "Multiple fills",
+ "~:revn": 19,
+ "~:modified-at": "~m1749564220299",
+ "~:vern": 0,
+ "~:id": "~uc0939f58-37bc-805d-8006-51cd3a51c255",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-add-partial-text-touched-flags",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0004-clean-shadow-and-colors",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u53a7ff09-2228-81d3-8006-4b5ea964593b",
+ "~:created-at": "~m1749564032332",
+ "~:data": {
+ "~:pages": [
+ "~uc0939f58-37bc-805d-8006-51cd3a51c256"
+ ],
+ "~:pages-index": {
+ "~uc0939f58-37bc-805d-8006-51cd3a51c256": {
+ "~:objects": {
+ "~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
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~: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,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~ub688a894-3697-80d3-8006-51cd477981bc",
+ "~ub688a894-3697-80d3-8006-51cd5504e381",
+ "~ub688a894-3697-80d3-8006-51cd5de7c5f3",
+ "~ub688a894-3697-80d3-8006-51cd67bc1de9"
+ ]
+ }
+ },
+ "~ub688a894-3697-80d3-8006-51cd477981bc": {
+ "~#shape": {
+ "~:y": 297,
+ "~: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": 153,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 239,
+ "~:y": 297
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 392,
+ "~:y": 297
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 392,
+ "~:y": 441
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 239,
+ "~:y": 441
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ub688a894-3697-80d3-8006-51cd477981bc",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 239,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 239,
+ "~:y": 297,
+ "~:width": 153,
+ "~:height": 144,
+ "~:x1": 239,
+ "~:y1": 297,
+ "~:x2": 392,
+ "~:y2": 441
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 1
+ },
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 144,
+ "~:flip-y": null
+ }
+ },
+ "~ub688a894-3697-80d3-8006-51cd5504e381": {
+ "~#shape": {
+ "~:y": 297,
+ "~: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": 153,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 442,
+ "~:y": 297
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 595,
+ "~:y": 297
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 595,
+ "~:y": 441
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 442,
+ "~:y": 441
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ub688a894-3697-80d3-8006-51cd5504e381",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 442,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 442,
+ "~:y": 297,
+ "~:width": 153,
+ "~:height": 144,
+ "~:x1": 442,
+ "~:y1": 297,
+ "~:x2": 595,
+ "~:y2": 441
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 0.5
+ },
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 144,
+ "~:flip-y": null
+ }
+ },
+ "~ub688a894-3697-80d3-8006-51cd5de7c5f3": {
+ "~#shape": {
+ "~:y": 476.99998474121094,
+ "~: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": 153,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 239,
+ "~:y": 476.99998474121094
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 392,
+ "~:y": 476.99998474121094
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 392,
+ "~:y": 620.9999847412109
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 239,
+ "~:y": 620.9999847412109
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ub688a894-3697-80d3-8006-51cd5de7c5f3",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 239,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 239,
+ "~:y": 476.99998474121094,
+ "~:width": 153,
+ "~:height": 144,
+ "~:x1": 239,
+ "~:y1": 476.99998474121094,
+ "~:x2": 392,
+ "~:y2": 620.9999847412109
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#ff0000",
+ "~:fill-opacity": 0.5
+ },
+ {
+ "~:fill-color-gradient": {
+ "~:stops": [
+ {
+ "~:color": "#003fff",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#003fff",
+ "~:offset": 1,
+ "~:opacity": 0
+ }
+ ],
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:start-x": 0.5,
+ "~:end-y": 1,
+ "~:end-x": 0.5,
+ "~:start-y": 0
+ }
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 144,
+ "~:flip-y": null
+ }
+ },
+ "~ub688a894-3697-80d3-8006-51cd67bc1de9": {
+ "~#shape": {
+ "~:y": 476.99998474121094,
+ "~: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": 153,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 442,
+ "~:y": 476.99998474121094
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 595,
+ "~:y": 476.99998474121094
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 595,
+ "~:y": 620.9999847412109
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 442,
+ "~:y": 620.9999847412109
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ub688a894-3697-80d3-8006-51cd67bc1de9",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 442,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 442,
+ "~:y": 476.99998474121094,
+ "~:width": 153,
+ "~:height": 144,
+ "~:x1": 442,
+ "~:y1": 476.99998474121094,
+ "~:x2": 595,
+ "~:y2": 620.9999847412109
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:stops": [
+ {
+ "~:color": "#010512",
+ "~:offset": 0,
+ "~:opacity": 0
+ },
+ {
+ "~:color": "#010512",
+ "~:offset": 1,
+ "~:opacity": 1
+ }
+ ],
+ "~:width": 1,
+ "~:type": "~:radial",
+ "~:start-x": 0.5,
+ "~:end-y": 1,
+ "~:end-x": 0.5,
+ "~:start-y": 0.5
+ },
+ "~:fill-opacity": 0.5
+ },
+ {
+ "~:fill-image": {
+ "~:mtype": "image/jpeg",
+ "~:name": "Aptenodytes_forsteri_-Snow_Hill_Island,_Antarctica_-adults_and_juvenile-8.jpg",
+ "~:keep-aspect-ratio": true,
+ "~:width": 872,
+ "~:id": "~uc0939f58-37bc-805d-8006-51cda84a405a",
+ "~:height": 1400
+ },
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 144,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~uc0939f58-37bc-805d-8006-51cd3a51c256",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~uc0939f58-37bc-805d-8006-51cd3a51c255",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-multiple-strokes.json b/frontend/playwright/data/render-wasm/get-file-multiple-strokes.json
new file mode 100644
index 0000000000..f4c1ec7801
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-multiple-strokes.json
@@ -0,0 +1,538 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "variants/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~: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": "Multiple strokes",
+ "~:revn": 16,
+ "~:modified-at": "~m1749564011553",
+ "~:vern": 0,
+ "~:id": "~uc0939f58-37bc-805d-8006-51cc78297208",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-add-partial-text-touched-flags",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0004-clean-shadow-and-colors",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u53a7ff09-2228-81d3-8006-4b5ea964593b",
+ "~:created-at": "~m1749563833517",
+ "~:data": {
+ "~:pages": [
+ "~uc0939f58-37bc-805d-8006-51cc78297209"
+ ],
+ "~:pages-index": {
+ "~uc0939f58-37bc-805d-8006-51cc78297209": {
+ "~:objects": {
+ "~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
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~: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,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~ub688a894-3697-80d3-8006-51cc8a55c2fd",
+ "~ub688a894-3697-80d3-8006-51ccce062cb3",
+ "~ub688a894-3697-80d3-8006-51ccfa2e6eeb"
+ ]
+ }
+ },
+ "~ub688a894-3697-80d3-8006-51cc8a55c2fd": {
+ "~#shape": {
+ "~:y": 334,
+ "~: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": 147,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 199,
+ "~:y": 334
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 346,
+ "~:y": 334
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 346,
+ "~:y": 464
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 199,
+ "~:y": 464
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ub688a894-3697-80d3-8006-51cc8a55c2fd",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#0000ff",
+ "~:stroke-opacity": 0.5,
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 20
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#ff0000",
+ "~:stroke-opacity": 1,
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 5
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1,
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 10
+ }
+ ],
+ "~:x": 199,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 199,
+ "~:y": 334,
+ "~:width": 147,
+ "~:height": 130,
+ "~:x1": 199,
+ "~:y1": 334,
+ "~:x2": 346,
+ "~:y2": 464
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 130,
+ "~:flip-y": null
+ }
+ },
+ "~ub688a894-3697-80d3-8006-51ccce062cb3": {
+ "~#shape": {
+ "~:y": 334,
+ "~: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": 130,
+ "~:type": "~:circle",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 512.9999961853027,
+ "~:y": 334
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 642.9999961853027,
+ "~:y": 334
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 642.9999961853027,
+ "~:y": 459
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 512.9999961853027,
+ "~:y": 459
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~ub688a894-3697-80d3-8006-51ccce062cb3",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#0000ff",
+ "~:stroke-opacity": 0.5,
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 20
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#ff0000",
+ "~:stroke-opacity": 1,
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 5
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1,
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 10
+ }
+ ],
+ "~:x": 512.9999961853027,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 512.9999961853027,
+ "~:y": 334,
+ "~:width": 130,
+ "~:height": 125,
+ "~:x1": 512.9999961853027,
+ "~:y1": 334,
+ "~:x2": 642.9999961853027,
+ "~:y2": 459
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 125,
+ "~:flip-y": null
+ }
+ },
+ "~ub688a894-3697-80d3-8006-51ccfa2e6eeb": {
+ "~#shape": {
+ "~:y": null,
+ "~: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",
+ "~:content": {
+ "~#penpot/path-data": "~bAQAAAAAAAAAAAAAAAAAAAAAAAACm6Y9DAAAJRAMAAADAyXlD4wIQRCW0I0NPCiJE9sBWQzqOK0QDAAAA4+aEQyUSNUS++9dDuQojRDj3xUPoxhlEAwAAALLys0MYgxBEpumPQwAACUSm6Y9DAAAJRA=="
+ },
+ "~:name": "Path",
+ "~:width": null,
+ "~:type": "~:path",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 198.9999999038273,
+ "~:y": 547.9999675750732
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 401.00001319474995,
+ "~:y": 547.9999675750732
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 401.00001319474995,
+ "~:y": 696.9999543199095
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 198.9999999038273,
+ "~:y": 696.9999543199095
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~ub688a894-3697-80d3-8006-51ccfa2e6eeb",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#0000ff",
+ "~:stroke-opacity": 0.5,
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 20
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#ff0000",
+ "~:stroke-opacity": 1,
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 5
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1,
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 10
+ }
+ ],
+ "~:x": null,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 198.9999999038273,
+ "~:y": 547.9999675750732,
+ "~:width": 202.00001329092265,
+ "~:height": 148.9999867448363,
+ "~:x1": 198.9999999038273,
+ "~:y1": 547.9999675750732,
+ "~:x2": 401.00001319474995,
+ "~:y2": 696.9999543199095
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": null,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~uc0939f58-37bc-805d-8006-51cc78297209",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~uc0939f58-37bc-805d-8006-51cc78297208",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-shapes-exif-rotated-fills.json b/frontend/playwright/data/render-wasm/get-file-shapes-exif-rotated-fills.json
new file mode 100644
index 0000000000..00472673fc
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-shapes-exif-rotated-fills.json
@@ -0,0 +1,779 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u5d1327cf-3054-8111-8005-328a160ff966",
+ "~: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": "Exif rotated fills",
+ "~:revn": 17,
+ "~:modified-at": "~m1750761275050",
+ "~:vern": 0,
+ "~:id": "~u27270c45-35b4-80f3-8006-63a3912bdce8",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u5d1327cf-3054-8111-8005-340b8ba38a69",
+ "~:created-at": "~m1750761070908",
+ "~:data": {
+ "~:pages": [
+ "~u27270c45-35b4-80f3-8006-63a3912bdce9"
+ ],
+ "~:pages-index": {
+ "~u27270c45-35b4-80f3-8006-63a3912bdce9": {
+ "~:objects": {
+ "~u00000000-0000-0000-0000-000000000000": {
+ "~#shape": {
+ "~:y": 0,
+ "~:hide-fill-on-export": false,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:name": "Root Frame",
+ "~:width": 0.01,
+ "~:type": "~:frame",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 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,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 0,
+ "~:y": 0,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u8ae169c2-73c6-809f-8006-63a3d429cea3",
+ "~u8ae169c2-73c6-809f-8006-63a394f96940",
+ "~u8ae169c2-73c6-809f-8006-63a3ef35c521",
+ "~u8ae169c2-73c6-809f-8006-63a40defed29"
+ ]
+ }
+ },
+ "~u8ae169c2-73c6-809f-8006-63a394f96940": {
+ "~#shape": {
+ "~:y": -119,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:hide-in-viewer": false,
+ "~:name": "Rectangle",
+ "~:width": 1044,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -2211,
+ "~:y": -119
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -1167,
+ "~:y": -119
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -1167,
+ "~:y": 577
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -2211,
+ "~:y": 577
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:layout-item-h-sizing": "~:fix",
+ "~:proportion-lock": true,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u8ae169c2-73c6-809f-8006-63a394f96940",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": -2211,
+ "~:proportion": 1.5,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -2211,
+ "~:y": -119,
+ "~:width": 1044,
+ "~:height": 696,
+ "~:x1": -2211,
+ "~:y1": -119,
+ "~:x2": -1167,
+ "~:y2": 577
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u27270c45-35b4-80f3-8006-63a39cf292e7",
+ "~:width": 1200,
+ "~:height": 1800,
+ "~:mtype": "image/jpeg",
+ "~:name": "Landscape_6.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 696,
+ "~:flip-y": null
+ }
+ },
+ "~u8ae169c2-73c6-809f-8006-63a3d429cea3": {
+ "~#shape": {
+ "~:y": -119,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:hide-in-viewer": false,
+ "~:name": "Rectangle",
+ "~:width": 1044,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -1059,
+ "~:y": -119
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -15,
+ "~:y": -119
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -15,
+ "~:y": 577
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -1059,
+ "~:y": 577
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:layout-item-h-sizing": "~:fix",
+ "~:proportion-lock": true,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u8ae169c2-73c6-809f-8006-63a3d429cea3",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 200,
+ "~:stroke-opacity": 1,
+ "~:stroke-image": {
+ "~:id": "~u27270c45-35b4-80f3-8006-63a3ea82557f",
+ "~:width": 1200,
+ "~:height": 1800,
+ "~:mtype": "image/jpeg",
+ "~:name": "Landscape_6.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:x": -1059,
+ "~:proportion": 1.5,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -1059,
+ "~:y": -119,
+ "~:width": 1044,
+ "~:height": 696,
+ "~:x1": -1059,
+ "~:y1": -119,
+ "~:x2": -15,
+ "~:y2": 577
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 696,
+ "~:flip-y": null
+ }
+ },
+ "~u8ae169c2-73c6-809f-8006-63a3ef35c521": {
+ "~#shape": {
+ "~:y": 577,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:font-size": "1500",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u27270c45-35b4-80f3-8006-63a41d147866",
+ "~:width": 1200,
+ "~:height": 1800,
+ "~:mtype": "image/jpeg",
+ "~:name": "Landscape_6.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "X"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "9nfs8",
+ "~:font-size": "1500",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u27270c45-35b4-80f3-8006-63a41d147866",
+ "~:width": 1200,
+ "~:height": 1800,
+ "~:mtype": "image/jpeg",
+ "~:name": "Landscape_6.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ]
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "X",
+ "~:width": 770,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -2211,
+ "~:y": 577
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -1441,
+ "~:y": 577
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -1441,
+ "~:y": 2377
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -2211,
+ "~:y": 2377
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u8ae169c2-73c6-809f-8006-63a3ef35c521",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 2448,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "1500px",
+ "~:font-weight": "400",
+ "~:y1": -71,
+ "~:width": 769.046875,
+ "~:text-decoration": "none solid rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": -2211,
+ "~:x1": 0,
+ "~:y2": 1871,
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u27270c45-35b4-80f3-8006-63a41d147866",
+ "~:width": 1200,
+ "~:height": 1800,
+ "~:mtype": "image/jpeg",
+ "~:name": "Landscape_6.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:x2": 769.046875,
+ "~:direction": "ltr",
+ "~:font-family": "sourcesanspro",
+ "~:height": 1942,
+ "~:text": "X"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": -2211,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -2211,
+ "~:y": 577,
+ "~:width": 770,
+ "~:height": 1800,
+ "~:x1": -2211,
+ "~:y1": 577,
+ "~:x2": -1441,
+ "~:y2": 2377
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 1800,
+ "~:flip-y": null
+ }
+ },
+ "~u8ae169c2-73c6-809f-8006-63a40defed29": {
+ "~#shape": {
+ "~:y": 577,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:font-size": "1500",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "X"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "9nfs8",
+ "~:font-size": "1500",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ]
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "X",
+ "~:width": 770,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -1059,
+ "~:y": 577
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -289,
+ "~:y": 577
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -289,
+ "~:y": 2377
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -1059,
+ "~:y": 2377
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u8ae169c2-73c6-809f-8006-63a40defed29",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 2448,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "1500px",
+ "~:font-weight": "400",
+ "~:y1": -71,
+ "~:width": 769.046875,
+ "~:text-decoration": "none solid rgb(177, 178, 181)",
+ "~:letter-spacing": "normal",
+ "~:x": -1059,
+ "~:x1": 0,
+ "~:y2": 1871,
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 769.046875,
+ "~:direction": "ltr",
+ "~:font-family": "sourcesanspro",
+ "~:height": 1942,
+ "~:text": "X"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 100,
+ "~:stroke-opacity": 1,
+ "~:stroke-image": {
+ "~:id": "~u27270c45-35b4-80f3-8006-63a43dc4984b",
+ "~:width": 1200,
+ "~:height": 1800,
+ "~:mtype": "image/jpeg",
+ "~:name": "Landscape_6.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:x": -1059,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -1059,
+ "~:y": 577,
+ "~:width": 770,
+ "~:height": 1800,
+ "~:x1": -1059,
+ "~:y1": 577,
+ "~:x2": -289,
+ "~:y2": 2377
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 1800,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~u27270c45-35b4-80f3-8006-63a3912bdce9",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u27270c45-35b4-80f3-8006-63a3912bdce8",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-shapes-fills.json b/frontend/playwright/data/render-wasm/get-file-shapes-fills.json
new file mode 100644
index 0000000000..3ba50cba2c
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-shapes-fills.json
@@ -0,0 +1,1047 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "variants/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~: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": "Fills",
+ "~:revn": 33,
+ "~:modified-at": "~m1749212476332",
+ "~:vern": 0,
+ "~:id": "~u1ebcea38-f1bf-8101-8006-4c8ec4a9bffe",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-add-partial-text-touched-flags",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u53a7ff09-2228-81d3-8006-4b5ea964593b",
+ "~:created-at": "~m1749212114638",
+ "~:data": {
+ "~:pages": [
+ "~u1ebcea38-f1bf-8101-8006-4c8ec4a9bfff"
+ ],
+ "~:pages-index": {
+ "~u1ebcea38-f1bf-8101-8006-4c8ec4a9bfff": {
+ "~:objects": {
+ "~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
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~: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,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u1636bde2-eab8-8056-8006-4c8ecac1c462",
+ "~u1636bde2-eab8-8056-8006-4c8f2836420e",
+ "~u1636bde2-eab8-8056-8006-4c8f6da5ee8f",
+ "~u1636bde2-eab8-8056-8006-4c8eccbd715d",
+ "~u1636bde2-eab8-8056-8006-4c8f0db998ca",
+ "~u1636bde2-eab8-8056-8006-4c8ef9e55f47",
+ "~ua359bc9e-34eb-808a-8006-4c8fc9d9fd54",
+ "~u532a36f0-2a4e-80b8-8006-4c8ff6d28e47"
+ ]
+ }
+ },
+ "~u532a36f0-2a4e-80b8-8006-4c8ff6d28e47": {
+ "~#shape": {
+ "~:y": null,
+ "~: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",
+ "~:content": {
+ "~#penpot/path-data": "~bAQAAAAAAAAAAAAAAAAAAAAAAAADfEBJEKATkQwMAAADg1AREdITjQ28u+kP3fe5D4NQERJy3A0QDAAAAiJIMRD0wEERiQUVEPOInRHsEO0RGcQ5EAwAAAJTHMESeAOpD3xASRCgE5EPfEBJEKATkQw=="
+ },
+ "~:name": "Path",
+ "~:width": null,
+ "~:type": "~:path",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 519.0000360585204,
+ "~:y": 455.9999889055738
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 753.0000180987242,
+ "~:y": 455.9999889055738
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 753.0000180987242,
+ "~:y": 614.9999902292648
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 519.0000360585204,
+ "~:y": 614.9999902292648
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~u532a36f0-2a4e-80b8-8006-4c8ff6d28e47",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": null,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 519.0000360585204,
+ "~:y": 455.9999889055738,
+ "~:width": 233.9999820402038,
+ "~:height": 159.00000132369098,
+ "~:x1": 519.0000360585204,
+ "~:y1": 455.9999889055738,
+ "~:x2": 753.0000180987242,
+ "~:y2": 614.9999902292648
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:stops": [
+ {
+ "~:color": "#0054ff",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#ff0077",
+ "~:offset": 1,
+ "~:opacity": 1
+ }
+ ]
+ }
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": null,
+ "~:flip-y": null
+ }
+ },
+ "~u1636bde2-eab8-8056-8006-4c8eccbd715d": {
+ "~#shape": {
+ "~:y": 379,
+ "~: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": 164,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 100.00000000000001,
+ "~:y": 379
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 264,
+ "~:y": 379
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 264,
+ "~:y": 525
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 100.00000000000001,
+ "~:y": 525
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u1636bde2-eab8-8056-8006-4c8eccbd715d",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 100,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 100,
+ "~:y": 379,
+ "~:width": 164,
+ "~:height": 146,
+ "~:x1": 100,
+ "~:y1": 379,
+ "~:x2": 264,
+ "~:y2": 525
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:stops": [
+ {
+ "~:color": "#003fff",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#ff007f",
+ "~:offset": 1,
+ "~:opacity": 0.5277777777777778
+ }
+ ]
+ }
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 146,
+ "~:flip-y": null
+ }
+ },
+ "~ua359bc9e-34eb-808a-8006-4c8fc9d9fd54": {
+ "~#shape": {
+ "~:y": null,
+ "~: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",
+ "~:content": {
+ "~#penpot/path-data": "~bAQAAAAAAAAAAAAAAAAAAAAAAAADe0BBEUQh0QwMAAADflANE6AhzQ26u90P3fYRD35QDRDlvnUMDAAAAiFILRHtgtkNhAUREecTlQ3rEOUSM4rJDAwAAAJOHL0SfAIBD3tAQRFEIdEPe0BBEUQh0Qw=="
+ },
+ "~:name": "Path",
+ "~:width": null,
+ "~:type": "~:path",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 513.9999890900604,
+ "~:y": 244.00000416436282
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 747.9999711302642,
+ "~:y": 244.00000416436282
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 747.9999711302642,
+ "~:y": 403.00000548805383
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 513.9999890900604,
+ "~:y": 403.00000548805383
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~ua359bc9e-34eb-808a-8006-4c8fc9d9fd54",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": null,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 513.9999890900604,
+ "~:y": 244.00000416436285,
+ "~:width": 233.9999820402038,
+ "~:height": 159.000001323691,
+ "~:x1": 513.9999890900604,
+ "~:y1": 244.00000416436285,
+ "~:x2": 747.9999711302642,
+ "~:y2": 403.00000548805383
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u1ebcea38-f1bf-8101-8006-4c8fd68e7c84",
+ "~:width": 872,
+ "~:height": 1400,
+ "~:mtype": "image/jpeg",
+ "~:name": "Aptenodytes_forsteri_-Snow_Hill_Island,_Antarctica_-adults_and_juvenile-8.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": null,
+ "~:flip-y": null
+ }
+ },
+ "~u1636bde2-eab8-8056-8006-4c8f0db998ca": {
+ "~#shape": {
+ "~:y": 194,
+ "~: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": 164,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 307,
+ "~:y": 194
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 471,
+ "~:y": 194
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 471,
+ "~:y": 340
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 307,
+ "~:y": 340
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u1636bde2-eab8-8056-8006-4c8f0db998ca",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 307,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 307,
+ "~:y": 194,
+ "~:width": 164,
+ "~:height": 146,
+ "~:x1": 307,
+ "~:y1": 194,
+ "~:x2": 471,
+ "~:y2": 340
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0.5,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:radial",
+ "~:stops": [
+ {
+ "~:color": "#003fff",
+ "~:offset": 0.32,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#ff007f",
+ "~:offset": 1,
+ "~:opacity": 0.5277777777777778
+ }
+ ]
+ }
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 146,
+ "~:flip-y": null
+ }
+ },
+ "~u1636bde2-eab8-8056-8006-4c8f6da5ee8f": {
+ "~#shape": {
+ "~:y": 564,
+ "~: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": 164,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 307,
+ "~:y": 564
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 471,
+ "~:y": 564
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 471,
+ "~:y": 710
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 307,
+ "~:y": 710
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u1636bde2-eab8-8056-8006-4c8f6da5ee8f",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 307,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 307,
+ "~:y": 564,
+ "~:width": 164,
+ "~:height": 146,
+ "~:x1": 307,
+ "~:y1": 564,
+ "~:x2": 471,
+ "~:y2": 710
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0.5,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:radial",
+ "~:stops": [
+ {
+ "~:color": "#000000",
+ "~:offset": 0.49,
+ "~:opacity": 0
+ },
+ {
+ "~:color": "#000000",
+ "~:offset": 1,
+ "~:opacity": 0.38333333333333336
+ }
+ ]
+ }
+ },
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u1ebcea38-f1bf-8101-8006-4c8f579da49c",
+ "~:width": 872,
+ "~:height": 1400,
+ "~:mtype": "image/jpeg",
+ "~:name": "Aptenodytes_forsteri_-Snow_Hill_Island,_Antarctica_-adults_and_juvenile-8.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 146,
+ "~:flip-y": null
+ }
+ },
+ "~u1636bde2-eab8-8056-8006-4c8f2836420e": {
+ "~#shape": {
+ "~:y": 379,
+ "~: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": 164,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 307,
+ "~:y": 379
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 471,
+ "~:y": 379
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 471,
+ "~:y": 525
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 307,
+ "~:y": 525
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u1636bde2-eab8-8056-8006-4c8f2836420e",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 307,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 307,
+ "~:y": 379,
+ "~:width": 164,
+ "~:height": 146,
+ "~:x1": 307,
+ "~:y1": 379,
+ "~:x2": 471,
+ "~:y2": 525
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u1ebcea38-f1bf-8101-8006-4c8f579da49c",
+ "~:width": 872,
+ "~:height": 1400,
+ "~:mtype": "image/jpeg",
+ "~:name": "Aptenodytes_forsteri_-Snow_Hill_Island,_Antarctica_-adults_and_juvenile-8.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 146,
+ "~:flip-y": null
+ }
+ },
+ "~u1636bde2-eab8-8056-8006-4c8ecac1c462": {
+ "~#shape": {
+ "~:y": 194,
+ "~: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": 164,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 100.00000000000001,
+ "~:y": 194
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 264,
+ "~:y": 194
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 264,
+ "~:y": 340
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 100.00000000000001,
+ "~:y": 340
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u1636bde2-eab8-8056-8006-4c8ecac1c462",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 100,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 100,
+ "~:y": 194,
+ "~:width": 164,
+ "~:height": 146,
+ "~:x1": 100,
+ "~:y1": 194,
+ "~:x2": 264,
+ "~:y2": 340
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#003fff",
+ "~:fill-opacity": 0.5
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 146,
+ "~:flip-y": null
+ }
+ },
+ "~u1636bde2-eab8-8056-8006-4c8ef9e55f47": {
+ "~#shape": {
+ "~:y": 564,
+ "~: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": 164,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 100.00000000000001,
+ "~:y": 564
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 264,
+ "~:y": 564
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 264,
+ "~:y": 710
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 100.00000000000001,
+ "~:y": 710
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u1636bde2-eab8-8056-8006-4c8ef9e55f47",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 100,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 100,
+ "~:y": 564,
+ "~:width": 164,
+ "~:height": 146,
+ "~:x1": 100,
+ "~:y1": 564,
+ "~:x2": 264,
+ "~:y2": 710
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 0.2
+ },
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:stops": [
+ {
+ "~:color": "#003fff",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#ff007f",
+ "~:offset": 1,
+ "~:opacity": 0.5277777777777778
+ }
+ ]
+ }
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 146,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~u1ebcea38-f1bf-8101-8006-4c8ec4a9bfff",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u1ebcea38-f1bf-8101-8006-4c8ec4a9bffe",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-shapes-groups-boards.json b/frontend/playwright/data/render-wasm/get-file-shapes-groups-boards.json
new file mode 100644
index 0000000000..c82afce716
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-shapes-groups-boards.json
@@ -0,0 +1,1089 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "variants/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~: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": "Shapes, Boards & Groups",
+ "~:revn": 18,
+ "~:modified-at": "~m1749133354832",
+ "~:vern": 0,
+ "~:id": "~u53a7ff09-2228-81d3-8006-4b5eac177245",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-add-partial-text-touched-flags",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u53a7ff09-2228-81d3-8006-4b5ea964593b",
+ "~:created-at": "~m1749132397698",
+ "~:data": {
+ "~:pages": [
+ "~u53a7ff09-2228-81d3-8006-4b5eac177246"
+ ],
+ "~:pages-index": {
+ "~u53a7ff09-2228-81d3-8006-4b5eac177246": {
+ "~:objects": {
+ "~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
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~: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,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~ub3609732-7b40-80dd-8006-4b5eaef6c61b",
+ "~ub3609732-7b40-80dd-8006-4b5f1195932c",
+ "~ub3609732-7b40-80dd-8006-4b5f1b3cd0fd"
+ ]
+ }
+ },
+ "~ub3609732-7b40-80dd-8006-4b5f0cf6c012": {
+ "~#shape": {
+ "~:y": 61,
+ "~: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": 90,
+ "~:type": "~:circle",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 916.9999580383301,
+ "~:y": 61
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1006.9999580383301,
+ "~:y": 61
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1006.9999580383301,
+ "~:y": 149
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 916.9999580383301,
+ "~:y": 149
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~ub3609732-7b40-80dd-8006-4b5f0cf6c012",
+ "~:parent-id": "~ub3609732-7b40-80dd-8006-4b5f1195932c",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 916.9999580383301,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 916.9999580383301,
+ "~:y": 61,
+ "~:width": 90,
+ "~:height": 88,
+ "~:x1": 916.9999580383301,
+ "~:y1": 61,
+ "~:x2": 1006.9999580383301,
+ "~:y2": 149
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 88,
+ "~:flip-y": null
+ }
+ },
+ "~ub3609732-7b40-80dd-8006-4b5ee7503d34": {
+ "~#shape": {
+ "~:y": 96.25051809431667,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 0.7071067811864666,
+ "~:b": -0.7071067811866283,
+ "~:c": 0.7071067811866287,
+ "~:d": 0.7071067811864662,
+ "~:e": -6.821210263296962E-13,
+ "~:f": -8.526512829121202E-13
+ }
+ },
+ "~:rotation": 315,
+ "~:grow-type": "~:fixed",
+ "~:hide-in-viewer": false,
+ "~:name": "Rectangle",
+ "~:width": 100.00004392689823,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 318.99997908942305,
+ "~:y": 141.71068550598807
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 389.71068826904013,
+ "~:y": 70.99997632635848
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 438.5010405885363,
+ "~:y": 119.79032864584667
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 367.790331408919,
+ "~:y": 190.50103782547617
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 0.7071067811864663,
+ "~:b": 0.7071067811866284,
+ "~:c": -0.7071067811866288,
+ "~:d": 0.7071067811864667,
+ "~:e": -1.2058310082703872E-13,
+ "~:f": 1.0852479074422117E-12
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ub3609732-7b40-80dd-8006-4b5ee7503d34",
+ "~:parent-id": "~ub3609732-7b40-80dd-8006-4b5eaef6c61b",
+ "~:frame-id": "~ub3609732-7b40-80dd-8006-4b5eaef6c61b",
+ "~:strokes": [],
+ "~:x": 328.75048787553044,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 328.75048787553044,
+ "~:y": 96.25051809431667,
+ "~:width": 100.00004392689823,
+ "~:height": 68.99997796320113,
+ "~:x1": 328.75048787553044,
+ "~:y1": 96.25051809431667,
+ "~:x2": 428.75053180242867,
+ "~:y2": 165.2504960575178
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": true,
+ "~:height": 68.99997796320113,
+ "~:flip-y": true
+ }
+ },
+ "~ub3609732-7b40-80dd-8006-4b5eaef6c61b": {
+ "~#shape": {
+ "~:y": 25.000003555024136,
+ "~:hide-fill-on-export": false,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:hide-in-viewer": false,
+ "~:name": "Board",
+ "~:width": 422.00003286409583,
+ "~:type": "~:frame",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 272.9999938713979,
+ "~:y": 25.000003555024136
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 695.0000267354937,
+ "~:y": 25.000003555024136
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 695.0000267354937,
+ "~:y": 382.0000004666714
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 272.9999938713979,
+ "~:y": 382.0000004666714
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:layout-item-h-sizing": "~:fix",
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ub3609732-7b40-80dd-8006-4b5eaef6c61b",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 272.9999938713979,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 272.9999938713979,
+ "~:y": 25.000003555024136,
+ "~:width": 422.00003286409583,
+ "~:height": 356.99999691164726,
+ "~:x1": 272.9999938713979,
+ "~:y1": 25.000003555024136,
+ "~:x2": 695.0000267354937,
+ "~:y2": 382.0000004666714
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#FFFFFF",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 356.99999691164726,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~ub3609732-7b40-80dd-8006-4b5eb6d6b8ba",
+ "~ub3609732-7b40-80dd-8006-4b5eb4b9233b",
+ "~ub3609732-7b40-80dd-8006-4b5ee7503d34"
+ ]
+ }
+ },
+ "~ub3609732-7b40-80dd-8006-4b5eb4b9233b": {
+ "~#shape": {
+ "~:y": 77.99998235703063,
+ "~: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": 111.99998664856003,
+ "~:type": "~:circle",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 519.0000252723696,
+ "~:y": 77.99998235703063
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 631.0000119209296,
+ "~:y": 77.99998235703063
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 631.0000119209296,
+ "~:y": 183.9999949932127
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 519.0000252723696,
+ "~:y": 183.9999949932127
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~ub3609732-7b40-80dd-8006-4b5eb4b9233b",
+ "~:parent-id": "~ub3609732-7b40-80dd-8006-4b5eaef6c61b",
+ "~:frame-id": "~ub3609732-7b40-80dd-8006-4b5eaef6c61b",
+ "~:strokes": [],
+ "~:x": 519.0000252723697,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 519.0000252723697,
+ "~:y": 77.99998235703063,
+ "~:width": 111.99998664856003,
+ "~:height": 106.00001263618208,
+ "~:x1": 519.0000252723697,
+ "~:y1": 77.99998235703063,
+ "~:x2": 631.0000119209298,
+ "~:y2": 183.9999949932127
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 106.00001263618208,
+ "~:flip-y": null
+ }
+ },
+ "~ub3609732-7b40-80dd-8006-4b5eb6d6b8ba": {
+ "~#shape": {
+ "~:y": 266.99998360878504,
+ "~: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": 96.99999421834946,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 318.9999945759773,
+ "~:y": 266.99998360878504
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 415.9999887943268,
+ "~:y": 266.99998360878504
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 415.9999887943268,
+ "~:y": 343.9999790191847
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 318.9999945759773,
+ "~:y": 343.9999790191847
+ }
+ }
+ ],
+ "~:r2": 20,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 20,
+ "~:r1": 20,
+ "~:id": "~ub3609732-7b40-80dd-8006-4b5eb6d6b8ba",
+ "~:parent-id": "~ub3609732-7b40-80dd-8006-4b5eaef6c61b",
+ "~:frame-id": "~ub3609732-7b40-80dd-8006-4b5eaef6c61b",
+ "~:strokes": [],
+ "~:x": 318.9999945759773,
+ "~:proportion": 1,
+ "~:r4": 20,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 318.9999945759773,
+ "~:y": 266.99998360878504,
+ "~:width": 96.99999421834946,
+ "~:height": 76.99999541039966,
+ "~:x1": 318.9999945759773,
+ "~:y1": 266.99998360878504,
+ "~:x2": 415.9999887943268,
+ "~:y2": 343.9999790191847
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 76.99999541039966,
+ "~:flip-y": null
+ }
+ },
+ "~ub3609732-7b40-80dd-8006-4b5f1b3cd0fd": {
+ "~#shape": {
+ "~:y": 64.1025296964562,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 0.8660253929828814,
+ "~:b": -0.5000000187088454,
+ "~:c": 0.5000000101884137,
+ "~:d": 0.866025397902155,
+ "~:e": 1.3642420526593924E-12,
+ "~:f": 4.547473508864641E-13
+ }
+ },
+ "~:rotation": 330,
+ "~:index": 3,
+ "~:name": "Group",
+ "~:width": 178.00005759344003,
+ "~:type": "~:group",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1102.9999918341623,
+ "~:y": 122.00000530482339
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1257.1524913336161,
+ "~:y": 32.999995025044484
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1357.152513948974,
+ "~:y": 206.205122909205
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1203.0000144495198,
+ "~:y": 295.20513318898395
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 0.8660253979021553,
+ "~:b": 0.5000000187088455,
+ "~:c": -0.5000000101884138,
+ "~:d": 0.8660253929828816,
+ "~:e": -9.54094586412817E-13,
+ "~:f": -1.0759438051124644E-12
+ }
+ },
+ "~:id": "~ub3609732-7b40-80dd-8006-4b5f1b3cd0fd",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1141.076224094848,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1141.076224094848,
+ "~:y": 64.1025296964562,
+ "~:width": 178.00005759344003,
+ "~:height": 200.0000688211158,
+ "~:x1": 1141.076224094848,
+ "~:y1": 64.1025296964562,
+ "~:x2": 1319.076281688288,
+ "~:y2": 264.102598517572
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 200.0000688211158,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~ub3609732-7b40-80dd-8006-4b5f1b3cd0fe",
+ "~ub3609732-7b40-80dd-8006-4b5f1b3cd0ff"
+ ]
+ }
+ },
+ "~ub3609732-7b40-80dd-8006-4b5f1b3cd0fe": {
+ "~#shape": {
+ "~:y": 177.75216304418154,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 0.8660253493492736,
+ "~:b": -0.5000000942844571,
+ "~:c": 0.49999995856927626,
+ "~:d": 0.8660254277044768,
+ "~:e": 1.0913936421275139E-11,
+ "~:f": 5.6843418860808015E-12
+ }
+ },
+ "~:rotation": 330,
+ "~:grow-type": "~:fixed",
+ "~:hide-in-viewer": false,
+ "~:name": "Rectangle",
+ "~:width": 108.99991328201122,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1154.9999992024932,
+ "~:y": 211.49990454997567
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1249.3966053964355,
+ "~:y": 156.99998036799627
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1297.896562493645,
+ "~:y": 241.0043942983914
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1203.4999562997027,
+ "~:y": 295.5043184803708
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 0.8660254277044886,
+ "~:b": 0.500000094284464,
+ "~:c": -0.4999999585692831,
+ "~:d": 0.8660253493492854,
+ "~:e": -6.609575749640357E-12,
+ "~:f": -1.0379753407366116E-11
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ub3609732-7b40-80dd-8006-4b5f1b3cd0fe",
+ "~:parent-id": "~ub3609732-7b40-80dd-8006-4b5f1b3cd0fd",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1171.9483242070637,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1171.9483242070637,
+ "~:y": 177.75216304418154,
+ "~:width": 108.99991328201122,
+ "~:height": 96.99997276000408,
+ "~:x1": 1171.9483242070637,
+ "~:y1": 177.75216304418154,
+ "~:x2": 1280.948237489075,
+ "~:y2": 274.7521358041856
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 96.99997276000408,
+ "~:flip-y": null
+ }
+ },
+ "~ub3609732-7b40-80dd-8006-4b5f1b3cd0ff": {
+ "~#shape": {
+ "~:y": 49.605113090800444,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 0.8660254038912871,
+ "~:b": -0.49999999981493426,
+ "~:c": 0.5000000101884137,
+ "~:d": 0.8660253979021553,
+ "~:e": 5.229594535194337E-12,
+ "~:f": 1.1368683772161603E-12
+ }
+ },
+ "~:rotation": 330,
+ "~:grow-type": "~:fixed",
+ "~:hide-in-viewer": false,
+ "~:name": "Ellipse",
+ "~:width": 89.99987703479155,
+ "~:type": "~:circle",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1179.0000899316829,
+ "~:y": 77.9999537231875
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1256.9422027632731,
+ "~:y": 33.000046307504476
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1300.9421531153894,
+ "~:y": 109.21020333191314
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1223.0000402838,
+ "~:y": 154.21011074759622
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 0.8660253979021547,
+ "~:b": 0.4999999998149339,
+ "~:c": -0.5000000101884133,
+ "~:d": 0.8660254038912866,
+ "~:e": -3.960527488017644E-12,
+ "~:f": -3.599354162179205E-12
+ }
+ },
+ "~:id": "~ub3609732-7b40-80dd-8006-4b5f1b3cd0ff",
+ "~:parent-id": "~ub3609732-7b40-80dd-8006-4b5f1b3cd0fd",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1194.9711830061403,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1194.9711830061403,
+ "~:y": 49.605113090800444,
+ "~:width": 89.99987703479155,
+ "~:height": 87.99993087349986,
+ "~:x1": 1194.9711830061403,
+ "~:y1": 49.605113090800444,
+ "~:x2": 1284.971060040932,
+ "~:y2": 137.6050439643003
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 87.99993087349986,
+ "~:flip-y": null
+ }
+ },
+ "~ub3609732-7b40-80dd-8006-4b5f1195932c": {
+ "~#shape": {
+ "~:y": 61,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:rotation": 0,
+ "~:index": 3,
+ "~:name": "Group",
+ "~:width": 178,
+ "~:type": "~:group",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 828.9999580383301,
+ "~:y": 61
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1006.9999580383301,
+ "~:y": 61
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1006.9999580383301,
+ "~:y": 261
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 828.9999580383301,
+ "~:y": 261
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~ub3609732-7b40-80dd-8006-4b5f1195932c",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 828.9999580383301,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 828.9999580383301,
+ "~:y": 61,
+ "~:width": 178,
+ "~:height": 200,
+ "~:x1": 828.9999580383301,
+ "~:y1": 61,
+ "~:x2": 1006.9999580383301,
+ "~:y2": 261
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 200,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~ub3609732-7b40-80dd-8006-4b5f0ab291ed",
+ "~ub3609732-7b40-80dd-8006-4b5f0cf6c012"
+ ]
+ }
+ },
+ "~ub3609732-7b40-80dd-8006-4b5f0ab291ed": {
+ "~#shape": {
+ "~:y": 164,
+ "~: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": 109,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 828.9999580383301,
+ "~:y": 164
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 937.9999580383301,
+ "~:y": 164
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 937.9999580383301,
+ "~:y": 261
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 828.9999580383301,
+ "~:y": 261
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~ub3609732-7b40-80dd-8006-4b5f0ab291ed",
+ "~:parent-id": "~ub3609732-7b40-80dd-8006-4b5f1195932c",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 828.9999580383301,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 828.9999580383301,
+ "~:y": 164,
+ "~:width": 109,
+ "~:height": 97,
+ "~:x1": 828.9999580383301,
+ "~:y1": 164,
+ "~:x2": 937.9999580383301,
+ "~:y2": 261
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#B1B2B5",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 97,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~u53a7ff09-2228-81d3-8006-4b5eac177246",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u53a7ff09-2228-81d3-8006-4b5eac177245",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-shapes-strokes.json b/frontend/playwright/data/render-wasm/get-file-shapes-strokes.json
new file mode 100644
index 0000000000..c709d15813
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-shapes-strokes.json
@@ -0,0 +1,1210 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "variants/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~: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": "Strokes",
+ "~:revn": 31,
+ "~:modified-at": "~m1749473897268",
+ "~:vern": 0,
+ "~:id": "~u202c1104-9385-81d3-8006-507413ff2c99",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u53a7ff09-2228-81d3-8006-4b5ea964593b",
+ "~:created-at": "~m1749473553426",
+ "~:data": {
+ "~:pages": [
+ "~u202c1104-9385-81d3-8006-507413ff2c9a"
+ ],
+ "~:pages-index": {
+ "~u202c1104-9385-81d3-8006-507413ff2c9a": {
+ "~:objects": {
+ "~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
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~: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,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u408f5e19-4051-8035-8006-50741a000bce",
+ "~u408f5e19-4051-8035-8006-507430023c4f",
+ "~u408f5e19-4051-8035-8006-507427e67662",
+ "~u76faa7f5-8253-80e7-8006-50746e7a587d",
+ "~u76faa7f5-8253-80e7-8006-50747874d301",
+ "~u76faa7f5-8253-80e7-8006-50747bfa2951",
+ "~u9202dd03-bb97-8037-8006-50751cf6b858",
+ "~u9202dd03-bb97-8037-8006-50752c8c937c",
+ "~udeb18501-60d5-8066-8006-50753a954c6e",
+ "~udeb18501-60d5-8066-8006-50754213576e"
+ ]
+ }
+ },
+ "~u76faa7f5-8253-80e7-8006-50747874d301": {
+ "~#shape": {
+ "~:y": null,
+ "~: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",
+ "~:content": {
+ "~#penpot/path-data": "~bAQAAAAAAAAAAAAAAAAAAAAAAAABD1LZDAADwQQMAAAD/XZND3+6xQuDId0Nr7btCLF2WQ4zj/0IDAAAA6NWwQ9bsIUO8xetD3uVRQylJ30Mx8QNDAwAAAJbM0kMQ8ldCQ9S2QwAA8EFD1LZDAADwQQ=="
+ },
+ "~:name": "Path",
+ "~:width": null,
+ "~:type": "~:path",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 278.0000278540057,
+ "~:y": 29.999999284744263
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 450.0000220072752,
+ "~:y": 29.999999284744263
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 450.0000220072752,
+ "~:y": 174.0000046654724
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 278.0000278540057,
+ "~:y": 174.0000046654724
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~u76faa7f5-8253-80e7-8006-50747874d301",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 10,
+ "~:stroke-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:stops": [
+ {
+ "~:color": "#ff0000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#000000",
+ "~:offset": 1,
+ "~:opacity": 0
+ }
+ ]
+ }
+ }
+ ],
+ "~:x": null,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 278.0000278540057,
+ "~:y": 29.999999284744263,
+ "~:width": 171.99999415326954,
+ "~:height": 144.00000538072814,
+ "~:x1": 278.0000278540057,
+ "~:y1": 29.999999284744263,
+ "~:x2": 450.0000220072752,
+ "~:y2": 174.0000046654724
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": null,
+ "~:flip-y": null
+ }
+ },
+ "~u408f5e19-4051-8035-8006-50741a000bce": {
+ "~#shape": {
+ "~:y": 233,
+ "~: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": 155,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 86,
+ "~:y": 233
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 241,
+ "~:y": 233
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 241,
+ "~:y": 371
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 86,
+ "~:y": 371
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u408f5e19-4051-8035-8006-50741a000bce",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1,
+ "~:stroke-width": 10
+ }
+ ],
+ "~:x": 86,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 86,
+ "~:y": 233,
+ "~:width": 155,
+ "~:height": 138,
+ "~:x1": 86,
+ "~:y1": 233,
+ "~:x2": 241,
+ "~:y2": 371
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 138,
+ "~:flip-y": null
+ }
+ },
+ "~u408f5e19-4051-8035-8006-507430023c4f": {
+ "~#shape": {
+ "~:y": 233,
+ "~: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": 155,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 504,
+ "~:y": 233
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 659,
+ "~:y": 233
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 659,
+ "~:y": 371
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 504,
+ "~:y": 371
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u408f5e19-4051-8035-8006-507430023c4f",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 10,
+ "~:stroke-opacity": 1,
+ "~:stroke-image": {
+ "~:id": "~u202c1104-9385-81d3-8006-5074e4682cac",
+ "~:width": 872,
+ "~:height": 1400,
+ "~:mtype": "image/jpeg",
+ "~:name": "Aptenodytes_forsteri_-Snow_Hill_Island,_Antarctica_-adults_and_juvenile-8.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:x": 504,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 504,
+ "~:y": 233,
+ "~:width": 155,
+ "~:height": 138,
+ "~:x1": 504,
+ "~:y1": 233,
+ "~:x2": 659,
+ "~:y2": 371
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 138,
+ "~:flip-y": null
+ }
+ },
+ "~u76faa7f5-8253-80e7-8006-50747bfa2951": {
+ "~#shape": {
+ "~:y": null,
+ "~: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",
+ "~:content": {
+ "~#penpot/path-data": "~bAQAAAAAAAAAAAAAAAAAAAAAAAABHWhJEAADwQQMAAAAlnwBE3+6xQrvE6UNr7btCux4CRIzj/0IDAAAAGVsPRNbsIUMD0yxE3uVRQ7qUJkQx8QNDAwAAAHBWIEQQ8ldCR1oSRAAA8EFHWhJEAADwQQ=="
+ },
+ "~:name": "Path",
+ "~:width": null,
+ "~:type": "~:path",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 497.75230432816625,
+ "~:y": 29.999998569488525
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 669.7522984814358,
+ "~:y": 29.999998569488525
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 669.7522984814358,
+ "~:y": 174.00000395021667
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 497.75230432816625,
+ "~:y": 174.00000395021667
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~u76faa7f5-8253-80e7-8006-50747bfa2951",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 10,
+ "~:stroke-opacity": 1,
+ "~:stroke-image": {
+ "~:id": "~u202c1104-9385-81d3-8006-5074c50339b6",
+ "~:width": 872,
+ "~:height": 1400,
+ "~:mtype": "image/jpeg",
+ "~:name": "Aptenodytes_forsteri_-Snow_Hill_Island,_Antarctica_-adults_and_juvenile-8.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:x": null,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 497.75230432816625,
+ "~:y": 29.999998569488525,
+ "~:width": 171.99999415326954,
+ "~:height": 144.00000538072814,
+ "~:x1": 497.75230432816625,
+ "~:y1": 29.999998569488525,
+ "~:x2": 669.7522984814358,
+ "~:y2": 174.00000395021667
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": null,
+ "~:flip-y": null
+ }
+ },
+ "~u9202dd03-bb97-8037-8006-50751cf6b858": {
+ "~#shape": {
+ "~:y": null,
+ "~: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",
+ "~:content": {
+ "~#penpot/path-data": "~bAQAAAAAAAAAAAAAAAAAAAAAAAAAAAJ5CAEAIRAIAAAAAAAAAAAAAAAAAAAAAAAAAAAApQwAA4kM="
+ },
+ "~:name": "Path",
+ "~:width": null,
+ "~:type": "~:path",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 79,
+ "~:y": 452
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 169,
+ "~:y": 452
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 169,
+ "~:y": 545
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 79,
+ "~:y": 545
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~u9202dd03-bb97-8037-8006-50751cf6b858",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 10,
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1,
+ "~:stroke-cap-start": "~:line-arrow",
+ "~:stroke-cap-end": "~:line-arrow"
+ }
+ ],
+ "~:x": null,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 79,
+ "~:y": 452,
+ "~:width": 90,
+ "~:height": 93,
+ "~:x1": 79,
+ "~:y1": 452,
+ "~:x2": 169,
+ "~:y2": 545
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": null,
+ "~:flip-y": null
+ }
+ },
+ "~u408f5e19-4051-8035-8006-507427e67662": {
+ "~#shape": {
+ "~:y": 233,
+ "~: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": 155,
+ "~:type": "~:rect",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 295,
+ "~:y": 233
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 450,
+ "~:y": 233
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 450,
+ "~:y": 371
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 295,
+ "~:y": 371
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:r3": 0,
+ "~:r1": 0,
+ "~:id": "~u408f5e19-4051-8035-8006-507427e67662",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 10,
+ "~:stroke-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:stops": [
+ {
+ "~:color": "#ff0000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#000000",
+ "~:offset": 1,
+ "~:opacity": 0
+ }
+ ]
+ }
+ }
+ ],
+ "~:x": 295,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 295,
+ "~:y": 233,
+ "~:width": 155,
+ "~:height": 138,
+ "~:x1": 295,
+ "~:y1": 233,
+ "~:x2": 450,
+ "~:y2": 371
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 138,
+ "~:flip-y": null
+ }
+ },
+ "~u76faa7f5-8253-80e7-8006-50746e7a587d": {
+ "~#shape": {
+ "~:y": null,
+ "~: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",
+ "~:content": {
+ "~#penpot/path-data": "~bAQAAAAAAAAAAAAAAAAAAAAAAAAAAABJDAADwQQMAAAAAAJZCAACyQgAA4EEAALxCAACiQgAAAEMDAAAAAAAGQwAAIkMAAHxDAABSQwAAY0MAAARDAwAAAAAASkMAAFhCAAASQwAA8EEAABJDAADwQQ=="
+ },
+ "~:name": "Path",
+ "~:width": null,
+ "~:type": "~:path",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 58.247751379845134,
+ "~:y": 30
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 230.43213038893805,
+ "~:y": 30
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 230.43213038893805,
+ "~:y": 174.0817110343229
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 58.247751379845134,
+ "~:y": 174.0817110343229
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~u76faa7f5-8253-80e7-8006-50746e7a587d",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 10,
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": null,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 58.247751379845134,
+ "~:y": 30,
+ "~:width": 172.1843790090929,
+ "~:height": 144.0817110343229,
+ "~:x1": 58.247751379845134,
+ "~:y1": 30,
+ "~:x2": 230.43213038893805,
+ "~:y2": 174.0817110343229
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": null,
+ "~:flip-y": null
+ }
+ },
+ "~udeb18501-60d5-8066-8006-50753a954c6e": {
+ "~#shape": {
+ "~:y": null,
+ "~: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",
+ "~:content": {
+ "~#penpot/path-data": "~bAQAAAAAAAAAAAAAAAAAAAAAAAAAAgJ9DAEAIRAIAAAAAAAAAAAAAAAAAAAAAAAAAAIDMQwAA4kM="
+ },
+ "~:name": "Path",
+ "~:width": null,
+ "~:type": "~:path",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 318.9999945163727,
+ "~:y": 452
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 408.9999945163727,
+ "~:y": 452
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 408.9999945163727,
+ "~:y": 545
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 318.9999945163727,
+ "~:y": 545
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~udeb18501-60d5-8066-8006-50753a954c6e",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 10,
+ "~:stroke-cap-start": "~:line-arrow",
+ "~:stroke-cap-end": "~:line-arrow",
+ "~:stroke-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:stops": [
+ {
+ "~:color": "#ff0000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#000000",
+ "~:offset": 1,
+ "~:opacity": 0
+ }
+ ]
+ }
+ }
+ ],
+ "~:x": null,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 318.9999945163727,
+ "~:y": 452,
+ "~:width": 90,
+ "~:height": 93,
+ "~:x1": 318.9999945163727,
+ "~:y1": 452,
+ "~:x2": 408.9999945163727,
+ "~:y2": 545
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": null,
+ "~:flip-y": null
+ }
+ },
+ "~udeb18501-60d5-8066-8006-50754213576e": {
+ "~#shape": {
+ "~:y": null,
+ "~: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",
+ "~:content": {
+ "~#penpot/path-data": "~bAQAAAAAAAAAAAAAAAAAAAAAAAAAAwAZEAEAIRAIAAAAAAAAAAAAAAAAAAAAAAAAAAEAdRAAA4kM="
+ },
+ "~:name": "Path",
+ "~:width": null,
+ "~:type": "~:path",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 538.999981880188,
+ "~:y": 452
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 628.999981880188,
+ "~:y": 452
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 628.999981880188,
+ "~:y": 545
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 538.999981880188,
+ "~:y": 545
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~udeb18501-60d5-8066-8006-50754213576e",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 10,
+ "~:stroke-cap-start": "~:line-arrow",
+ "~:stroke-cap-end": "~:line-arrow",
+ "~:stroke-opacity": 1,
+ "~:stroke-image": {
+ "~:id": "~u202c1104-9385-81d3-8006-507560ce29e3",
+ "~:width": 872,
+ "~:height": 1400,
+ "~:mtype": "image/jpeg",
+ "~:name": "Aptenodytes_forsteri_-Snow_Hill_Island,_Antarctica_-adults_and_juvenile-8.jpg",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:x": null,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 538.999981880188,
+ "~:y": 452,
+ "~:width": 90,
+ "~:height": 93,
+ "~:x1": 538.999981880188,
+ "~:y1": 452,
+ "~:x2": 628.999981880188,
+ "~:y2": 545
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": null,
+ "~:flip-y": null
+ }
+ },
+ "~u9202dd03-bb97-8037-8006-50752c8c937c": {
+ "~#shape": {
+ "~:y": null,
+ "~: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",
+ "~:content": {
+ "~#penpot/path-data": "~bAQAAAAAAAAAAAAAAAAAAAAAAAAAAAJ5CAEAIRAIAAAAAAAAAAAAAAAAAAAAAAAAAAAApQwAA4kM="
+ },
+ "~:name": "Path",
+ "~:width": null,
+ "~:type": "~:path",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 79,
+ "~:y": 452
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 169,
+ "~:y": 452
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 169,
+ "~:y": 545
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 79,
+ "~:y": 545
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~u9202dd03-bb97-8037-8006-50752c8c937c",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 10,
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1,
+ "~:stroke-cap-start": "~:line-arrow",
+ "~:stroke-cap-end": "~:line-arrow"
+ }
+ ],
+ "~:x": null,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 79,
+ "~:y": 452,
+ "~:width": 90,
+ "~:height": 93,
+ "~:x1": 79,
+ "~:y1": 452,
+ "~:x2": 169,
+ "~:y2": 545
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": null,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~u202c1104-9385-81d3-8006-507413ff2c9a",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u202c1104-9385-81d3-8006-507413ff2c99",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-text-align.json b/frontend/playwright/data/render-wasm/get-file-text-align.json
new file mode 100644
index 0000000000..a9b41b6937
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-text-align.json
@@ -0,0 +1,2365 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u6bd7c17d-4f59-815e-8006-5c1f6882469a",
+ "~: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": "text_align",
+ "~:revn": 1,
+ "~:modified-at": "~m1750689458888",
+ "~:vern": 0,
+ "~:id": "~u692f368b-63ca-8141-8006-62925640b827",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u6bd7c17d-4f59-815e-8006-5c1f68846e43",
+ "~:created-at": "~m1750689445124",
+ "~:data": {
+ "~:pages": [
+ "~u692f368b-63ca-8141-8006-62925640b828"
+ ],
+ "~:pages-index": {
+ "~u692f368b-63ca-8141-8006-62925640b828": {
+ "~:objects": {
+ "~u00000000-0000-0000-0000-000000000000": {
+ "~#shape": {
+ "~:y": 0,
+ "~:hide-fill-on-export": false,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:name": "Root Frame",
+ "~:width": 0.01,
+ "~:type": "~:frame",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 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,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 0,
+ "~:y": 0,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~ufedf3ca0-a74b-80e6-8006-6292579408ec",
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f0",
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f4"
+ ]
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f4": {
+ "~#shape": {
+ "~:y": 456,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:index": 9,
+ "~:name": "Group",
+ "~:width": 1715,
+ "~:type": "~:group",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 456
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 2052,
+ "~:y": 456
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 2052,
+ "~:y": 657
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 657
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408f4",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 337,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 337,
+ "~:y": 456,
+ "~:width": 1715,
+ "~:height": 201,
+ "~:x1": 337,
+ "~:y1": 456,
+ "~:x2": 2052,
+ "~:y2": 657
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": false,
+ "~:height": 201,
+ "~:flip-y": false,
+ "~:shapes": [
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f5",
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f6",
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f7"
+ ]
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f5": {
+ "~#shape": {
+ "~:y": 456,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1a85igulykn",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "m5baoxjmyj",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "vertical alignment top"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "7o72h10fl4",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 509,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 456
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 846,
+ "~:y": 456
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 846,
+ "~:y": 657
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 657
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408f5",
+ "~:parent-id": "~ufedf3ca0-a74b-80e6-8006-6292579408f4",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 501.6000061035156,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": -2.399993896484375,
+ "~:width": 331.29998779296875,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 337,
+ "~:x1": 0,
+ "~:y2": 45.600006103515625,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 331.29998779296875,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "vertical alignment top"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 337,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 337,
+ "~:y": 456,
+ "~:width": 509,
+ "~:height": 201,
+ "~:x1": 337,
+ "~:y1": 456,
+ "~:x2": 846,
+ "~:y2": 657
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 201,
+ "~:flip-y": null
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f6": {
+ "~#shape": {
+ "~:y": 456,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1a85igulykn",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "m5baoxjmyj",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "vertical alignment center"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "7o72h10fl4",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "center"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 509,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 940,
+ "~:y": 456
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1449,
+ "~:y": 456
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1449,
+ "~:y": 657
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 940,
+ "~:y": 657
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408f6",
+ "~:parent-id": "~ufedf3ca0-a74b-80e6-8006-6292579408f4",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 580.5,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 76.5,
+ "~:width": 375.33331298828125,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 940,
+ "~:x1": 0,
+ "~:y2": 124.5,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 375.33331298828125,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "vertical alignment center"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 940,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 940,
+ "~:y": 456,
+ "~:width": 509,
+ "~:height": 201,
+ "~:x1": 940,
+ "~:y1": 456,
+ "~:x2": 1449,
+ "~:y2": 657
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 201,
+ "~:flip-y": null
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f7": {
+ "~#shape": {
+ "~:y": 456,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1a85igulykn",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "m5baoxjmyj",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "vertical alignment bottom"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "7o72h10fl4",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "bottom"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 509,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1543,
+ "~:y": 456
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 2052,
+ "~:y": 456
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 2052,
+ "~:y": 657
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1543,
+ "~:y": 657
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408f7",
+ "~:parent-id": "~ufedf3ca0-a74b-80e6-8006-6292579408f4",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 659.3999938964844,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 155.39999389648438,
+ "~:width": 392.816650390625,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1543,
+ "~:x1": 0,
+ "~:y2": 203.39999389648438,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 392.816650390625,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "vertical alignment bottom"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1543,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1543,
+ "~:y": 456,
+ "~:width": 509,
+ "~:height": 201,
+ "~:x1": 1543,
+ "~:y1": 456,
+ "~:x2": 2052,
+ "~:y2": 657
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 201,
+ "~:flip-y": null
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f0": {
+ "~#shape": {
+ "~:y": 1059.5,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:index": 4,
+ "~:name": "Group",
+ "~:width": 1655,
+ "~:type": "~:group",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 1059.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1992,
+ "~:y": 1059.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1992,
+ "~:y": 1260.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 1260.5
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408f0",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 337,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 337,
+ "~:y": 1059.5,
+ "~:width": 1655,
+ "~:height": 201,
+ "~:x1": 337,
+ "~:y1": 1059.5,
+ "~:x2": 1992,
+ "~:y2": 1260.5
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": false,
+ "~:height": 201,
+ "~:flip-y": false,
+ "~:shapes": [
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f1",
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f2",
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f3"
+ ]
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f1": {
+ "~#shape": {
+ "~:y": 1059.5,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1a85igulykn",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "591pq8tjdw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "vertical and text alignment with multiple lines and horizontal align"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "justify",
+ "~:font-id": "sourcesanspro",
+ "~:key": "7fglhdq8u7",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 489,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 1059.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 826,
+ "~:y": 1059.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 826,
+ "~:y": 1260.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 1260.5
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408f1",
+ "~:parent-id": "~ufedf3ca0-a74b-80e6-8006-6292579408f0",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 1105.1000061035156,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": -2.399993896484375,
+ "~:width": 111.66667175292969,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 337,
+ "~:x1": 0,
+ "~:y2": 45.600006103515625,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 111.66667175292969,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "vertical"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1105.1000061035156,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": -2.399993896484375,
+ "~:width": 58.06666564941406,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 457.0500030517578,
+ "~:x1": 120.05000305175781,
+ "~:y2": 45.600006103515625,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 178.11666870117188,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "and"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1105.1000061035156,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": -2.399993896484375,
+ "~:width": 57.633331298828125,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 523.5166625976562,
+ "~:x1": 186.51666259765625,
+ "~:y2": 45.600006103515625,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 244.14999389648438,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "text"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1105.1000061035156,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": -2.399993896484375,
+ "~:width": 153.79998779296875,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 589.5333404541016,
+ "~:x1": 252.53334045410156,
+ "~:y2": 45.600006103515625,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 406.3333282470703,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "alignment"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1105.1000061035156,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": -2.399993896484375,
+ "~:width": 66.43333435058594,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 751.7333374023438,
+ "~:x1": 414.73333740234375,
+ "~:y2": 45.600006103515625,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 481.1666717529297,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "with"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1148.3000030517578,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 40.80000305175781,
+ "~:width": 126.61666870117188,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 337,
+ "~:x1": 0,
+ "~:y2": 88.80000305175781,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 126.61666870117188,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "multiple"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1148.3000030517578,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 40.80000305175781,
+ "~:width": 70.64999389648438,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 485.4166717529297,
+ "~:x1": 148.4166717529297,
+ "~:y2": 88.80000305175781,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 219.06666564941406,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "lines"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1148.3000030517578,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 40.80000305175781,
+ "~:width": 58.06666564941406,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 577.8833312988281,
+ "~:x1": 240.88333129882812,
+ "~:y2": 88.80000305175781,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 298.9499969482422,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "and"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1148.3000030517578,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 40.80000305175781,
+ "~:width": 153.68333435058594,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 657.7666625976562,
+ "~:x1": 320.76666259765625,
+ "~:y2": 88.80000305175781,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 474.4499969482422,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "horizontal"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1191.5,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 84,
+ "~:width": 74.26666259765625,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 337,
+ "~:x1": 0,
+ "~:y2": 132,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 74.26666259765625,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "align"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 337,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 337,
+ "~:y": 1059.5,
+ "~:width": 489,
+ "~:height": 201,
+ "~:x1": 337,
+ "~:y1": 1059.5,
+ "~:x2": 826,
+ "~:y2": 1260.5
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 201,
+ "~:flip-y": null
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f2": {
+ "~#shape": {
+ "~:y": 1059.5,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1a85igulykn",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1px1cyplb74",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "vertical and text alignment with multiple lines and horizontal align"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "center",
+ "~:font-id": "sourcesanspro",
+ "~:key": "19zkh07leb",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "center"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 489,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 902,
+ "~:y": 1059.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1391,
+ "~:y": 1059.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1391,
+ "~:y": 1260.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 902,
+ "~:y": 1260.5
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408f2",
+ "~:parent-id": "~ufedf3ca0-a74b-80e6-8006-6292579408f0",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 1140.8000030517578,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 33.30000305175781,
+ "~:width": 483.9333190917969,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 904.5333404541016,
+ "~:x1": 2.5333404541015625,
+ "~:y2": 81.30000305175781,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 486.46665954589844,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "vertical and text alignment with "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1184,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 76.5,
+ "~:width": 438.0833435058594,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 927.4499969482422,
+ "~:x1": 25.449996948242188,
+ "~:y2": 124.5,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 463.53334045410156,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "multiple lines and horizontal "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1227.1999969482422,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 119.69999694824219,
+ "~:width": 74.26666259765625,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1109.3666687011719,
+ "~:x1": 207.36666870117188,
+ "~:y2": 167.6999969482422,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 281.6333312988281,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "align"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 902,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 902,
+ "~:y": 1059.5,
+ "~:width": 489,
+ "~:height": 201,
+ "~:x1": 902,
+ "~:y1": 1059.5,
+ "~:x2": 1391,
+ "~:y2": 1260.5
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 201,
+ "~:flip-y": null
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408f3": {
+ "~#shape": {
+ "~:y": 1059.5,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1a85igulykn",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1byaarcwres",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "vertical and text alignment with multiple lines and horizontal align"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "right",
+ "~:font-id": "sourcesanspro",
+ "~:key": "p20hz0wokq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "bottom"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 489,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1503,
+ "~:y": 1059.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1992,
+ "~:y": 1059.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1992,
+ "~:y": 1260.5
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1503,
+ "~:y": 1260.5
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408f3",
+ "~:parent-id": "~ufedf3ca0-a74b-80e6-8006-6292579408f0",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 1176.5,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 69,
+ "~:width": 483.933349609375,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1508.066665649414,
+ "~:x1": 5.0666656494140625,
+ "~:y2": 117,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 489.00001525878906,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "vertical and text alignment with "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1219.6999969482422,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 112.19999694824219,
+ "~:width": 438.08331298828125,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1553.9166717529297,
+ "~:x1": 50.91667175292969,
+ "~:y2": 160.1999969482422,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 488.99998474121094,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "multiple lines and horizontal "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1262.8999938964844,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 155.39999389648438,
+ "~:width": 74.26666259765625,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1917.7333374023438,
+ "~:x1": 414.73333740234375,
+ "~:y2": 203.39999389648438,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 489,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "align"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1503,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1503,
+ "~:y": 1059.5,
+ "~:width": 489,
+ "~:height": 201,
+ "~:x1": 1503,
+ "~:y1": 1059.5,
+ "~:x2": 1992,
+ "~:y2": 1260.5
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 201,
+ "~:flip-y": null
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408ec": {
+ "~#shape": {
+ "~:y": 757.75,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:index": 5,
+ "~:name": "Group",
+ "~:width": 1655,
+ "~:type": "~:group",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 757.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1992,
+ "~:y": 757.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1992,
+ "~:y": 958.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 958.75
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408ec",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 337,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 337,
+ "~:y": 757.75,
+ "~:width": 1655,
+ "~:height": 201,
+ "~:x1": 337,
+ "~:y1": 757.75,
+ "~:x2": 1992,
+ "~:y2": 958.75
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": false,
+ "~:height": 201,
+ "~:flip-y": false,
+ "~:shapes": [
+ "~ufedf3ca0-a74b-80e6-8006-6292579408ed",
+ "~ufedf3ca0-a74b-80e6-8006-6292579408ee",
+ "~ufedf3ca0-a74b-80e6-8006-6292579408ef"
+ ]
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408ed": {
+ "~#shape": {
+ "~:y": 757.75,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1a85igulykn",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "591pq8tjdw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "vertical and text alignment with multiple lines"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "7fglhdq8u7",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 489,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 757.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 826,
+ "~:y": 757.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 826,
+ "~:y": 958.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 337,
+ "~:y": 958.75
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408ed",
+ "~:parent-id": "~ufedf3ca0-a74b-80e6-8006-6292579408ec",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 803.3500061035156,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": -2.399993896484375,
+ "~:width": 483.933349609375,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 337,
+ "~:x1": 0,
+ "~:y2": 45.600006103515625,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 483.933349609375,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "vertical and text alignment with "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 846.5500030517578,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 40.80000305175781,
+ "~:width": 204.53334045410156,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 337,
+ "~:x1": 0,
+ "~:y2": 88.80000305175781,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 204.53334045410156,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "multiple lines"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 337,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 337,
+ "~:y": 757.75,
+ "~:width": 489,
+ "~:height": 201,
+ "~:x1": 337,
+ "~:y1": 757.75,
+ "~:x2": 826,
+ "~:y2": 958.75
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 201,
+ "~:flip-y": null
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408ee": {
+ "~#shape": {
+ "~:y": 757.75,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1a85igulykn",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "591pq8tjdw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "vertical and text alignment with multiple lines"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "7fglhdq8u7",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "center"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 489,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 920,
+ "~:y": 757.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1409,
+ "~:y": 757.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1409,
+ "~:y": 958.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 920,
+ "~:y": 958.75
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408ee",
+ "~:parent-id": "~ufedf3ca0-a74b-80e6-8006-6292579408ec",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 860.6499938964844,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 54.899993896484375,
+ "~:width": 483.933349609375,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 920,
+ "~:x1": 0,
+ "~:y2": 102.89999389648438,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 483.933349609375,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "vertical and text alignment with "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 903.8500061035156,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 98.10000610351562,
+ "~:width": 204.53334045410156,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 920,
+ "~:x1": 0,
+ "~:y2": 146.10000610351562,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 204.53334045410156,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "multiple lines"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 920,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 920,
+ "~:y": 757.75,
+ "~:width": 489,
+ "~:height": 201,
+ "~:x1": 920,
+ "~:y1": 757.75,
+ "~:x2": 1409,
+ "~:y2": 958.75
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 201,
+ "~:flip-y": null
+ }
+ },
+ "~ufedf3ca0-a74b-80e6-8006-6292579408ef": {
+ "~#shape": {
+ "~:y": 757.75,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1a85igulykn",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "591pq8tjdw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "vertical and text alignment with multiple lines"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "7fglhdq8u7",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "bottom"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 489,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1503,
+ "~:y": 757.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1992,
+ "~:y": 757.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1992,
+ "~:y": 958.75
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1503,
+ "~:y": 958.75
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ufedf3ca0-a74b-80e6-8006-6292579408ef",
+ "~:parent-id": "~ufedf3ca0-a74b-80e6-8006-6292579408ec",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 917.9499969482422,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 112.19999694824219,
+ "~:width": 483.933349609375,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1503,
+ "~:x1": 0,
+ "~:y2": 160.1999969482422,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 483.933349609375,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "vertical and text alignment with "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 961.1499938964844,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "36px",
+ "~:font-weight": "400",
+ "~:y1": 155.39999389648438,
+ "~:width": 204.53334045410156,
+ "~:text-decoration": "rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1503,
+ "~:x1": 0,
+ "~:y2": 203.39999389648438,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 204.53334045410156,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 48,
+ "~:text": "multiple lines"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1503,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1503,
+ "~:y": 757.75,
+ "~:width": 489,
+ "~:height": 201,
+ "~:x1": 1503,
+ "~:y1": 757.75,
+ "~:x2": 1992,
+ "~:y2": 958.75
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 201,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~u692f368b-63ca-8141-8006-62925640b828",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u692f368b-63ca-8141-8006-62925640b827",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-text-custom-fonts.json b/frontend/playwright/data/render-wasm/get-file-text-custom-fonts.json
new file mode 100644
index 0000000000..3bbc2f87bb
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-text-custom-fonts.json
@@ -0,0 +1,486 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "variants/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~: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": "Text: Custom Fonts",
+ "~:revn": 13,
+ "~:modified-at": "~m1750151641034",
+ "~:vern": 0,
+ "~:id": "~u434b0541-fa2f-802f-8006-59827d964a9b",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u53a7ff09-2228-81d3-8006-4b5ea964593b",
+ "~:created-at": "~m1750081311326",
+ "~:data": {
+ "~:pages": [
+ "~u434b0541-fa2f-802f-8006-59827d964a9c"
+ ],
+ "~:pages-index": {
+ "~u434b0541-fa2f-802f-8006-59827d964a9c": {
+ "~:objects": {
+ "~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
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~: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,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u7d85a63e-18e7-809f-8006-59827fe8501e",
+ "~u7d85a63e-18e7-809f-8006-59833ef5fcef"
+ ]
+ }
+ },
+ "~u7d85a63e-18e7-809f-8006-59827fe8501e": {
+ "~#shape": {
+ "~:y": 451.9999962296588,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "xgmgu1frox",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "custom-7d85a63e-18e7-809f-8006-5983057a9b7c",
+ "~:key": "ee7vl7klqs",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "normal-400",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Nodesto Caps Condensed\"",
+ "~:text": "Penpot & Dragons"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "center",
+ "~:font-id": "custom-7d85a63e-18e7-809f-8006-5983057a9b7c",
+ "~:key": "17bt2f4evfs",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "normal-400",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Nodesto Caps Condensed\""
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Penpot & Dragons",
+ "~:width": 403.99995992417394,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 744.0000211580308,
+ "~:y": 451.9999962296588
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1147.9999810822046,
+ "~:y": 451.9999962296588
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1147.9999810822046,
+ "~:y": 537.9999971833331
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 744.0000211580308,
+ "~:y": 537.9999971833331
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~u7d85a63e-18e7-809f-8006-59827fe8501e",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 744.0000211580307,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 744.0000211580307,
+ "~:y": 451.9999962296588,
+ "~:width": 403.99995992417394,
+ "~:height": 86.00000095367432,
+ "~:x1": 744.0000211580307,
+ "~:y1": 451.9999962296588,
+ "~:x2": 1147.9999810822046,
+ "~:y2": 537.9999971833331
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 86.00000095367432,
+ "~:flip-y": null
+ }
+ },
+ "~u7d85a63e-18e7-809f-8006-59833ef5fcef": {
+ "~#shape": {
+ "~:y": 537.9999971833331,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "xgmgu1frox",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "custom-7d85a63e-18e7-809f-8006-59832d696634",
+ "~:key": "ee7vl7klqs",
+ "~:font-size": "36",
+ "~:font-weight": "500",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "normal-500",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Mr Eaves SC Remake\"",
+ "~:text": "Lorem Ipsum Dolors Sit Amet"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "center",
+ "~:font-id": "custom-7d85a63e-18e7-809f-8006-59832d696634",
+ "~:key": "17bt2f4evfs",
+ "~:font-size": "0",
+ "~:font-weight": "500",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "normal-500",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Mr Eaves SC Remake\""
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Penpot & Dragons",
+ "~:width": 466.0000131576671,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 712.9999941849438,
+ "~:y": 537.9999971833331
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1179.0000073426108,
+ "~:y": 537.9999971833331
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1179.0000073426108,
+ "~:y": 580.9999976601703
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 712.9999941849438,
+ "~:y": 580.9999976601703
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~u7d85a63e-18e7-809f-8006-59833ef5fcef",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 712.9999941849437,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 712.9999941849437,
+ "~:y": 537.9999971833331,
+ "~:width": 466.0000131576671,
+ "~:height": 43.00000047683716,
+ "~:x1": 712.9999941849437,
+ "~:y1": 537.9999971833331,
+ "~:x2": 1179.0000073426108,
+ "~:y2": 580.9999976601703
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 43.00000047683716,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~u434b0541-fa2f-802f-8006-59827d964a9c",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u434b0541-fa2f-802f-8006-59827d964a9b",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-text-decoration.json b/frontend/playwright/data/render-wasm/get-file-text-decoration.json
new file mode 100644
index 0000000000..562790b374
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-text-decoration.json
@@ -0,0 +1,2950 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u6bd7c17d-4f59-815e-8006-5c1f6882469a",
+ "~: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": "test_text_decoration",
+ "~:revn": 71,
+ "~:modified-at": "~m1753090236677",
+ "~:vern": 0,
+ "~:id": "~ud6c33e7b-7b64-80f3-8006-785098582f1d",
+ "~:is-shared": false,
+ "~:migrations": {
+ "~#ordered-set": [
+ "legacy-2",
+ "legacy-3",
+ "legacy-5",
+ "legacy-6",
+ "legacy-7",
+ "legacy-8",
+ "legacy-9",
+ "legacy-10",
+ "legacy-11",
+ "legacy-12",
+ "legacy-13",
+ "legacy-14",
+ "legacy-16",
+ "legacy-17",
+ "legacy-18",
+ "legacy-19",
+ "legacy-25",
+ "legacy-26",
+ "legacy-27",
+ "legacy-28",
+ "legacy-29",
+ "legacy-31",
+ "legacy-32",
+ "legacy-33",
+ "legacy-34",
+ "legacy-36",
+ "legacy-37",
+ "legacy-38",
+ "legacy-39",
+ "legacy-40",
+ "legacy-41",
+ "legacy-42",
+ "legacy-43",
+ "legacy-44",
+ "legacy-45",
+ "legacy-46",
+ "legacy-47",
+ "legacy-48",
+ "legacy-49",
+ "legacy-50",
+ "legacy-51",
+ "legacy-52",
+ "legacy-53",
+ "legacy-54",
+ "legacy-55",
+ "legacy-56",
+ "legacy-57",
+ "legacy-59",
+ "legacy-62",
+ "legacy-65",
+ "legacy-66",
+ "legacy-67",
+ "0001-remove-tokens-from-groups",
+ "0002-normalize-bool-content-v2",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content-v2",
+ "0004-clean-shadow-color",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-v4",
+ "0009-clean-library-colors",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u6bd7c17d-4f59-815e-8006-5c1f68846e43",
+ "~:created-at": "~m1752148606307",
+ "~:data": {
+ "~:pages": [
+ "~ud6c33e7b-7b64-80f3-8006-785098582f1e"
+ ],
+ "~:pages-index": {
+ "~ud6c33e7b-7b64-80f3-8006-785098582f1e": {
+ "~:objects": {
+ "~u00000000-0000-0000-0000-000000000000": {
+ "~#shape": {
+ "~:y": 0,
+ "~:hide-fill-on-export": false,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:name": "Root Frame",
+ "~:width": 0.01,
+ "~:type": "~:frame",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 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,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 0,
+ "~:y": 0,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u38c139ee-c848-80d4-8006-865242dea382",
+ "~ue2dd1e47-c379-8002-8006-78509a3c4e5f",
+ "~ue2dd1e47-c379-8002-8006-7850bc8822f4",
+ "~ue2dd1e47-c379-8002-8006-7850f96e4ae9",
+ "~ue2dd1e47-c379-8002-8006-78510f062679",
+ "~ue2dd1e47-c379-8002-8006-7850de3e8f37",
+ "~ue2dd1e47-c379-8002-8006-7850f96e4aea",
+ "~ue2dd1e47-c379-8002-8006-7850ae42e717",
+ "~ue2dd1e47-c379-8002-8006-78510f062678",
+ "~ue2dd1e47-c379-8002-8006-785197fca88d",
+ "~ue2dd1e47-c379-8002-8006-7851b9d948e5"
+ ]
+ }
+ },
+ "~ue2dd1e47-c379-8002-8006-7850bc8822f4": {
+ "~#shape": {
+ "~:y": 237.0000021805366,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1u5z8e0kt88",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cq42ierf4m",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "this is an inner stroke"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1595dptq0av",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 638.9999826118988,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 870.9999749172263,
+ "~:y": 237.00000218053657
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1509.999957529125,
+ "~:y": 237.00000218053657
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1509.999957529125,
+ "~:y": 324.0000021805366
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 870.9999749172263,
+ "~:y": 324.0000021805366
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:hidden": false,
+ "~:id": "~ue2dd1e47-c379-8002-8006-7850bc8822f4",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 327.1999991287788,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -3.8000030517578125,
+ "~:width": 638.36669921875,
+ "~:text-decoration": "underline rgb(16, 68, 222)",
+ "~:letter-spacing": "normal",
+ "~:x": 870.9999749172262,
+ "~:x1": 0,
+ "~:y2": 90.19999694824219,
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 638.36669921875,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "this is an inner stroke"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#c100c8",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 870.9999749172262,
+ "~:shadow": [],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 870.9999749172262,
+ "~:y": 237.0000021805366,
+ "~:width": 638.9999826118988,
+ "~:height": 87.00000000000003,
+ "~:x1": 870.9999749172262,
+ "~:y1": 237.0000021805366,
+ "~:x2": 1509.999957529125,
+ "~:y2": 324.0000021805366
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 87.00000000000003,
+ "~:flip-y": null
+ }
+ },
+ "~ue2dd1e47-c379-8002-8006-7850ae42e717": {
+ "~#shape": {
+ "~:y": 242.00000464717482,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1u5z8e0kt88",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "160ttioqvlh",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "aaa "
+ },
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "oq8cktt92j",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "bbb and this is a longer text that I'm trying "
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1om4rmmvuxq",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#cd20bb",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "to break"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cuj5swwjwo",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#cd20bb",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 616.9999914049297,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 103.9999971822214,
+ "~:y": 242.00000464717482
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 720.999988587151,
+ "~:y": 242.00000464717482
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 720.999988587151,
+ "~:y": 586.0000047266474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 103.9999971822214,
+ "~:y": 586.0000047266474
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:hidden": false,
+ "~:id": "~ue2dd1e47-c379-8002-8006-7850ae42e717",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 610.1999835819006,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -3.8000030517578125,
+ "~:width": 125.06666564941406,
+ "~:text-decoration": "line-through rgb(32, 75, 205)",
+ "~:letter-spacing": "normal",
+ "~:x": 187.99999899513318,
+ "~:x1": 0,
+ "~:y2": 90.19999694824219,
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 125.06666564941406,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "aaa "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 610.1999835819006,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -3.8000030517578125,
+ "~:width": 504.6000061035156,
+ "~:text-decoration": "line-through rgb(32, 75, 205)",
+ "~:letter-spacing": "normal",
+ "~:x": 313.06666464454725,
+ "~:x1": 125.06666564941406,
+ "~:y2": 90.19999694824219,
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 629.6666717529297,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "bbb and this is a "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 696.599992737174,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 82.60000610351562,
+ "~:width": 587.1500244140625,
+ "~:text-decoration": "line-through rgb(32, 75, 205)",
+ "~:letter-spacing": "normal",
+ "~:x": 187.99999899513318,
+ "~:x1": 0,
+ "~:y2": 176.60000610351562,
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 587.1500244140625,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "longer text that I'm "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 782.9999866336584,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 169,
+ "~:width": 192.14999389648438,
+ "~:text-decoration": "line-through rgb(32, 75, 205)",
+ "~:letter-spacing": "normal",
+ "~:x": 187.99999899513318,
+ "~:x1": 0,
+ "~:y2": 263,
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 192.14999389648438,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "trying "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 782.9999866336584,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 169,
+ "~:width": 248.48333740234375,
+ "~:text-decoration": "line-through rgb(205, 32, 187)",
+ "~:letter-spacing": "normal",
+ "~:x": 380.14999289161756,
+ "~:x1": 192.14999389648438,
+ "~:y2": 263,
+ "~:fills": [
+ {
+ "~:fill-color": "#cd20bb",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 440.6333312988281,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "to break"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 3,
+ "~:stroke-opacity": 1,
+ "~:stroke-image": {
+ "~:id": "~ud6c33e7b-7b64-80f3-8006-78509a3a2d21",
+ "~:width": 2560,
+ "~:height": 1325,
+ "~:mtype": "image/png",
+ "~:name": "background_4.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:x": 103.99999718222136,
+ "~:shadow": [],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 103.99999718222136,
+ "~:y": 242.00000464717482,
+ "~:width": 616.9999914049297,
+ "~:height": 344.00000007947256,
+ "~:x1": 103.99999718222136,
+ "~:y1": 242.00000464717482,
+ "~:x2": 720.999988587151,
+ "~:y2": 586.0000047266474
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 344.00000007947256,
+ "~:flip-y": null
+ }
+ },
+ "~ue2dd1e47-c379-8002-8006-7850de3e8f37": {
+ "~#shape": {
+ "~:y": 352.00000015261764,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1u5z8e0kt88",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cq42ierf4m",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "this is an inner stroke"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1595dptq0av",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 638.9999826118988,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 870.999972771459,
+ "~:y": 352.00000015261764
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1509.9999553833577,
+ "~:y": 352.00000015261764
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1509.9999553833577,
+ "~:y": 438.00000130678507
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 870.999972771459,
+ "~:y": 438.00000130678507
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:hidden": false,
+ "~:id": "~ue2dd1e47-c379-8002-8006-7850de3e8f37",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 442.19999710085983,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -3.8000030517578125,
+ "~:width": 638.36669921875,
+ "~:text-decoration": "line-through rgb(16, 68, 222)",
+ "~:letter-spacing": "normal",
+ "~:x": 870.9999727714589,
+ "~:x1": 0,
+ "~:y2": 90.19999694824219,
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 638.36669921875,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "this is an inner stroke"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#c100c8",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 870.9999727714589,
+ "~:shadow": [],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 870.9999727714589,
+ "~:y": 352.00000015261764,
+ "~:width": 638.9999826118988,
+ "~:height": 86.00000115416742,
+ "~:x1": 870.9999727714589,
+ "~:y1": 352.00000015261764,
+ "~:x2": 1509.9999553833577,
+ "~:y2": 438.00000130678507
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 86.00000115416742,
+ "~:flip-y": null
+ }
+ },
+ "~ue2dd1e47-c379-8002-8006-78509a3c4e5f": {
+ "~#shape": {
+ "~:y": -169.99999571851134,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-height",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1u5z8e0kt88",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "160ttioqvlh",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "aaa "
+ },
+ {
+ "~:line-height": "1",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "oq8cktt92j",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "bbb and this is a longer text that I'm trying "
+ },
+ {
+ "~:line-height": "1",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1om4rmmvuxq",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#cd20bb",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "to break"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cuj5swwjwo",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#cd20bb",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 918.0000003792857,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 161.00000125861249,
+ "~:y": -169.99999571851134
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1079.000001637898,
+ "~:y": -169.99999571851134
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1079.000001637898,
+ "~:y": -25.999992049812093
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 161.00000125861249,
+ "~:y": -25.999992049812093
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:hidden": false,
+ "~:id": "~ue2dd1e47-c379-8002-8006-78509a3c4e5f",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": -87,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -11,
+ "~:width": 125.06666564941406,
+ "~:text-decoration": "underline rgb(32, 75, 205)",
+ "~:letter-spacing": "normal",
+ "~:x": 161,
+ "~:x1": 0,
+ "~:y2": 83,
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 125.06666564941406,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "aaa "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": -87,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -11,
+ "~:width": 1091.75,
+ "~:text-decoration": "underline rgb(32, 75, 205)",
+ "~:letter-spacing": "normal",
+ "~:x": 286.06666564941406,
+ "~:x1": 125.06666564941406,
+ "~:y2": 83,
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 1216.816665649414,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "bbb and this is a longer text that I'm "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": -15,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 61,
+ "~:width": 192.14999389648438,
+ "~:text-decoration": "underline rgb(32, 75, 205)",
+ "~:letter-spacing": "normal",
+ "~:x": 161,
+ "~:x1": 0,
+ "~:y2": 155,
+ "~:fills": [
+ {
+ "~:fill-color": "#204bcd",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 192.14999389648438,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "trying "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": -15,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 61,
+ "~:width": 248.48333740234375,
+ "~:text-decoration": "underline rgb(205, 32, 187)",
+ "~:letter-spacing": "normal",
+ "~:x": 353.1499938964844,
+ "~:x1": 192.14999389648438,
+ "~:y2": 155,
+ "~:fills": [
+ {
+ "~:fill-color": "#cd20bb",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 440.6333312988281,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "to break"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 2,
+ "~:stroke-opacity": 1,
+ "~:stroke-image": {
+ "~:id": "~ud6c33e7b-7b64-80f3-8006-78509a3a2d21",
+ "~:width": 2560,
+ "~:height": 1325,
+ "~:mtype": "image/png",
+ "~:name": "background_4.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:x": 161.00000125861243,
+ "~:shadow": [],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 161.00000125861243,
+ "~:y": -169.99999571851134,
+ "~:width": 918.0000003792857,
+ "~:height": 144.00000366869926,
+ "~:x1": 161.00000125861243,
+ "~:y1": -169.99999571851134,
+ "~:x2": 1079.000001637898,
+ "~:y2": -25.99999204981208
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 144.00000366869926,
+ "~:flip-y": null
+ }
+ },
+ "~u38c139ee-c848-80d4-8006-865242dea382": {
+ "~#shape": {
+ "~:y": 242.00000248741526,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1mxs0ewwp99",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "10oavl240v3",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "this is"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1ibzt9aof7z",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "6jo4ptrcmt",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": ""
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "10ycxmel3fk",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "209ny6c3lke",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "a text that contains"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "s4kqryho97",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "ggjfjlzupm",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "new lines"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1768rvmgl86",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "7ra7cu2h32",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": ""
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1i06ol6sfgj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1l3gjdva3k4",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": ""
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2fu0jrcz4a2",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1mm12t8z9re",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "an some breaking lines"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1n4dr5ql8td",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 674.7362248725649,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1603.999927294974,
+ "~:y": 242.00000248741503
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 2278.7361521675393,
+ "~:y": 242.00000248741503
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 2278.7361521675393,
+ "~:y": 1064.0000024874153
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1603.999927294974,
+ "~:y": 1064.0000024874153
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u38c139ee-c848-80d4-8006-865242dea382",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 332.1999994356572,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -3.8000030517578125,
+ "~:width": 173.71665954589844,
+ "~:text-decoration": "line-through rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1603.9999079088113,
+ "~:x1": 0,
+ "~:y2": 90.19999694824219,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 173.71665954589844,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "this is"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 418.60000859093066,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 82.60000610351562,
+ "~:width": 14.51666259765625,
+ "~:text-decoration": "line-through rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1603.9999079088113,
+ "~:x1": 0,
+ "~:y2": 176.60000610351562,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 14.51666259765625,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": " "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 505.00000248741503,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 169,
+ "~:width": 577.13330078125,
+ "~:text-decoration": "line-through rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1603.9999079088113,
+ "~:x1": 0,
+ "~:y2": 263,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 577.13330078125,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "a text that contains"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 591.3999963838994,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 255.39999389648438,
+ "~:width": 282.8833312988281,
+ "~:text-decoration": "line-through rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1603.9999079088113,
+ "~:x1": 0,
+ "~:y2": 349.3999938964844,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 282.8833312988281,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "new lines"
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 677.7999902803838,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 341.79998779296875,
+ "~:width": 14.51666259765625,
+ "~:text-decoration": "line-through rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1603.9999079088113,
+ "~:x1": 0,
+ "~:y2": 435.79998779296875,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 14.51666259765625,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": " "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 764.2000146944463,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 428.20001220703125,
+ "~:width": 14.51666259765625,
+ "~:text-decoration": "line-through rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1603.9999079088113,
+ "~:x1": 0,
+ "~:y2": 522.2000122070312,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 14.51666259765625,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": " "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 850.5999780733528,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 514.5999755859375,
+ "~:width": 548.9833374023438,
+ "~:text-decoration": "line-through rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1603.9999079088113,
+ "~:x1": 0,
+ "~:y2": 608.5999755859375,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 548.9833374023438,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "an some breaking "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 937.0000024874153,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": 601,
+ "~:width": 141.26666259765625,
+ "~:text-decoration": "line-through rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 1603.9999079088113,
+ "~:x1": 0,
+ "~:y2": 695,
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 141.26666259765625,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "lines"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 1603.999927294974,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1603.999927294974,
+ "~:y": 242.00000248741526,
+ "~:width": 674.7362248725649,
+ "~:height": 822.0000000000002,
+ "~:x1": 1603.999927294974,
+ "~:y1": 242.00000248741526,
+ "~:x2": 2278.736152167539,
+ "~:y2": 1064.0000024874155
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 822.0000000000002,
+ "~:flip-y": null
+ }
+ },
+ "~ue2dd1e47-c379-8002-8006-78510f062678": {
+ "~#shape": {
+ "~:y": 816.0000001526176,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1u5z8e0kt88",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cq42ierf4m",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "this is a center stroke"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1595dptq0av",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 698.0000563466937,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 871.0000165508966,
+ "~:y": 816.0000001526176
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1569.0000728975901,
+ "~:y": 816.0000001526176
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1569.0000728975901,
+ "~:y": 902.0000013067851
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 871.0000165508966,
+ "~:y": 902.0000013067851
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:hidden": false,
+ "~:id": "~ue2dd1e47-c379-8002-8006-78510f062678",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 906.1999971008598,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -3.8000030517578125,
+ "~:width": 632.5166625976562,
+ "~:text-decoration": "line-through rgb(16, 68, 222)",
+ "~:letter-spacing": "normal",
+ "~:x": 871.0000165508965,
+ "~:x1": 0,
+ "~:y2": 90.19999694824219,
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 632.5166625976562,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "this is a center stroke"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#c100c8",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 871.0000165508965,
+ "~:shadow": [],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 871.0000165508965,
+ "~:y": 816.0000001526176,
+ "~:width": 698.0000563466937,
+ "~:height": 86.00000115416742,
+ "~:x1": 871.0000165508965,
+ "~:y1": 816.0000001526176,
+ "~:x2": 1569.0000728975901,
+ "~:y2": 902.0000013067851
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 86.00000115416742,
+ "~:flip-y": null
+ }
+ },
+ "~ue2dd1e47-c379-8002-8006-78510f062679": {
+ "~#shape": {
+ "~:y": 700.9999564041693,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1u5z8e0kt88",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cq42ierf4m",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "this is a center stroke"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1595dptq0av",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 633,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 871.0000188947865,
+ "~:y": 700.9999564041693
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1504.0000188947865,
+ "~:y": 700.9999564041693
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1504.0000188947865,
+ "~:y": 787.9999564041693
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 871.0000188947865,
+ "~:y": 787.9999564041693
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:hidden": false,
+ "~:id": "~ue2dd1e47-c379-8002-8006-78510f062679",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 791.1999533524115,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -3.8000030517578125,
+ "~:width": 632.5166625976562,
+ "~:text-decoration": "underline rgb(16, 68, 222)",
+ "~:letter-spacing": "normal",
+ "~:x": 871.0000188947865,
+ "~:x1": 0,
+ "~:y2": 90.19999694824219,
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 632.5166625976562,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "this is a center stroke"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#c100c8",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 871.0000188947865,
+ "~:shadow": [],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 871.0000188947865,
+ "~:y": 700.9999564041693,
+ "~:width": 633,
+ "~:height": 87,
+ "~:x1": 871.0000188947865,
+ "~:y1": 700.9999564041693,
+ "~:x2": 1504.0000188947865,
+ "~:y2": 787.9999564041693
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 87,
+ "~:flip-y": null
+ }
+ },
+ "~ue2dd1e47-c379-8002-8006-7851b9d948e5": {
+ "~#shape": {
+ "~:y": 931.000015710521,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1p37s6olgps",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1qkhdo08sfk",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "I "
+ },
+ {
+ "~:line-height": "1",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1firkih92rc",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "❤️ Unicode"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1t9rwzf81g3",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 405.1900070264876,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 187.9999948925661,
+ "~:y": 931.000015710521
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 593.1900019190537,
+ "~:y": 931.000015710521
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 593.1900019190537,
+ "~:y": 1122.999941323922
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 187.9999948925661,
+ "~:y": 1122.999941323922
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~ue2dd1e47-c379-8002-8006-7851b9d948e5",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 1014.0000081062317,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -11,
+ "~:width": 33.43333435058594,
+ "~:text-decoration": "line-through rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 187.99999888267257,
+ "~:x1": 0,
+ "~:y2": 83,
+ "~:fills": [],
+ "~:x2": 33.43333435058594,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "I "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 1014.0000081062317,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -11,
+ "~:width": 353.8999938964844,
+ "~:text-decoration": "line-through rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 221.4333332332585,
+ "~:x1": 33.43333435058594,
+ "~:y2": 83,
+ "~:fills": [],
+ "~:x2": 387.3333282470703,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "❤️ Unicode"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 187.99999489256606,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 187.99999489256606,
+ "~:y": 931.000015710521,
+ "~:width": 405.1900070264876,
+ "~:height": 191.99992561340105,
+ "~:x1": 187.99999489256606,
+ "~:y1": 931.000015710521,
+ "~:x2": 593.1900019190537,
+ "~:y2": 1122.999941323922
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 191.99992561340105,
+ "~:flip-y": null
+ }
+ },
+ "~ue2dd1e47-c379-8002-8006-785197fca88d": {
+ "~#shape": {
+ "~:y": 816.0000004768372,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1p37s6olgps",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1qkhdo08sfk",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "I "
+ },
+ {
+ "~:line-height": "1",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1firkih92rc",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "❤️ Unicode"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1t9rwzf81g3",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 388,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 192.0000118613243,
+ "~:y": 816.0000004768372
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 580.0000118613243,
+ "~:y": 816.0000004768372
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 580.0000118613243,
+ "~:y": 888.0000004768372
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 192.0000118613243,
+ "~:y": 888.0000004768372
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~ue2dd1e47-c379-8002-8006-785197fca88d",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 899.0000004768372,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -11,
+ "~:width": 33.43333435058594,
+ "~:text-decoration": "underline rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 192.0000118613243,
+ "~:x1": 0,
+ "~:y2": 83,
+ "~:fills": [],
+ "~:x2": 33.43333435058594,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "I "
+ }
+ },
+ {
+ "~#rect": {
+ "~:y": 899.0000004768372,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -11,
+ "~:width": 353.8999938964844,
+ "~:text-decoration": "underline rgb(0, 0, 0)",
+ "~:letter-spacing": "normal",
+ "~:x": 225.43334621191025,
+ "~:x1": 33.43333435058594,
+ "~:y2": 83,
+ "~:fills": [],
+ "~:x2": 387.3333282470703,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "❤️ Unicode"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 192.0000118613243,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 192.0000118613243,
+ "~:y": 816.0000004768372,
+ "~:width": 388,
+ "~:height": 72,
+ "~:x1": 192.0000118613243,
+ "~:y1": 816.0000004768372,
+ "~:x2": 580.0000118613243,
+ "~:y2": 888.0000004768372
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 72,
+ "~:flip-y": null
+ }
+ },
+ "~ue2dd1e47-c379-8002-8006-7850f96e4ae9": {
+ "~#shape": {
+ "~:y": 470.99998692174745,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1u5z8e0kt88",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cq42ierf4m",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "this is an outer stroke"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1595dptq0av",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "underline",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 643.9999999999999,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 871.0000188947865,
+ "~:y": 470.9999869217475
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1515.0000188947863,
+ "~:y": 470.9999869217475
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1515.0000188947863,
+ "~:y": 557.9999869217474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 871.0000188947865,
+ "~:y": 557.9999869217474
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:hidden": false,
+ "~:id": "~ue2dd1e47-c379-8002-8006-7850f96e4ae9",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 561.1999838699896,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -3.8000030517578125,
+ "~:width": 643.7333374023438,
+ "~:text-decoration": "underline rgb(16, 68, 222)",
+ "~:letter-spacing": "normal",
+ "~:x": 871.0000188947866,
+ "~:x1": 0,
+ "~:y2": 90.19999694824219,
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 643.7333374023438,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "this is an outer stroke"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#c100c8",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 871.0000188947866,
+ "~:shadow": [],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 871.0000188947866,
+ "~:y": 470.99998692174745,
+ "~:width": 643.9999999999999,
+ "~:height": 87,
+ "~:x1": 871.0000188947866,
+ "~:y1": 470.99998692174745,
+ "~:x2": 1515.0000188947865,
+ "~:y2": 557.9999869217474
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 87,
+ "~:flip-y": null
+ }
+ },
+ "~ue2dd1e47-c379-8002-8006-7850f96e4aea": {
+ "~#shape": {
+ "~:y": 585.9999696350395,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1u5z8e0kt88",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cq42ierf4m",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "this is an outer stroke"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1595dptq0av",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 698.0000563466937,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 871.0000165508966,
+ "~:y": 585.9999696350395
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1569.0000728975901,
+ "~:y": 585.9999696350395
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1569.0000728975901,
+ "~:y": 671.9999707892069
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 871.0000165508966,
+ "~:y": 671.9999707892069
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:hidden": false,
+ "~:id": "~ue2dd1e47-c379-8002-8006-7850f96e4aea",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:position-data": [
+ {
+ "~#rect": {
+ "~:y": 676.1999665832817,
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-size": "72px",
+ "~:font-weight": "400",
+ "~:y1": -3.8000030517578125,
+ "~:width": 643.7333374023438,
+ "~:text-decoration": "line-through rgb(16, 68, 222)",
+ "~:letter-spacing": "normal",
+ "~:x": 871.0000165508965,
+ "~:x1": 0,
+ "~:y2": 90.19999694824219,
+ "~:fills": [
+ {
+ "~:fill-color": "#1044de",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:x2": 643.7333374023438,
+ "~:direction": "ltr",
+ "~:font-family": "\"sourcesanspro\"",
+ "~:height": 94,
+ "~:text": "this is an outer stroke"
+ }
+ }
+ ],
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#c100c8",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 871.0000165508965,
+ "~:shadow": [],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 871.0000165508965,
+ "~:y": 585.9999696350395,
+ "~:width": 698.0000563466937,
+ "~:height": 86.00000115416742,
+ "~:x1": 871.0000165508965,
+ "~:y1": 585.9999696350395,
+ "~:x2": 1569.0000728975901,
+ "~:y2": 671.9999707892069
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 86.00000115416742,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~ud6c33e7b-7b64-80f3-8006-785098582f1e",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~ud6c33e7b-7b64-80f3-8006-785098582f1d",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-text-emoji-board.json b/frontend/playwright/data/render-wasm/get-file-text-emoji-board.json
new file mode 100644
index 0000000000..e5514661e5
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-text-emoji-board.json
@@ -0,0 +1,791 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u6bd7c17d-4f59-815e-8006-5c1f6882469a",
+ "~: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": "garden",
+ "~:revn": 26,
+ "~:modified-at": "~m1750423208667",
+ "~:vern": 0,
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e999f38f210",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u6bd7c17d-4f59-815e-8006-5c1f68846e43",
+ "~:created-at": "~m1750422919396",
+ "~:data": {
+ "~:pages": [
+ "~u6bd7c17d-4f59-815e-8006-5e999f38f211"
+ ],
+ "~:pages-index": {
+ "~u6bd7c17d-4f59-815e-8006-5e999f38f211": {
+ "~:objects": {
+ "~u00000000-0000-0000-0000-000000000000": {
+ "~#shape": {
+ "~:y": 0,
+ "~:hide-fill-on-export": false,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:name": "Root Frame",
+ "~:width": 0.01,
+ "~:type": "~:frame",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 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,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 0,
+ "~:y": 0,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~uef609b51-0d34-80f3-8006-5e99c014febd"
+ ]
+ }
+ },
+ "~uef609b51-0d34-80f3-8006-5e99a0e7e241": {
+ "~#shape": {
+ "~:y": 224.0000021457672,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "24e85t84f3p",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:font-id": "",
+ "~:key": "1vetvwgrfb6",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": "",
+ "~:text": "▫️▫️🌲▫️🌲🌲🌲▫️🌲🌲"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:text-align": "",
+ "~:font-id": "",
+ "~:key": "r0535lnzdr",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:text-direction": "",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": ""
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:font-id": "",
+ "~:key": "1yug53qv91w",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": "",
+ "~:text": "🌲🐛🌲🌲▫️🌲🌲🌲🌲🌲"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:text-align": "",
+ "~:font-id": "",
+ "~:key": "2aqkfsbxb5i",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:text-direction": "",
+ "~:type": "paragraph",
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": ""
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:font-id": "",
+ "~:key": "22yly6s8yv3",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": "",
+ "~:text": "🌲🌲▫️🌲▫️🌲🌲🌰🌲🌲"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:text-align": "",
+ "~:font-id": "",
+ "~:key": "q9ovldxs6h",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:text-direction": "",
+ "~:type": "paragraph",
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": ""
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:font-id": "",
+ "~:key": "2e29fo2vfyu",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": "",
+ "~:text": "🌲🌲▫️🌲▫️🌲🌲▫️🌲▫️"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:text-align": "",
+ "~:font-id": "",
+ "~:key": "1f8krcpsg8l",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:text-direction": "",
+ "~:type": "paragraph",
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": ""
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:font-id": "",
+ "~:key": "1ehkqv5vril",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": "",
+ "~:text": "▫️▫️▫️🐌🌲🍁🌲▫️🥕🌲"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:text-align": "",
+ "~:font-id": "",
+ "~:key": "kikos098xa",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:text-direction": "",
+ "~:type": "paragraph",
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": ""
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:font-id": "",
+ "~:key": "2cxzm7orynt",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": "",
+ "~:text": "🌲🌲🐰🌲▫️▫️🌲🌲🌲▫️"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:text-align": "",
+ "~:font-id": "",
+ "~:key": "so4z3gbyhs",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:text-direction": "",
+ "~:type": "paragraph",
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": ""
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:font-id": "",
+ "~:key": "1ey304k5xqb",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": "",
+ "~:text": "🌲🌲🌲🥕☁️🌲🐰▫️🌲🌲"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:text-align": "",
+ "~:font-id": "",
+ "~:key": "1orh5xhi3o3",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:text-direction": "",
+ "~:type": "paragraph",
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": ""
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:font-id": "",
+ "~:key": "8aout8mor6",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": "",
+ "~:text": "▫️🌲▫️▫️🌲▫️🌲🌲▫️🌲"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:text-align": "",
+ "~:font-id": "",
+ "~:key": "lir8cs117z",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:text-direction": "",
+ "~:type": "paragraph",
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": ""
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:font-id": "",
+ "~:key": "1iqonahtkum",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": "",
+ "~:text": "🌲▫️🌲▫️🌲▫️▫️🌲▫️🌲"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:text-align": "",
+ "~:font-id": "",
+ "~:key": "2urfb0xejy",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:text-direction": "",
+ "~:type": "paragraph",
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": ""
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "",
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:font-id": "",
+ "~:key": "1e06otc9bbq",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": "",
+ "~:text": "▫️🌲▫️▫️🌲▫️🌲▫️🍃🌲"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "",
+ "~:text-align": "",
+ "~:font-id": "",
+ "~:key": "1t55y3u9pg3",
+ "~:font-size": "16",
+ "~:font-weight": "",
+ "~:typography-ref-file": null,
+ "~:text-direction": "",
+ "~:type": "paragraph",
+ "~:font-variant-id": "",
+ "~:text-decoration": "",
+ "~:letter-spacing": "",
+ "~:fills": null,
+ "~:font-family": ""
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": ""
+ },
+ "~:name": "▫️▫️🌲▫️🌲🌲🌲▫️🌲🌲🌲🐛🌲🌲▫️🌲🌲🌲🌲🌲🌲🌲▫️🌲▫️🌲🌲🌰🌲🌲🌲🌲▫️🌲▫️🌲🌲▫️🌲▫️▫️▫️▫️🐌🌲🍁🌲▫️🥕🌲🌲🌲🐰🌲▫️▫️🌲🌲🌲▫️🌲🌲🌲🥕☁️🌲🐰▫️🌲🌲▫️🌲▫️▫️🌲▫️🌲🌲▫️🌲🌲▫️🌲▫️🌲▫️▫️🌲▫️🌲▫️🌲▫️▫️🌲▫️🌲▫️🍃🌲",
+ "~:width": 200.00000894069672,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 224.99999487400055,
+ "~:y": 224.0000021457672
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 425.00000381469727,
+ "~:y": 224.0000021457672
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 425.00000381469727,
+ "~:y": 414.0000021457672
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 224.99999487400055,
+ "~:y": 414.0000021457672
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~uef609b51-0d34-80f3-8006-5e99a0e7e241",
+ "~:parent-id": "~uef609b51-0d34-80f3-8006-5e99c014febd",
+ "~:frame-id": "~uef609b51-0d34-80f3-8006-5e99c014febd",
+ "~:x": 224.99999487400055,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 224.99999487400055,
+ "~:y": 224.0000021457672,
+ "~:width": 200.00000894069672,
+ "~:height": 190,
+ "~:x1": 224.99999487400055,
+ "~:y1": 224.0000021457672,
+ "~:x2": 425.00000381469727,
+ "~:y2": 414.0000021457672
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 190,
+ "~:flip-y": null
+ }
+ },
+ "~uef609b51-0d34-80f3-8006-5e99c014febd": {
+ "~#shape": {
+ "~:y": 194.00000454845173,
+ "~:hide-fill-on-export": false,
+ "~:layout-gap-type": "~:multiple",
+ "~:layout-padding": {
+ "~:p1": 18.999997597315485,
+ "~:p2": 13.999998715849017,
+ "~:p3": 18.999997597315485,
+ "~:p4": 13.999998715849017
+ },
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:layout-wrap-type": "~:nowrap",
+ "~:layout": "~:flex",
+ "~:hide-in-viewer": false,
+ "~:name": "Garden",
+ "~:layout-align-items": "~:center",
+ "~:width": 249.99999881089417,
+ "~:layout-padding-type": "~:simple",
+ "~:type": "~:frame",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 199.99999615815156,
+ "~:y": 194.00000454845173
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 449.99999496904576,
+ "~:y": 194.00000454845173
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 449.99999496904576,
+ "~:y": 444.00000708125077
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 199.99999615815156,
+ "~:y": 444.00000708125077
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:layout-item-h-sizing": "~:fix",
+ "~:proportion-lock": false,
+ "~:layout-gap": {
+ "~:row-gap": 0,
+ "~:column-gap": 0
+ },
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:r3": 0,
+ "~:layout-justify-content": "~:center",
+ "~:r1": 0,
+ "~:id": "~uef609b51-0d34-80f3-8006-5e99c014febd",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:layout-flex-dir": "~:row",
+ "~:layout-align-content": "~:stretch",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": 199.99999615815153,
+ "~:proportion": 1,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 199.99999615815153,
+ "~:y": 194.00000454845173,
+ "~:width": 249.99999881089417,
+ "~:height": 250.00000253279904,
+ "~:x1": 199.99999615815153,
+ "~:y1": 194.00000454845173,
+ "~:x2": 449.9999949690457,
+ "~:y2": 444.00000708125077
+ }
+ },
+ "~:fills": [
+ {
+ "~:fill-color": "#939a85",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:flip-x": null,
+ "~:height": 250.00000253279904,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~uef609b51-0d34-80f3-8006-5e99a0e7e241"
+ ]
+ }
+ }
+ },
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e999f38f211",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e999f38f210",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-text-google-fonts.json b/frontend/playwright/data/render-wasm/get-file-text-google-fonts.json
new file mode 100644
index 0000000000..3cd2ddce0d
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-text-google-fonts.json
@@ -0,0 +1,420 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "variants/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~: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": "Text: Google Fonts",
+ "~:revn": 9,
+ "~:modified-at": "~m1750150559868",
+ "~:vern": 0,
+ "~:id": "~u434b0541-fa2f-802f-8006-5981e47bd732",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u53a7ff09-2228-81d3-8006-4b5ea964593b",
+ "~:created-at": "~m1750081154582",
+ "~:data": {
+ "~:pages": [
+ "~u434b0541-fa2f-802f-8006-5981e47bd733"
+ ],
+ "~:pages-index": {
+ "~u434b0541-fa2f-802f-8006-5981e47bd733": {
+ "~:objects": {
+ "~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
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~: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,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u83b40135-4e19-8020-8006-598226885685"
+ ]
+ }
+ },
+ "~u83b40135-4e19-8020-8006-598226885685": {
+ "~#shape": {
+ "~:y": 399.99997901916055,
+ "~: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",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "u3udm63y8h",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "gfont-eb-garamond",
+ "~:key": "hjb7822r9z",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"EB Garamond\"",
+ "~:text": "This is an example text"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "gfont-eb-garamond",
+ "~:key": "2dka2139qgl",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"EB Garamond\""
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "gfont-eb-garamond",
+ "~:key": "26bngh5on3d",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"EB Garamond\"",
+ "~:text": "with "
+ },
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "gfont-fira-code",
+ "~:key": "23oemgkf8le",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Fira Code\"",
+ "~:text": "Google Fonts"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "gfont-fira-code",
+ "~:key": "1dxk7oihnqh",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Fira Code\""
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "This is an example textwith Google Fonts",
+ "~:width": 325.00011493483566,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 680.9999634764606,
+ "~:y": 399.9999790191605
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1006.0000784112963,
+ "~:y": 399.9999790191605
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1006.0000784112963,
+ "~:y": 658.0000126361805
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 680.9999634764606,
+ "~:y": 658.0000126361805
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:id": "~u83b40135-4e19-8020-8006-598226885685",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 680.9999634764606,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 680.9999634764606,
+ "~:y": 399.99997901916055,
+ "~:width": 325.00011493483566,
+ "~:height": 258.00003361702,
+ "~:x1": 680.9999634764606,
+ "~:y1": 399.99997901916055,
+ "~:x2": 1006.0000784112963,
+ "~:y2": 658.0000126361806
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 258.00003361702,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~u434b0541-fa2f-802f-8006-5981e47bd733",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u434b0541-fa2f-802f-8006-5981e47bd732",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-text-images.json b/frontend/playwright/data/render-wasm/get-file-text-images.json
new file mode 100644
index 0000000000..a320062251
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-text-images.json
@@ -0,0 +1,2761 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u6bd7c17d-4f59-815e-8006-5c1f6882469a",
+ "~: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": "New File 7",
+ "~:revn": 66,
+ "~:modified-at": "~m1750422866332",
+ "~:vern": 0,
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e96453952b0",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u6bd7c17d-4f59-815e-8006-5c1f68846e43",
+ "~:created-at": "~m1750422040808",
+ "~:data": {
+ "~:pages": [
+ "~u6bd7c17d-4f59-815e-8006-5e96453952b1"
+ ],
+ "~:pages-index": {
+ "~u6bd7c17d-4f59-815e-8006-5e96453952b1": {
+ "~:objects": {
+ "~u00000000-0000-0000-0000-000000000000": {
+ "~#shape": {
+ "~:y": 0,
+ "~:hide-fill-on-export": false,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:name": "Root Frame",
+ "~:width": 0.01,
+ "~:type": "~:frame",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 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,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 0,
+ "~:y": 0,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u4b4f5ff7-44ac-808a-8006-5e979761b8f6",
+ "~uef609b51-0d34-80f3-8006-5e99460c12bb",
+ "~u4b4f5ff7-44ac-808a-8006-5e980c195547",
+ "~uef609b51-0d34-80f3-8006-5e9934d0368b",
+ "~u4b4f5ff7-44ac-808a-8006-5e968ea9bb4b",
+ "~u4b4f5ff7-44ac-808a-8006-5e974ec5dede",
+ "~u4b4f5ff7-44ac-808a-8006-5e9771af0c6e",
+ "~u4b4f5ff7-44ac-808a-8006-5e9775677e20",
+ "~u4b4f5ff7-44ac-808a-8006-5e977f9e30ea",
+ "~u4b4f5ff7-44ac-808a-8006-5e98597c0d17",
+ "~uef609b51-0d34-80f3-8006-5e995b054836",
+ "~uef609b51-0d34-80f3-8006-5e9929d3fa47",
+ "~uef609b51-0d34-80f3-8006-5e9880f79b0b",
+ "~uef609b51-0d34-80f3-8006-5e98ba9c0e14",
+ "~uef609b51-0d34-80f3-8006-5e98cb62758b"
+ ]
+ }
+ },
+ "~u4b4f5ff7-44ac-808a-8006-5e977f9e30ea": {
+ "~#shape": {
+ "~:y": 318.9999734620724,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "sgffdxj3ur",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "qropsg36kw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e9765e0fabd",
+ "~:width": 443,
+ "~:height": 441,
+ "~:mtype": "image/png",
+ "~:name": "pattern.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "hello world"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "dctsmh90vx",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e9765e0fabd",
+ "~:width": 443,
+ "~:height": 441,
+ "~:mtype": "image/png",
+ "~:name": "pattern.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 170.00000804662704,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 188.99999672174454,
+ "~:y": 318.9999734620724
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 359.0000047683716,
+ "~:y": 318.9999734620724
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 359.0000047683716,
+ "~:y": 361.99997143551445
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 188.99999672174454,
+ "~:y": 361.99997143551445
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u4b4f5ff7-44ac-808a-8006-5e977f9e30ea",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 3,
+ "~:stroke-color": "#f513de",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 188.99999672174454,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 188.99999672174454,
+ "~:y": 318.9999734620724,
+ "~:width": 170.00000804662704,
+ "~:height": 42.99999797344208,
+ "~:x1": 188.99999672174454,
+ "~:y1": 318.9999734620724,
+ "~:x2": 359.0000047683716,
+ "~:y2": 361.99997143551445
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 42.99999797344208,
+ "~:flip-y": null
+ }
+ },
+ "~u4b4f5ff7-44ac-808a-8006-5e968ea9bb4b": {
+ "~#shape": {
+ "~:y": 262.9999810914669,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "sgffdxj3ur",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "qropsg36kw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#15d12e",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "hello world"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "dctsmh90vx",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#15d12e",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 170.00000804662704,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 188.99999672174454,
+ "~:y": 262.9999810914669
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 359.0000047683716,
+ "~:y": 262.9999810914669
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 359.0000047683716,
+ "~:y": 305.999979064909
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 188.99999672174454,
+ "~:y": 305.999979064909
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u4b4f5ff7-44ac-808a-8006-5e968ea9bb4b",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 3,
+ "~:stroke-opacity": 1,
+ "~:stroke-image": {
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e97441071cc",
+ "~:width": 443,
+ "~:height": 441,
+ "~:mtype": "image/png",
+ "~:name": "pattern.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:x": 188.99999672174454,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 188.99999672174454,
+ "~:y": 262.9999810914669,
+ "~:width": 170.00000804662704,
+ "~:height": 42.99999797344208,
+ "~:x1": 188.99999672174454,
+ "~:y1": 262.9999810914669,
+ "~:x2": 359.0000047683716,
+ "~:y2": 305.999979064909
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 42.99999797344208,
+ "~:flip-y": null
+ }
+ },
+ "~uef609b51-0d34-80f3-8006-5e99460c12bb": {
+ "~#shape": {
+ "~:y": 550.7500897584513,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 0.8191520396891823,
+ "~:b": 0.5735764429202543,
+ "~:c": -0.5735764429202543,
+ "~:d": 0.8191520396891825,
+ "~:e": 3.979039320256561e-13,
+ "~:f": 4.547473508864641e-13
+ }
+ },
+ "~:rotation": 35,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "sgffdxj3ur",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "48novmbeyv",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "萎ポくろ"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1adbszhly27",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 151.12151347433988,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 211.91854190645938,
+ "~:y": 511.2984505926968
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 335.7100379098812,
+ "~:y": 597.9781907400339
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 311.0462523788645,
+ "~:y": 633.2017262836614
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 187.25475637544255,
+ "~:y": 546.5219861363242
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 0.8191520396891827,
+ "~:b": -0.5735764429202546,
+ "~:c": 0.5735764429202546,
+ "~:d": 0.8191520396891825,
+ "~:e": -5.867761854680291e-13,
+ "~:f": -1.442788980666409e-13
+ }
+ },
+ "~:id": "~uef609b51-0d34-80f3-8006-5e99460c12bb",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#13a9f5",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 185.92164040549193,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 185.92164040549193,
+ "~:y": 550.7500897584513,
+ "~:width": 151.12151347433988,
+ "~:height": 42.99999735945553,
+ "~:x1": 185.92164040549193,
+ "~:y1": 550.7500897584513,
+ "~:x2": 337.04315387983183,
+ "~:y2": 593.7500871179068
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 42.99999735945553,
+ "~:flip-y": null
+ }
+ },
+ "~u4b4f5ff7-44ac-808a-8006-5e980c195547": {
+ "~#shape": {
+ "~:y": 423.0000117740001,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "sgffdxj3ur",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "48novmbeyv",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "萎ポくろ"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "center",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1adbszhly27",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 90.99999851290627,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 183.0000022321518,
+ "~:y": 423.0000117740001
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 274.00000074505806,
+ "~:y": 423.0000117740001
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 274.00000074505806,
+ "~:y": 509.00000772088424
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 183.0000022321518,
+ "~:y": 509.00000772088424
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u4b4f5ff7-44ac-808a-8006-5e980c195547",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#13a9f5",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 183.0000022321518,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 183.0000022321518,
+ "~:y": 423.0000117740001,
+ "~:width": 90.99999851290627,
+ "~:height": 85.99999594688416,
+ "~:x1": 183.0000022321518,
+ "~:y1": 423.0000117740001,
+ "~:x2": 274.00000074505806,
+ "~:y2": 509.00000772088424
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 85.99999594688416,
+ "~:flip-y": null
+ }
+ },
+ "~u4b4f5ff7-44ac-808a-8006-5e9771af0c6e": {
+ "~#shape": {
+ "~:y": 318.9999734620724,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "sgffdxj3ur",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "qropsg36kw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e9765e0fabd",
+ "~:width": 443,
+ "~:height": 441,
+ "~:mtype": "image/png",
+ "~:name": "pattern.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "hello world"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "dctsmh90vx",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e9765e0fabd",
+ "~:width": 443,
+ "~:height": 441,
+ "~:mtype": "image/png",
+ "~:name": "pattern.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 170.00000804662704,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 188.99999672174454,
+ "~:y": 318.9999734620724
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 359.0000047683716,
+ "~:y": 318.9999734620724
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 359.0000047683716,
+ "~:y": 361.99997143551445
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 188.99999672174454,
+ "~:y": 361.99997143551445
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u4b4f5ff7-44ac-808a-8006-5e9771af0c6e",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 3,
+ "~:stroke-color": "#f513de",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 188.99999672174454,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 188.99999672174454,
+ "~:y": 318.9999734620724,
+ "~:width": 170.00000804662704,
+ "~:height": 42.99999797344208,
+ "~:x1": 188.99999672174454,
+ "~:y1": 318.9999734620724,
+ "~:x2": 359.0000047683716,
+ "~:y2": 361.99997143551445
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 42.99999797344208,
+ "~:flip-y": null
+ }
+ },
+ "~u4b4f5ff7-44ac-808a-8006-5e9775677e20": {
+ "~#shape": {
+ "~:y": 318.9999734620724,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "sgffdxj3ur",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "qropsg36kw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e9765e0fabd",
+ "~:width": 443,
+ "~:height": 441,
+ "~:mtype": "image/png",
+ "~:name": "pattern.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "hello world"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "dctsmh90vx",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e9765e0fabd",
+ "~:width": 443,
+ "~:height": 441,
+ "~:mtype": "image/png",
+ "~:name": "pattern.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 170.00000804662704,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 188.99999672174454,
+ "~:y": 318.9999734620724
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 359.0000047683716,
+ "~:y": 318.9999734620724
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 359.0000047683716,
+ "~:y": 361.99997143551445
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 188.99999672174454,
+ "~:y": 361.99997143551445
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u4b4f5ff7-44ac-808a-8006-5e9775677e20",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 3,
+ "~:stroke-color": "#f513de",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 188.99999672174454,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 188.99999672174454,
+ "~:y": 318.9999734620724,
+ "~:width": 170.00000804662704,
+ "~:height": 42.99999797344208,
+ "~:x1": 188.99999672174454,
+ "~:y1": 318.9999734620724,
+ "~:x2": 359.0000047683716,
+ "~:y2": 361.99997143551445
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 42.99999797344208,
+ "~:flip-y": null
+ }
+ },
+ "~uef609b51-0d34-80f3-8006-5e995b054836": {
+ "~#shape": {
+ "~:y": 553.4624040823653,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 0.8191520419506291,
+ "~:b": 0.5735764396905747,
+ "~:c": -0.5735764396905756,
+ "~:d": 0.8191520419506286,
+ "~:e": 1.0800249583553523e-12,
+ "~:f": 1.3642420526593924e-12
+ }
+ },
+ "~:rotation": 35,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "26bzlb8q9d5",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "xz2qooo55r",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "🔥"
+ },
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "bcqsnxgrmw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "👩🏿\u200d🚀"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "vaxp7xayt3",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "👺"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1con6sm4643",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "🚀"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "10k4ei57t29",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "qopgplegd0",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": ""
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cmxi1d1shv",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 180.00002134842015,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 282.3845213766798,
+ "~:y": 506.00002254111644
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 429.831906415395,
+ "~:y": 609.2437939303707
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 403.4473830964035,
+ "~:y": 646.9247979902748
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 255.99999805768823,
+ "~:y": 543.6810266010206
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 0.8191520419506279,
+ "~:b": -0.5735764396905741,
+ "~:c": 0.573576439690575,
+ "~:d": 0.8191520419506283,
+ "~:e": -1.667201749434965e-12,
+ "~:f": -4.980447927604343e-13
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~uef609b51-0d34-80f3-8006-5e995b054836",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 252.9159415623315,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 252.9159415623315,
+ "~:y": 553.4624040823653,
+ "~:width": 180.00002134842015,
+ "~:height": 46.000012366660485,
+ "~:x1": 252.9159415623315,
+ "~:y1": 553.4624040823653,
+ "~:x2": 432.91596291075166,
+ "~:y2": 599.4624164490258
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 46.000012366660485,
+ "~:flip-y": null
+ }
+ },
+ "~uef609b51-0d34-80f3-8006-5e98ba9c0e14": {
+ "~#shape": {
+ "~:y": 321.99998189293103,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "23iz9tepv1l",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "t8mkaljm92",
+ "~:font-size": "24",
+ "~:font-weight": "normal",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Droid Sans Mono\", \"monospace\", monospace",
+ "~:text": "❤️ I "
+ },
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "59c3kg7hq8",
+ "~:font-size": "24",
+ "~:font-weight": "normal",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Droid Sans Mono\", \"monospace\", monospace",
+ "~:text": "love"
+ },
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2ezx1yspc8b",
+ "~:font-size": "24",
+ "~:font-weight": "normal",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Droid Sans Mono\", \"monospace\", monospace",
+ "~:text": " unicode ❤️"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "center",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2bd6up3z17w",
+ "~:font-size": "0",
+ "~:font-weight": "normal",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Droid Sans Mono\", \"monospace\", monospace"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": ""
+ },
+ "~:name": "❤️ I hate emoji ❤️",
+ "~:width": 208.99999821277834,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 377.99998569086324,
+ "~:y": 321.99998189293103
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 586.9999839036416,
+ "~:y": 321.99998189293103
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 586.9999839036416,
+ "~:y": 351.9999805349818
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 377.99998569086324,
+ "~:y": 351.9999805349818
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~uef609b51-0d34-80f3-8006-5e98ba9c0e14",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#ff0000",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 377.99998569086324,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 377.99998569086324,
+ "~:y": 321.99998189293103,
+ "~:width": 208.99999821277834,
+ "~:height": 29.99999864205074,
+ "~:x1": 377.99998569086324,
+ "~:y1": 321.99998189293103,
+ "~:x2": 586.9999839036416,
+ "~:y2": 351.9999805349818
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 29.99999864205074,
+ "~:flip-y": null
+ }
+ },
+ "~uef609b51-0d34-80f3-8006-5e9934d0368b": {
+ "~#shape": {
+ "~:y": 423.00001160904685,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "sgffdxj3ur",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "48novmbeyv",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "萎ポくろ"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "center",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1adbszhly27",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 90.99999851290625,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 266.9999941259201,
+ "~:y": 423.00001160904685
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 357.99999263882637,
+ "~:y": 423.00001160904685
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 357.99999263882637,
+ "~:y": 509.000007555931
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 266.9999941259201,
+ "~:y": 509.000007555931
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~uef609b51-0d34-80f3-8006-5e9934d0368b",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#13a9f5",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 266.9999941259201,
+ "~:shadow": [
+ {
+ "~:color": {
+ "~:color": "#000000",
+ "~:opacity": 0.2
+ },
+ "~:spread": 0,
+ "~:offset-y": 4,
+ "~:style": "~:drop-shadow",
+ "~:blur": 4,
+ "~:hidden": false,
+ "~:id": "~uef609b51-0d34-80f3-8006-5e99383d5786",
+ "~:offset-x": 4
+ }
+ ],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 266.9999941259201,
+ "~:y": 423.00001160904685,
+ "~:width": 90.99999851290625,
+ "~:height": 85.99999594688416,
+ "~:x1": 266.9999941259201,
+ "~:y1": 423.00001160904685,
+ "~:x2": 357.99999263882637,
+ "~:y2": 509.000007555931
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 85.99999594688416,
+ "~:flip-y": null
+ }
+ },
+ "~uef609b51-0d34-80f3-8006-5e98cb62758b": {
+ "~#shape": {
+ "~:y": 373.0000183528921,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "23iz9tepv1l",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "t8mkaljm92",
+ "~:font-size": "24",
+ "~:font-weight": "normal",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Droid Sans Mono\", \"monospace\", monospace",
+ "~:text": "❤️ I "
+ },
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "59c3kg7hq8",
+ "~:font-size": "24",
+ "~:font-weight": "normal",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Droid Sans Mono\", \"monospace\", monospace",
+ "~:text": "love"
+ },
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2ezx1yspc8b",
+ "~:font-size": "24",
+ "~:font-weight": "normal",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Droid Sans Mono\", \"monospace\", monospace",
+ "~:text": " unicode ❤️"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "center",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2bd6up3z17w",
+ "~:font-size": "0",
+ "~:font-weight": "normal",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "line-through",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Droid Sans Mono\", \"monospace\", monospace"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": ""
+ },
+ "~:name": "❤️ I hate emoji ❤️",
+ "~:width": 148.00002662937948,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 350.0000044405443,
+ "~:y": 373.0000183528921
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 498.0000310699237,
+ "~:y": 373.0000183528921
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 498.0000310699237,
+ "~:y": 439.9999976771411
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 350.0000044405443,
+ "~:y": 439.9999976771411
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~uef609b51-0d34-80f3-8006-5e98cb62758b",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#ff0000",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 350.00000444054433,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 350.00000444054433,
+ "~:y": 373.0000183528921,
+ "~:width": 148.00002662937948,
+ "~:height": 66.99997932424901,
+ "~:x1": 350.00000444054433,
+ "~:y1": 373.0000183528921,
+ "~:x2": 498.0000310699238,
+ "~:y2": 439.9999976771411
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 66.99997932424901,
+ "~:flip-y": null
+ }
+ },
+ "~uef609b51-0d34-80f3-8006-5e9880f79b0b": {
+ "~#shape": {
+ "~:y": 270.00000668846326,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "23iz9tepv1l",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "t8mkaljm92",
+ "~:font-size": "24",
+ "~:font-weight": "normal",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Droid Sans Mono\", \"monospace\", monospace",
+ "~:text": "❤️ I love unicode ❤️"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "center",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2bd6up3z17w",
+ "~:font-size": "24",
+ "~:font-weight": "normal",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "\"Droid Sans Mono\", \"monospace\", monospace"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": ""
+ },
+ "~:name": "❤️ I hate emoji ❤️",
+ "~:width": 208.99999821277834,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 377.99998569086324,
+ "~:y": 270.00000668846326
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 586.9999839036416,
+ "~:y": 270.00000668846326
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 586.9999839036416,
+ "~:y": 300.000005330514
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 377.99998569086324,
+ "~:y": 300.000005330514
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~uef609b51-0d34-80f3-8006-5e9880f79b0b",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#ff0000",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 377.99998569086324,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 377.99998569086324,
+ "~:y": 270.00000668846326,
+ "~:width": 208.99999821277834,
+ "~:height": 29.99999864205074,
+ "~:x1": 377.99998569086324,
+ "~:y1": 270.00000668846326,
+ "~:x2": 586.9999839036416,
+ "~:y2": 300.000005330514
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 29.99999864205074,
+ "~:flip-y": null
+ }
+ },
+ "~u4b4f5ff7-44ac-808a-8006-5e98597c0d17": {
+ "~#shape": {
+ "~:y": 455.0000055686013,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "26bzlb8q9d5",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "xz2qooo55r",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "🔥"
+ },
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "bcqsnxgrmw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "👩🏿\u200d🚀"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "vaxp7xayt3",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "👺"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1con6sm4643",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "🚀"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "10k4ei57t29",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "qopgplegd0",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": ""
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cmxi1d1shv",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 180.0000180039333,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 400.9999895156452,
+ "~:y": 455.0000055686013
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 581.0000075195785,
+ "~:y": 455.0000055686013
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 581.0000075195785,
+ "~:y": 501.0000170805595
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 400.9999895156452,
+ "~:y": 501.0000170805595
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u4b4f5ff7-44ac-808a-8006-5e98597c0d17",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 400.9999895156452,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 400.9999895156452,
+ "~:y": 455.0000055686013,
+ "~:width": 180.0000180039333,
+ "~:height": 46.00001151195818,
+ "~:x1": 400.9999895156452,
+ "~:y1": 455.0000055686013,
+ "~:x2": 581.0000075195785,
+ "~:y2": 501.0000170805595
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 46.00001151195818,
+ "~:flip-y": null
+ }
+ },
+ "~u4b4f5ff7-44ac-808a-8006-5e974ec5dede": {
+ "~#shape": {
+ "~:y": 318.9999734620724,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "sgffdxj3ur",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "qropsg36kw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e9765e0fabd",
+ "~:width": 443,
+ "~:height": 441,
+ "~:mtype": "image/png",
+ "~:name": "pattern.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "hello world"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "dctsmh90vx",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-opacity": 1,
+ "~:fill-image": {
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e9765e0fabd",
+ "~:width": 443,
+ "~:height": 441,
+ "~:mtype": "image/png",
+ "~:name": "pattern.png",
+ "~:keep-aspect-ratio": true
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 170.00000804662704,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 188.99999672174454,
+ "~:y": 318.9999734620724
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 359.0000047683716,
+ "~:y": 318.9999734620724
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 359.0000047683716,
+ "~:y": 361.99997143551445
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 188.99999672174454,
+ "~:y": 361.99997143551445
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u4b4f5ff7-44ac-808a-8006-5e974ec5dede",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 3,
+ "~:stroke-color": "#f513de",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 188.99999672174454,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 188.99999672174454,
+ "~:y": 318.9999734620724,
+ "~:width": 170.00000804662704,
+ "~:height": 42.99999797344208,
+ "~:x1": 188.99999672174454,
+ "~:y1": 318.9999734620724,
+ "~:x2": 359.0000047683716,
+ "~:y2": 361.99997143551445
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 42.99999797344208,
+ "~:flip-y": null
+ }
+ },
+ "~u4b4f5ff7-44ac-808a-8006-5e979761b8f6": {
+ "~#shape": {
+ "~:y": 371.0000077943487,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "sgffdxj3ur",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "48novmbeyv",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "萎ポくろ"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1adbszhly27",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 151.12151563216787,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 188.99999416516704,
+ "~:y": 371.0000077943487
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 340.1215097973349,
+ "~:y": 371.0000077943487
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 340.1215097973349,
+ "~:y": 414.00000576779075
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 188.99999416516704,
+ "~:y": 414.00000576779075
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u4b4f5ff7-44ac-808a-8006-5e979761b8f6",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#13a9f5",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 188.99999416516704,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 188.99999416516704,
+ "~:y": 371.0000077943487,
+ "~:width": 151.12151563216787,
+ "~:height": 42.99999797344208,
+ "~:x1": 188.99999416516704,
+ "~:y1": 371.0000077943487,
+ "~:x2": 340.1215097973349,
+ "~:y2": 414.00000576779075
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 42.99999797344208,
+ "~:flip-y": null
+ }
+ },
+ "~uef609b51-0d34-80f3-8006-5e9929d3fa47": {
+ "~#shape": {
+ "~:y": 519.0000103369729,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "26bzlb8q9d5",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "xz2qooo55r",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "🔥"
+ },
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "bcqsnxgrmw",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "👩🏿\u200d🚀"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "vaxp7xayt3",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "👺"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1con6sm4643",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": "🚀"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "10k4ei57t29",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ },
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "qopgplegd0",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro",
+ "~:text": ""
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2cmxi1d1shv",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 180.0000180039333,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 400.9999895156452,
+ "~:y": 519.0000103369729
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 581.0000075195785,
+ "~:y": 519.0000103369729
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 581.0000075195785,
+ "~:y": 565.000021848931
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 400.9999895156452,
+ "~:y": 565.000021848931
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~uef609b51-0d34-80f3-8006-5e9929d3fa47",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 400.9999895156452,
+ "~:shadow": [
+ {
+ "~:color": {
+ "~:color": "#000000",
+ "~:opacity": 0.2
+ },
+ "~:spread": 0,
+ "~:offset-y": 4,
+ "~:style": "~:drop-shadow",
+ "~:blur": 4,
+ "~:hidden": false,
+ "~:id": "~uef609b51-0d34-80f3-8006-5e992e5ed7a5",
+ "~:offset-x": 4
+ }
+ ],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 400.9999895156452,
+ "~:y": 519.0000103369729,
+ "~:width": 180.0000180039333,
+ "~:height": 46.00001151195818,
+ "~:x1": 400.9999895156452,
+ "~:y1": 519.0000103369729,
+ "~:x2": 581.0000075195785,
+ "~:y2": 565.000021848931
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 46.00001151195818,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e96453952b1",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5e96453952b0",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-text-styles.json b/frontend/playwright/data/render-wasm/get-file-text-styles.json
new file mode 100644
index 0000000000..f93f0cd48c
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-text-styles.json
@@ -0,0 +1,3098 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u6bd7c17d-4f59-815e-8006-5c1f6882469a",
+ "~: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": "New File 2",
+ "~:revn": 116,
+ "~:modified-at": "~m1750260272189",
+ "~:vern": 0,
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5c2559af4939",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u6bd7c17d-4f59-815e-8006-5c1f68846e43",
+ "~:created-at": "~m1750258221760",
+ "~:data": {
+ "~:pages": [
+ "~u6bd7c17d-4f59-815e-8006-5c2559af493a"
+ ],
+ "~:pages-index": {
+ "~u6bd7c17d-4f59-815e-8006-5c2559af493a": {
+ "~:objects": {
+ "~u00000000-0000-0000-0000-000000000000": {
+ "~#shape": {
+ "~:y": 0,
+ "~:hide-fill-on-export": false,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:name": "Root Frame",
+ "~:width": 0.01,
+ "~:type": "~:frame",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 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,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 0,
+ "~:y": 0,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u42dae815-ff2f-802c-8006-5c2cb4c7a8e0",
+ "~u58c78647-2265-8024-8006-5c2564ab99ec",
+ "~u42dae815-ff2f-802c-8006-5c293714cf2d",
+ "~u42dae815-ff2f-802c-8006-5c29848baca0",
+ "~u42dae815-ff2f-802c-8006-5c2cce142b67",
+ "~u42dae815-ff2f-802c-8006-5c2cf831ac6b",
+ "~u42dae815-ff2f-802c-8006-5c291d17afc1",
+ "~u42dae815-ff2f-802c-8006-5c2c5bb11e66"
+ ]
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2a367557d3": {
+ "~#shape": {
+ "~:y": 176.9999996522973,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 392.0000020656456,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -337.99999253234387,
+ "~:y": 176.99999965229733
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.00000953330175,
+ "~:y": 176.99999965229733
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.00000953330175,
+ "~:y": 262.99999807786116
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -337.99999253234387,
+ "~:y": 262.99999807786116
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2a367557d3",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#e50404",
+ "~:stroke-opacity": 1
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:center",
+ "~:stroke-width": 3,
+ "~:stroke-color": "#172bde",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": -337.99999253234387,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -337.99999253234387,
+ "~:y": 176.9999996522973,
+ "~:width": 392.0000020656456,
+ "~:height": 85.9999984255638,
+ "~:x1": -337.99999253234387,
+ "~:y1": 176.9999996522973,
+ "~:x2": 54.00000953330175,
+ "~:y2": 262.9999980778611
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 85.9999984255638,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2a363d3312": {
+ "~#shape": {
+ "~:y": 111.99999834853443,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 392.000006988994,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -338.00001025778175,
+ "~:y": 111.99999834853443
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 53.999996731212285,
+ "~:y": 111.99999834853443
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 53.999996731212285,
+ "~:y": 193.0000012837786
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -338.00001025778175,
+ "~:y": 193.0000012837786
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2a363d3312",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#1641ee",
+ "~:stroke-opacity": 1
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-width": 2,
+ "~:stroke-color": "#d11616",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": -338.00001025778175,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -338.00001025778175,
+ "~:y": 111.99999834853443,
+ "~:width": 392.000006988994,
+ "~:height": 81.0000029352442,
+ "~:x1": -338.00001025778175,
+ "~:y1": 111.99999834853443,
+ "~:x2": 53.99999673121226,
+ "~:y2": 193.00000128377863
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 81.0000029352442,
+ "~:flip-y": null
+ }
+ },
+ "~u58c78647-2265-8024-8006-5c2564ab99ec": {
+ "~#shape": {
+ "~:y": -248.00000404331982,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "ye7sp38a1",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1dmijf1j68k",
+ "~:font-size": "36",
+ "~:font-weight": "bold",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "bold",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0.5,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:radial",
+ "~:stops": [
+ {
+ "~:color": "#000000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#de0094",
+ "~:offset": 1,
+ "~:opacity": 1
+ }
+ ]
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "X"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "txhrpw0kcu",
+ "~:font-size": "36",
+ "~:font-weight": "bold",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "bold",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0.5,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:radial",
+ "~:stops": [
+ {
+ "~:color": "#000000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#de0094",
+ "~:offset": 1,
+ "~:opacity": 1
+ }
+ ]
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 20.9999999224288,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 74.99999446855401,
+ "~:y": -248.00000404331982
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 95.9999943909828,
+ "~:y": -248.00000404331982
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 95.9999943909828,
+ "~:y": -205.00001455697273
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 74.99999446855401,
+ "~:y": -205.00001455697273
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u58c78647-2265-8024-8006-5c2564ab99ec",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 74.99999446855401,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 74.99999446855401,
+ "~:y": -248.00000404331982,
+ "~:width": 20.9999999224288,
+ "~:height": 42.99998948634709,
+ "~:x1": 74.99999446855401,
+ "~:y1": -248.00000404331982,
+ "~:x2": 95.9999943909828,
+ "~:y2": -205.00001455697273
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 42.99998948634709,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2bdd0738be": {
+ "~#shape": {
+ "~:y": 311.00001944663495,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#d8f005",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#d8f005",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 391.9999962332266,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -338.0000058263421,
+ "~:y": 311.00001944663495
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 53.99999040688451,
+ "~:y": 311.00001944663495
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 53.99999040688451,
+ "~:y": 397.00002098229663
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -338.0000058263421,
+ "~:y": 397.00002098229663
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2bdd0738be",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 3,
+ "~:stroke-color": "#009bf5",
+ "~:stroke-opacity": 1
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 2,
+ "~:stroke-color": "#ea0e0e",
+ "~:stroke-opacity": 1
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#1a4ecc",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": -338.0000058263421,
+ "~:shadow": [
+ {
+ "~:color": {
+ "~:color": "#6314ec",
+ "~:opacity": 0.5944444444444444
+ },
+ "~:spread": 0,
+ "~:offset-y": 0,
+ "~:style": "~:inner-shadow",
+ "~:blur": 4,
+ "~:hidden": false,
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2be835a848",
+ "~:offset-x": 2
+ },
+ {
+ "~:color": {
+ "~:color": "#5414d4",
+ "~:opacity": 0.2
+ },
+ "~:spread": 0,
+ "~:offset-y": 4,
+ "~:style": "~:drop-shadow",
+ "~:blur": 4,
+ "~:hidden": false,
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2be53c569c",
+ "~:offset-x": 4
+ }
+ ],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -338.0000058263421,
+ "~:y": 311.00001944663495,
+ "~:width": 391.9999962332266,
+ "~:height": 86.00000153566162,
+ "~:x1": -338.0000058263421,
+ "~:y1": 311.00001944663495,
+ "~:x2": 53.999990406884535,
+ "~:y2": 397.0000209822966
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 86.00000153566162,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c29848baca0": {
+ "~#shape": {
+ "~:y": -204.99999612348097,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "ye7sp38a1",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1fgj5tnxjp9",
+ "~:font-size": "36",
+ "~:font-weight": "bold",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "bold",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:stops": [
+ {
+ "~:color": "#000000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#de0094",
+ "~:offset": 1,
+ "~:opacity": 1
+ }
+ ]
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2csqgsg3et3",
+ "~:font-size": "0",
+ "~:font-weight": "bold",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "bold",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:stops": [
+ {
+ "~:color": "#000000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#de0094",
+ "~:offset": 1,
+ "~:opacity": 1
+ }
+ ]
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 610.9999281976051,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 706.9999823688112,
+ "~:y": -204.99999612348097
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1317.9999105664162,
+ "~:y": -204.99999612348097
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1317.9999105664162,
+ "~:y": 403.99999206289226
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 706.9999823688112,
+ "~:y": 403.99999206289226
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u42dae815-ff2f-802c-8006-5c29848baca0",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 706.9999823688112,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 706.9999823688112,
+ "~:y": -204.99999612348097,
+ "~:width": 610.9999281976051,
+ "~:height": 608.9999881863732,
+ "~:x1": 706.9999823688112,
+ "~:y1": -204.99999612348097,
+ "~:x2": 1317.9999105664162,
+ "~:y2": 403.99999206289226
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 608.9999881863732,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2a31fab503": {
+ "~#shape": {
+ "~:y": -209.99999747726048,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#ee1313",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#ee1313",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 391.99999038908356,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -337.9999990801268,
+ "~:y": -209.99999747726048
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 53.99999130895676,
+ "~:y": -209.99999747726048
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 53.99999130895676,
+ "~:y": -124.00000837282306
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -337.9999990801268,
+ "~:y": -124.00000837282306
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2a31fab503",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": -337.9999990801268,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -337.9999990801268,
+ "~:y": -209.99999747726048,
+ "~:width": 391.99999038908356,
+ "~:height": 85.99998910443742,
+ "~:x1": -337.9999990801268,
+ "~:y1": -209.99999747726048,
+ "~:x2": 53.99999130895674,
+ "~:y2": -124.00000837282306
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 85.99998910443742,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c291d17afc1": {
+ "~#shape": {
+ "~:y": -248.00000118229832,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "ye7sp38a1",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1dmijf1j68k",
+ "~:font-size": "36",
+ "~:font-weight": "bold",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "bold",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:stops": [
+ {
+ "~:color": "#000000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#de0094",
+ "~:offset": 1,
+ "~:opacity": 1
+ }
+ ]
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "X"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "txhrpw0kcu",
+ "~:font-size": "36",
+ "~:font-weight": "bold",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "bold",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:linear",
+ "~:stops": [
+ {
+ "~:color": "#000000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#de0094",
+ "~:offset": 1,
+ "~:opacity": 1
+ }
+ ]
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 20.9999999224288,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 104.00000209794854,
+ "~:y": -248.00000118229832
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 125.00000202037734,
+ "~:y": -248.00000118229832
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 125.00000202037734,
+ "~:y": -204.99999631795663
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 104.00000209794854,
+ "~:y": -204.99999631795663
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u42dae815-ff2f-802c-8006-5c291d17afc1",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 104.00000209794854,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 104.00000209794854,
+ "~:y": -248.00000118229832,
+ "~:width": 20.9999999224288,
+ "~:height": 43.00000486434169,
+ "~:x1": 104.00000209794854,
+ "~:y1": -248.00000118229832,
+ "~:x2": 125.00000202037734,
+ "~:y2": -204.99999631795663
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 43.00000486434169,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2cce142b67": {
+ "~#shape": {
+ "~:y": 421.9999916708832,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "ye7sp38a1",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1fgj5tnxjp9",
+ "~:font-size": "36",
+ "~:font-weight": "200",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "200",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#043de9",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "right",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2csqgsg3et3",
+ "~:font-size": "0",
+ "~:font-weight": "200",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "200",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#043de9",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 496.00000888405407,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 96.00000128061538,
+ "~:y": 421.9999916708833
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 592.0000101646694,
+ "~:y": 421.9999916708833
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 592.0000101646694,
+ "~:y": 1027.0000358495622
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 96.00000128061538,
+ "~:y": 1027.0000358495622
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2cce142b67",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 96.0000012806156,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 96.0000012806156,
+ "~:y": 421.9999916708832,
+ "~:width": 496.00000888405407,
+ "~:height": 605.000044178679,
+ "~:x1": 96.0000012806156,
+ "~:y1": 421.9999916708832,
+ "~:x2": 592.0000101646697,
+ "~:y2": 1027.0000358495622
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 605.000044178679,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2c5bb11e66": {
+ "~#shape": {
+ "~:y": -274.99998830878695,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:index": 15,
+ "~:name": "Group",
+ "~:width": 392.0000069889898,
+ "~:type": "~:group",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -338.0000057874328,
+ "~:y": -274.99998830878695
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.000001201557,
+ "~:y": -274.99998830878695
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.000001201557,
+ "~:y": 459.9999901965925
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -338.0000057874328,
+ "~:y": 459.9999901965925
+ }
+ }
+ ],
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [],
+ "~:x": -338.0000057874328,
+ "~:proportion": 1,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -338.0000057874328,
+ "~:y": -274.99998830878695,
+ "~:width": 392.0000069889898,
+ "~:height": 734.9999785053794,
+ "~:x1": -338.0000057874328,
+ "~:y1": -274.99998830878695,
+ "~:x2": 54.000001201556984,
+ "~:y2": 459.9999901965925
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 734.9999785053794,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u42dae815-ff2f-802c-8006-5c2bdd0738be",
+ "~u42dae815-ff2f-802c-8006-5c2a10e98045",
+ "~u42dae815-ff2f-802c-8006-5c2a31fab503",
+ "~u42dae815-ff2f-802c-8006-5c2a34fa92a9",
+ "~u42dae815-ff2f-802c-8006-5c2a3583a629",
+ "~u42dae815-ff2f-802c-8006-5c2a35d7c1af",
+ "~u42dae815-ff2f-802c-8006-5c2a3612584c",
+ "~u42dae815-ff2f-802c-8006-5c2a363d3312",
+ "~u42dae815-ff2f-802c-8006-5c2a367557d3",
+ "~u42dae815-ff2f-802c-8006-5c2a36981f24",
+ "~u42dae815-ff2f-802c-8006-5c2c18ff7260"
+ ]
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2cb4c7a8e0": {
+ "~#shape": {
+ "~:y": 469.48201453143633,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 0.9829464308424565,
+ "~:b": 0.18389212624274043,
+ "~:c": -0.183892133489449,
+ "~:d": 0.9829464294867236,
+ "~:e": 2.2737367544323206e-13,
+ "~:f": 1.7053025658242404e-13
+ }
+ },
+ "~:rotation": 10.596548005627483,
+ "~:grow-type": "~:auto-height",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "center"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 382.99991925634913,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -322.18529116108846,
+ "~:y": 434.9999877731222
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.28311206403565,
+ "~:y": 505.4306554864536
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 38.46839316811677,
+ "~:y": 589.9640270284463
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -338.0000100570073,
+ "~:y": 519.5333593151149
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 0.9829464294867237,
+ "~:b": -0.18389212624274046,
+ "~:c": 0.18389213348944902,
+ "~:d": 0.9829464308424566,
+ "~:e": -2.5485531514364324e-13,
+ "~:f": -1.2580987842945934e-13
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2cb4c7a8e0",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1,
+ "~:stroke-width": 1
+ }
+ ],
+ "~:x": -333.3584086246604,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -333.3584086246604,
+ "~:y": 469.48201453143633,
+ "~:width": 382.99991925634913,
+ "~:height": 85.99998573869584,
+ "~:x1": -333.3584086246604,
+ "~:y1": 469.48201453143633,
+ "~:x2": 49.64151063168873,
+ "~:y2": 555.4820002701322
+ }
+ },
+ "~:fills": [],
+ "~:flip-x": null,
+ "~:height": 85.99998573869584,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2c18ff7260": {
+ "~#shape": {
+ "~:y": 373.9999914828512,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#d12a2a",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#d12a2a",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 392.0000303540334,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -338.0000141844768,
+ "~:y": 373.9999914828512
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.00001616955658,
+ "~:y": 373.9999914828512
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.00001616955658,
+ "~:y": 459.99998583729763
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -338.0000141844768,
+ "~:y": 459.99998583729763
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:blur": {
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2c1eaec261",
+ "~:type": "~:layer-blur",
+ "~:value": 4,
+ "~:hidden": false
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2c18ff7260",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": -338.0000141844768,
+ "~:shadow": [
+ {
+ "~:color": {
+ "~:color": "#d71111",
+ "~:opacity": 0.7333333333333333
+ },
+ "~:spread": 0,
+ "~:offset-y": 4,
+ "~:style": "~:drop-shadow",
+ "~:blur": 4,
+ "~:hidden": false,
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2bc1eec182",
+ "~:offset-x": 4
+ }
+ ],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -338.0000141844768,
+ "~:y": 373.9999914828512,
+ "~:width": 392.0000303540334,
+ "~:height": 85.99999435444641,
+ "~:x1": -338.0000141844768,
+ "~:y1": 373.9999914828512,
+ "~:x2": 54.0000161695566,
+ "~:y2": 459.99998583729763
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 85.99999435444641,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2a10e98045": {
+ "~#shape": {
+ "~:y": -274.99998724704835,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 391.9999854536687,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -337.9999943936534,
+ "~:y": -274.99998724704835
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 53.99999106001533,
+ "~:y": -274.99998724704835
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 53.99999106001533,
+ "~:y": -189.00001437956377
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -337.9999943936534,
+ "~:y": -189.00001437956377
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2a10e98045",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": -337.9999943936534,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -337.9999943936534,
+ "~:y": -274.99998724704835,
+ "~:width": 391.9999854536687,
+ "~:height": 85.99997286748459,
+ "~:x1": -337.9999943936534,
+ "~:y1": -274.99998724704835,
+ "~:x2": 53.999991060015304,
+ "~:y2": -189.00001437956377
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 85.99997286748459,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2a36981f24": {
+ "~#shape": {
+ "~:y": 241.99999506961544,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#d12a2a",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#d12a2a",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 392.00000698901925,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -337.99999064916483,
+ "~:y": 241.99999506961544
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.00001633985441,
+ "~:y": 241.99999506961544
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.00001633985441,
+ "~:y": 327.9999951461159
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -337.99999064916483,
+ "~:y": 327.9999951461159
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2a36981f24",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": -337.99999064916483,
+ "~:shadow": [
+ {
+ "~:color": {
+ "~:color": "#d71111",
+ "~:opacity": 0.7333333333333333
+ },
+ "~:spread": 0,
+ "~:offset-y": 4,
+ "~:style": "~:drop-shadow",
+ "~:blur": 4,
+ "~:hidden": false,
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2bc1eec182",
+ "~:offset-x": 4
+ }
+ ],
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -337.99999064916483,
+ "~:y": 241.99999506961544,
+ "~:width": 392.00000698901925,
+ "~:height": 86.00000007650047,
+ "~:x1": -337.99999064916483,
+ "~:y1": 241.99999506961544,
+ "~:x2": 54.000016339854426,
+ "~:y2": 327.9999951461159
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 86.00000007650047,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2a3583a629": {
+ "~#shape": {
+ "~:y": -80.99999716807616,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 391.9999945791926,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -338.0000005981459,
+ "~:y": -80.99999716807616
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 53.99999398104674,
+ "~:y": -80.99999716807616
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 53.99999398104674,
+ "~:y": 4.999996712514275
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -338.0000005981459,
+ "~:y": 4.999996712514275
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2a3583a629",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-alignment": "~:center",
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1,
+ "~:stroke-width": 1
+ }
+ ],
+ "~:x": -338.0000005981459,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -338.0000005981459,
+ "~:y": -80.99999716807616,
+ "~:width": 391.9999945791926,
+ "~:height": 85.99999388059044,
+ "~:x1": -338.0000005981459,
+ "~:y1": -80.99999716807616,
+ "~:x2": 53.999993981046714,
+ "~:y2": 4.999996712514275
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 85.99999388059044,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2a34fa92a9": {
+ "~#shape": {
+ "~:y": -145.00000383730716,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 392.0000020656457,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -337.99999263374957,
+ "~:y": -145.00000383730716
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.0000094318961,
+ "~:y": -145.00000383730716
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.0000094318961,
+ "~:y": -59.0000081418616
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -337.99999263374957,
+ "~:y": -59.0000081418616
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2a34fa92a9",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-alignment": "~:inner",
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1,
+ "~:stroke-width": 1
+ }
+ ],
+ "~:x": -337.99999263374957,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -337.99999263374957,
+ "~:y": -145.00000383730716,
+ "~:width": 392.0000020656457,
+ "~:height": 85.99999569544556,
+ "~:x1": -337.99999263374957,
+ "~:y1": -145.00000383730716,
+ "~:x2": 54.0000094318961,
+ "~:y2": -59.0000081418616
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 85.99999569544556,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2a35d7c1af": {
+ "~#shape": {
+ "~:y": -15.999998980559468,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#90ff00",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 392.00000206564573,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -337.9999943502804,
+ "~:y": -15.999998980559468
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.000007715365314,
+ "~:y": -15.999998980559468
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.000007715365314,
+ "~:y": 69.99999839136431
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -337.9999943502804,
+ "~:y": 69.99999839136431
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2a35d7c1af",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-style": "~:solid",
+ "~:stroke-color": "#000000",
+ "~:stroke-opacity": 1,
+ "~:stroke-width": 1
+ }
+ ],
+ "~:x": -337.9999943502804,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -337.9999943502804,
+ "~:y": -15.999998980559468,
+ "~:width": 392.00000206564573,
+ "~:height": 85.99999737192377,
+ "~:x1": -337.9999943502804,
+ "~:y1": -15.999998980559468,
+ "~:x2": 54.000007715365314,
+ "~:y2": 69.9999983913643
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 85.99999737192377,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c293714cf2d": {
+ "~#shape": {
+ "~:y": -195.99999612348097,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "ye7sp38a1",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1fgj5tnxjp9",
+ "~:font-size": "36",
+ "~:font-weight": "bold",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "bold",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0.5,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:radial",
+ "~:stops": [
+ {
+ "~:color": "#000000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#de0094",
+ "~:offset": 1,
+ "~:opacity": 1
+ }
+ ]
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2csqgsg3et3",
+ "~:font-size": "0",
+ "~:font-weight": "bold",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "bold",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color-gradient": {
+ "~:start-x": 0.5,
+ "~:start-y": 0.5,
+ "~:end-x": 0.5,
+ "~:end-y": 1,
+ "~:width": 1,
+ "~:type": "~:radial",
+ "~:stops": [
+ {
+ "~:color": "#000000",
+ "~:offset": 0,
+ "~:opacity": 1
+ },
+ {
+ "~:color": "#de0094",
+ "~:offset": 1,
+ "~:opacity": 1
+ }
+ ]
+ }
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 611.0000374529255,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 74.99999577985554,
+ "~:y": -195.99999612348097
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 686.0000332327811,
+ "~:y": -195.99999612348097
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 686.0000332327811,
+ "~:y": 412.99999206289226
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 74.99999577985554,
+ "~:y": 412.99999206289226
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u42dae815-ff2f-802c-8006-5c293714cf2d",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 74.99999577985552,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 74.99999577985552,
+ "~:y": -195.99999612348097,
+ "~:width": 611.0000374529255,
+ "~:height": 608.9999881863732,
+ "~:x1": 74.99999577985552,
+ "~:y1": -195.99999612348097,
+ "~:x2": 686.0000332327811,
+ "~:y2": 412.99999206289226
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 608.9999881863732,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2cf831ac6b": {
+ "~#shape": {
+ "~:y": 421.9999916098435,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "ye7sp38a1",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1fgj5tnxjp9",
+ "~:font-size": "36",
+ "~:font-weight": "200",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "200",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#a503ea",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "center",
+ "~:font-id": "sourcesanspro",
+ "~:key": "2csqgsg3et3",
+ "~:font-size": "0",
+ "~:font-weight": "200",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "200",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#a503ea",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 496.0000680117778,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 730.999957544444,
+ "~:y": 421.9999916098435
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1227.0000255562215,
+ "~:y": 421.9999916098435
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1227.0000255562215,
+ "~:y": 1066.999982681063
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 730.999957544444,
+ "~:y": 1066.999982681063
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2cf831ac6b",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#65e97c",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": 730.9999575444442,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 730.9999575444442,
+ "~:y": 421.9999916098435,
+ "~:width": 496.0000680117778,
+ "~:height": 644.9999910712196,
+ "~:x1": 730.9999575444442,
+ "~:y1": 421.9999916098435,
+ "~:x2": 1227.000025556222,
+ "~:y2": 1066.999982681063
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 644.9999910712196,
+ "~:flip-y": null
+ }
+ },
+ "~u42dae815-ff2f-802c-8006-5c2a3612584c": {
+ "~#shape": {
+ "~:y": 46.99999608604875,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "1uoasdewtqd",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "jy4c8k2f8v",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#d8f005",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "abracadabra"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1gmnpff0iyj",
+ "~:font-size": "72",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#d8f005",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text",
+ "~:width": 392.00001959825676,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": -338.0000090387647,
+ "~:y": 46.99999608604875
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.00001055949209,
+ "~:y": 46.99999608604875
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 54.00001055949209,
+ "~:y": 133.0000009595795
+ }
+ },
+ {
+ "~#point": {
+ "~:x": -338.0000090387647,
+ "~:y": 133.0000009595795
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u42dae815-ff2f-802c-8006-5c2a3612584c",
+ "~:parent-id": "~u42dae815-ff2f-802c-8006-5c2c5bb11e66",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:strokes": [
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 3,
+ "~:stroke-color": "#009bf5",
+ "~:stroke-opacity": 1
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 2,
+ "~:stroke-color": "#ea0e0e",
+ "~:stroke-opacity": 1
+ },
+ {
+ "~:stroke-style": "~:solid",
+ "~:stroke-alignment": "~:outer",
+ "~:stroke-width": 1,
+ "~:stroke-color": "#1a4ecc",
+ "~:stroke-opacity": 1
+ }
+ ],
+ "~:x": -338.0000090387647,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": -338.0000090387647,
+ "~:y": 46.99999608604875,
+ "~:width": 392.00001959825676,
+ "~:height": 86.00000487353074,
+ "~:x1": -338.0000090387647,
+ "~:y1": 46.99999608604875,
+ "~:x2": 54.00001055949207,
+ "~:y2": 133.0000009595795
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 86.00000487353074,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5c2559af493a",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u6bd7c17d-4f59-815e-8006-5c2559af4939",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-file-text.json b/frontend/playwright/data/render-wasm/get-file-text.json
new file mode 100644
index 0000000000..fec3ccbed1
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-file-text.json
@@ -0,0 +1,349 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u1091e979-bbec-8194-8005-f7aa420b5660",
+ "~: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": "simple-text",
+ "~:revn": 7,
+ "~:modified-at": "~m1749629891313",
+ "~:vern": 0,
+ "~:id": "~u3b0d758a-8c9d-8013-8006-52c8337e5c72",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u1091e979-bbec-8194-8005-f7aa420b8b07",
+ "~:created-at": "~m1749629823499",
+ "~:data": {
+ "~:pages": [
+ "~u3b0d758a-8c9d-8013-8006-52c8337e5c73"
+ ],
+ "~:pages-index": {
+ "~u3b0d758a-8c9d-8013-8006-52c8337e5c73": {
+ "~:objects": {
+ "~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
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~: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,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u7274a6af-66db-8009-8006-52c837bed25d"
+ ]
+ }
+ },
+ "~u7274a6af-66db-8009-8006-52c837bed25d": {
+ "~#shape": {
+ "~:y": 368.000005463652,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:auto-width",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "13hr3ftth2o",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "1qm8gi1rphc",
+ "~:font-size": "48",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "this is a text"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "r8gahivbg7",
+ "~:font-size": "48",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "this is a text",
+ "~:width": 237.0000390021974,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 414.9999714372273,
+ "~:y": 368.000005463652
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 652.0000104394247,
+ "~:y": 368.000005463652
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 652.0000104394247,
+ "~:y": 426.0000039162686
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 414.9999714372273,
+ "~:y": 426.0000039162686
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1.0,
+ "~:b": 0.0,
+ "~:c": 0.0,
+ "~:d": 1.0,
+ "~:e": 0.0,
+ "~:f": 0.0
+ }
+ },
+ "~:layout-item-v-sizing": "~:fix",
+ "~:id": "~u7274a6af-66db-8009-8006-52c837bed25d",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 414.9999714372274,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 414.9999714372274,
+ "~:y": 368.000005463652,
+ "~:width": 237.0000390021974,
+ "~:height": 57.99999845261664,
+ "~:x1": 414.9999714372274,
+ "~:y1": 368.000005463652,
+ "~:x2": 652.0000104394248,
+ "~:y2": 426.0000039162686
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 57.99999845261664,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~u3b0d758a-8c9d-8013-8006-52c8337e5c73",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~u3b0d758a-8c9d-8013-8006-52c8337e5c72",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-font-variants-custom-fonts.json b/frontend/playwright/data/render-wasm/get-font-variants-custom-fonts.json
new file mode 100644
index 0000000000..5df269f70d
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-font-variants-custom-fonts.json
@@ -0,0 +1,106 @@
+[
+ {
+ "~:font-style": "normal",
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~:font-id": "~u7d85a63e-18e7-809f-8006-5983057a9b7c",
+ "~:font-weight": 400,
+ "~:ttf-file-id": "~u69e76833-0816-49fa-8c7b-4b97c71c6f1a",
+ "~:modified-at": "~m1750081452108",
+ "~:otf-file-id": "~uf7ea405b-73be-40d7-8ce4-fbf734696997",
+ "~:id": "~u434b0541-fa2f-802f-8006-5983078ad50e",
+ "~:woff1-file-id": "~uaef0b0c5-56de-47c7-bd99-fb5e249ccef9",
+ "~:created-at": "~m1750081452108",
+ "~:font-family": "Nodesto Caps Condensed"
+ },
+ {
+ "~:font-style": "italic",
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~:font-id": "~u7d85a63e-18e7-809f-8006-5983057a9b7c",
+ "~:font-weight": 400,
+ "~:ttf-file-id": "~uc45955ee-8c16-47ce-a89e-1a2faadb4178",
+ "~:modified-at": "~m1750081452589",
+ "~:otf-file-id": "~ucc0a799d-34ed-4829-a0de-6d7efec8202c",
+ "~:id": "~u434b0541-fa2f-802f-8006-59830795b436",
+ "~:woff1-file-id": "~u9940a178-833e-4a4c-8bc3-64ee09472ea6",
+ "~:created-at": "~m1750081452589",
+ "~:font-family": "Nodesto Caps Condensed"
+ },
+ {
+ "~:font-style": "normal",
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~:font-id": "~u7d85a63e-18e7-809f-8006-5983057a9b7c",
+ "~:font-weight": 700,
+ "~:ttf-file-id": "~u5c59fbdc-46de-456f-8da6-0dcd916e95c1",
+ "~:modified-at": "~m1750081452631",
+ "~:otf-file-id": "~uadde63b1-c9f4-484a-bf1c-1121b77d751d",
+ "~:id": "~u434b0541-fa2f-802f-8006-598307a05c77",
+ "~:woff1-file-id": "~ucea20394-48af-41a0-8d0f-ada497a5ffe3",
+ "~:created-at": "~m1750081452631",
+ "~:font-family": "Nodesto Caps Condensed"
+ },
+ {
+ "~:font-style": "italic",
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~:font-id": "~u7d85a63e-18e7-809f-8006-5983057a9b7c",
+ "~:font-weight": 700,
+ "~:ttf-file-id": "~u5060a5c4-c7c3-44ea-aa0e-5797d6647ec0",
+ "~:modified-at": "~m1750081452674",
+ "~:otf-file-id": "~u1646d986-88d1-4904-910d-663da9e35eef",
+ "~:id": "~u434b0541-fa2f-802f-8006-598307a94233",
+ "~:woff1-file-id": "~ue7691712-70d1-49d9-a596-7b4ac495ff15",
+ "~:created-at": "~m1750081452674",
+ "~:font-family": "Nodesto Caps Condensed"
+ },
+ {
+ "~:font-style": "normal",
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~:font-id": "~u7d85a63e-18e7-809f-8006-59831351af03",
+ "~:font-weight": 700,
+ "~:ttf-file-id": "~u3ccf58ee-731c-402f-8b26-5b5049abbe7f",
+ "~:modified-at": "~m1750081469706",
+ "~:otf-file-id": "~u96293106-78ff-4683-b819-c1c4c1e44705",
+ "~:id": "~u434b0541-fa2f-802f-8006-5983185366a8",
+ "~:woff1-file-id": "~uaf7a782a-c96b-4926-a08a-c6a81a1726dc",
+ "~:created-at": "~m1750081469706",
+ "~:font-family": "Bookinsanity Remake"
+ },
+ {
+ "~:font-style": "normal",
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~:font-id": "~u7d85a63e-18e7-809f-8006-59831351af03",
+ "~:font-weight": 400,
+ "~:ttf-file-id": "~u78583622-dc5d-4780-990f-85afb4ad59e9",
+ "~:modified-at": "~m1750081469774",
+ "~:otf-file-id": "~ua5eb4175-91d0-4c61-a471-2d5d0648f8a6",
+ "~:id": "~u434b0541-fa2f-802f-8006-5983185cde2d",
+ "~:woff1-file-id": "~u0fcf15c2-59c5-4566-9dc9-9acaf280a518",
+ "~:created-at": "~m1750081469774",
+ "~:font-family": "Bookinsanity Remake"
+ },
+ {
+ "~:font-style": "italic",
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~:font-id": "~u7d85a63e-18e7-809f-8006-59831351af03",
+ "~:font-weight": 400,
+ "~:ttf-file-id": "~u517f4e27-b83f-4a6f-8753-8acaabc49ee9",
+ "~:modified-at": "~m1750081469812",
+ "~:otf-file-id": "~u12fe845f-afd2-453d-9ac6-6a899487ba9f",
+ "~:id": "~u434b0541-fa2f-802f-8006-598318671138",
+ "~:woff1-file-id": "~u53e0cb17-788e-4dae-aa7c-28f090f87968",
+ "~:created-at": "~m1750081469812",
+ "~:font-family": "Bookinsanity Remake"
+ },
+ {
+ "~:font-style": "normal",
+ "~:team-id": "~u04868522-3ebf-81e8-8006-306b0c9b5f59",
+ "~:font-id": "~u7d85a63e-18e7-809f-8006-59832d696634",
+ "~:font-weight": 500,
+ "~:ttf-file-id": "~u2d1ffeb6-e70b-4027-bbcc-910248ba45f8",
+ "~:modified-at": "~m1750081492609",
+ "~:otf-file-id": "~ubcc136ac-da48-4335-af5c-52abc4613490",
+ "~:id": "~u434b0541-fa2f-802f-8006-59832eb23bb4",
+ "~:woff1-file-id": "~u01338648-afcc-47aa-8aa7-600c9022bd9f",
+ "~:created-at": "~m1750081492609",
+ "~:font-family": "Mr Eaves SC Remake"
+ }
+]
\ No newline at end of file
diff --git a/frontend/playwright/data/render-wasm/get-multiple-texts-base.json b/frontend/playwright/data/render-wasm/get-multiple-texts-base.json
new file mode 100644
index 0000000000..353ddd4552
--- /dev/null
+++ b/frontend/playwright/data/render-wasm/get-multiple-texts-base.json
@@ -0,0 +1,1858 @@
+{
+ "~:features": {
+ "~#set": [
+ "fdata/path-data",
+ "plugins/runtime",
+ "design-tokens/v1",
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "render-wasm/v1",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:team-id": "~u6bd7c17d-4f59-815e-8006-5c1f6882469a",
+ "~: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": "text_base",
+ "~:revn": 32,
+ "~:modified-at": "~m1750756543053",
+ "~:vern": 0,
+ "~:id": "~uf8b42814-8653-81cf-8006-638aacdc3ffa",
+ "~: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",
+ "0002-clean-shape-interactions",
+ "0003-fix-root-shape",
+ "0003-convert-path-content",
+ "0004-clean-shadow-and-colors",
+ "0005-deprecate-image-type",
+ "0006-fix-old-texts-fills",
+ "0007-clear-invalid-strokes-and-fills-v2",
+ "0008-fix-library-colors-opacity",
+ "0009-add-partial-text-touched-flags"
+ ]
+ },
+ "~:version": 67,
+ "~:project-id": "~u6bd7c17d-4f59-815e-8006-5c1f68846e43",
+ "~:created-at": "~m1750754545523",
+ "~:data": {
+ "~:pages": [
+ "~uf8b42814-8653-81cf-8006-638aacdc3ffb"
+ ],
+ "~:pages-index": {
+ "~uf8b42814-8653-81cf-8006-638aacdc3ffb": {
+ "~:objects": {
+ "~u00000000-0000-0000-0000-000000000000": {
+ "~#shape": {
+ "~:y": 0,
+ "~:hide-fill-on-export": false,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:name": "Root Frame",
+ "~:width": 0.01,
+ "~:type": "~:frame",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0.01,
+ "~:y": 0.01
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 0,
+ "~:y": 0.01
+ }
+ }
+ ],
+ "~:r2": 0,
+ "~:proportion-lock": false,
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 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,
+ "~:r4": 0,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 0,
+ "~:y": 0,
+ "~:width": 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,
+ "~:height": 0.01,
+ "~:flip-y": null,
+ "~:shapes": [
+ "~u2d36f842-6329-801b-8006-638c5e8808cd",
+ "~u2d36f842-6329-801b-8006-638c578d864d",
+ "~u2d36f842-6329-801b-8006-638c4b1765cb",
+ "~u2d36f842-6329-801b-8006-638c35c32b39",
+ "~u2d36f842-6329-801b-8006-638aedf61630",
+ "~u2d36f842-6329-801b-8006-638ae53d4e66",
+ "~u2d36f842-6329-801b-8006-638ad618c609",
+ "~u2d36f842-6329-801b-8006-638aceecaf72",
+ "~u2d36f842-6329-801b-8006-638aca630922",
+ "~u2d36f842-6329-801b-8006-638ac5d4c21d",
+ "~u2d36f842-6329-801b-8006-638abd13d5b9",
+ "~u2d36f842-6329-801b-8006-638aaee1355f"
+ ]
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638aceecaf72": {
+ "~#shape": {
+ "~:y": 350,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 5"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 5",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 97.00000238418582,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 510.0000147819519,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 510.0000147819519,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 97.00000238418582,
+ "~:y": 474
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638aceecaf72",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 97.00000238418579,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 97.00000238418579,
+ "~:y": 350,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 97.00000238418579,
+ "~:y1": 350,
+ "~:x2": 510.0000147819519,
+ "~:y2": 474
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638aedf61630": {
+ "~#shape": {
+ "~:y": 350,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 8"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 8",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1410.9999947547913,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1824.0000071525574,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1824.0000071525574,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1410.9999947547913,
+ "~:y": 474
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638aedf61630",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 1410.9999947547913,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1410.9999947547913,
+ "~:y": 350,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 1410.9999947547913,
+ "~:y1": 350,
+ "~:x2": 1824.0000071525574,
+ "~:y2": 474
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638abd13d5b9": {
+ "~#shape": {
+ "~:y": 226,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 2"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 2",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 534.9999947547913,
+ "~:y": 226
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 948.0000071525574,
+ "~:y": 226
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 948.0000071525574,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 534.9999947547913,
+ "~:y": 350
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638abd13d5b9",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 534.9999947547913,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 534.9999947547913,
+ "~:y": 226,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 534.9999947547913,
+ "~:y1": 226,
+ "~:x2": 948.0000071525574,
+ "~:y2": 350
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638c35c32b39": {
+ "~#shape": {
+ "~:y": 474,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 9"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 9",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 97.00000238418582,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 510.0000147819519,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 510.0000147819519,
+ "~:y": 598
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 97.00000238418582,
+ "~:y": 598
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638c35c32b39",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 97.00000238418579,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 97.00000238418579,
+ "~:y": 474,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 97.00000238418579,
+ "~:y1": 474,
+ "~:x2": 510.0000147819519,
+ "~:y2": 598
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638aaee1355f": {
+ "~#shape": {
+ "~:y": 226,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 1"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 1",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 97.00000238418582,
+ "~:y": 226
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 510.0000147819519,
+ "~:y": 226
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 510.0000147819519,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 97.00000238418582,
+ "~:y": 350
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638aaee1355f",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 97.00000238418579,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 97.00000238418579,
+ "~:y": 226,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 97.00000238418579,
+ "~:y1": 226,
+ "~:x2": 510.0000147819519,
+ "~:y2": 350
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638ac5d4c21d": {
+ "~#shape": {
+ "~:y": 226,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 3"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 3",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 972.9999947547913,
+ "~:y": 226
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1386.0000071525574,
+ "~:y": 226
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1386.0000071525574,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 972.9999947547913,
+ "~:y": 350
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638ac5d4c21d",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 972.9999947547913,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 972.9999947547913,
+ "~:y": 226,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 972.9999947547913,
+ "~:y1": 226,
+ "~:x2": 1386.0000071525574,
+ "~:y2": 350
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638aca630922": {
+ "~#shape": {
+ "~:y": 226,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 4"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 4",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1410.9999947547913,
+ "~:y": 226
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1824.0000071525574,
+ "~:y": 226
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1824.0000071525574,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1410.9999947547913,
+ "~:y": 350
+ }
+ }
+ ],
+ "~:layout-item-h-sizing": "~:fix",
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638aca630922",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 1410.9999947547913,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1410.9999947547913,
+ "~:y": 226,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 1410.9999947547913,
+ "~:y1": 226,
+ "~:x2": 1824.0000071525574,
+ "~:y2": 350
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638ae53d4e66": {
+ "~#shape": {
+ "~:y": 350,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 7"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 7",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 972.9999947547913,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1386.0000071525574,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1386.0000071525574,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 972.9999947547913,
+ "~:y": 474
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638ae53d4e66",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 972.9999947547913,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 972.9999947547913,
+ "~:y": 350,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 972.9999947547913,
+ "~:y1": 350,
+ "~:x2": 1386.0000071525574,
+ "~:y2": 474
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638c578d864d": {
+ "~#shape": {
+ "~:y": 474,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 11"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 11",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 972.9999947547913,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1386.0000071525574,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1386.0000071525574,
+ "~:y": 598
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 972.9999947547913,
+ "~:y": 598
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638c578d864d",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 972.9999947547913,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 972.9999947547913,
+ "~:y": 474,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 972.9999947547913,
+ "~:y1": 474,
+ "~:x2": 1386.0000071525574,
+ "~:y2": 598
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638c5e8808cd": {
+ "~#shape": {
+ "~:y": 474,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 12"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 12",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 1410.9999947547913,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1824.0000071525574,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1824.0000071525574,
+ "~:y": 598
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 1410.9999947547913,
+ "~:y": 598
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638c5e8808cd",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 1410.9999947547913,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 1410.9999947547913,
+ "~:y": 474,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 1410.9999947547913,
+ "~:y1": 474,
+ "~:x2": 1824.0000071525574,
+ "~:y2": 598
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638ad618c609": {
+ "~#shape": {
+ "~:y": 350,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 6"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 6",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 534.9999947547913,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 948.0000071525574,
+ "~:y": 350
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 948.0000071525574,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 534.9999947547913,
+ "~:y": 474
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638ad618c609",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 534.9999947547913,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 534.9999947547913,
+ "~:y": 350,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 534.9999947547913,
+ "~:y1": 350,
+ "~:x2": 948.0000071525574,
+ "~:y2": 474
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ },
+ "~u2d36f842-6329-801b-8006-638c4b1765cb": {
+ "~#shape": {
+ "~:y": 474,
+ "~:transform": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:rotation": 0,
+ "~:grow-type": "~:fixed",
+ "~:content": {
+ "~:type": "root",
+ "~:key": "18iiwe28c8",
+ "~:children": [
+ {
+ "~:type": "paragraph-set",
+ "~:children": [
+ {
+ "~:line-height": "1.2",
+ "~:font-style": "normal",
+ "~:children": [
+ {
+ "~:line-height": "",
+ "~:font-style": "normal",
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:font-id": "sourcesanspro",
+ "~:key": "189wz0cyaiq",
+ "~:font-size": "36",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro",
+ "~:text": "text 10"
+ }
+ ],
+ "~:typography-ref-id": null,
+ "~:text-transform": "none",
+ "~:text-align": "left",
+ "~:font-id": "sourcesanspro",
+ "~:key": "pu0nozn04k",
+ "~:font-size": "0",
+ "~:font-weight": "400",
+ "~:typography-ref-file": null,
+ "~:text-direction": "ltr",
+ "~:type": "paragraph",
+ "~:font-variant-id": "regular",
+ "~:text-decoration": "none",
+ "~:letter-spacing": "0",
+ "~:fills": [
+ {
+ "~:fill-color": "#000000",
+ "~:fill-opacity": 1
+ }
+ ],
+ "~:font-family": "sourcesanspro"
+ }
+ ]
+ }
+ ],
+ "~:vertical-align": "top"
+ },
+ "~:hide-in-viewer": false,
+ "~:name": "Text 10",
+ "~:width": 413.0000123977661,
+ "~:type": "~:text",
+ "~:points": [
+ {
+ "~#point": {
+ "~:x": 534.9999947547913,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 948.0000071525574,
+ "~:y": 474
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 948.0000071525574,
+ "~:y": 598
+ }
+ },
+ {
+ "~#point": {
+ "~:x": 534.9999947547913,
+ "~:y": 598
+ }
+ }
+ ],
+ "~:transform-inverse": {
+ "~#matrix": {
+ "~:a": 1,
+ "~:b": 0,
+ "~:c": 0,
+ "~:d": 1,
+ "~:e": 0,
+ "~:f": 0
+ }
+ },
+ "~:id": "~u2d36f842-6329-801b-8006-638c4b1765cb",
+ "~:parent-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:frame-id": "~u00000000-0000-0000-0000-000000000000",
+ "~:x": 534.9999947547913,
+ "~:selrect": {
+ "~#rect": {
+ "~:x": 534.9999947547913,
+ "~:y": 474,
+ "~:width": 413.0000123977661,
+ "~:height": 124,
+ "~:x1": 534.9999947547913,
+ "~:y1": 474,
+ "~:x2": 948.0000071525574,
+ "~:y2": 598
+ }
+ },
+ "~:flip-x": null,
+ "~:height": 124,
+ "~:flip-y": null
+ }
+ }
+ },
+ "~:id": "~uf8b42814-8653-81cf-8006-638aacdc3ffb",
+ "~:name": "Page 1"
+ }
+ },
+ "~:id": "~uf8b42814-8653-81cf-8006-638aacdc3ffa",
+ "~:options": {
+ "~:components-v2": true,
+ "~:base-font-size": "16px"
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/playwright/data/subscription/get-owned-teams.json b/frontend/playwright/data/subscription/get-owned-teams.json
new file mode 100644
index 0000000000..7d925f91fc
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-owned-teams.json
@@ -0,0 +1,14 @@
+[
+ {
+ "~:id": "~uf88e52d7-2b77-81fd-8006-23413fafe56c",
+ "~:name": "The Alpaca team",
+ "~:total-editors": 3,
+ "~:total-members": 3
+ },
+ {
+ "~:id": "~u81be1d05-a07b-81d5-8006-3e728bea76fb",
+ "~:name": "The Quokka team",
+ "~:total-editors": 1,
+ "~:total-members": 1
+ }
+]
\ No newline at end of file
diff --git a/frontend/playwright/data/subscription/get-profile-enterprise-canceled-subscription.json b/frontend/playwright/data/subscription/get-profile-enterprise-canceled-subscription.json
new file mode 100644
index 0000000000..4273ee2012
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-profile-enterprise-canceled-subscription.json
@@ -0,0 +1,25 @@
+{
+ "~: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": {
+ "~:subscription": {
+ "~:quantity": 2,
+ "~:status": "canceled",
+ "~:type": "enterprise",
+ "~start-date": "~m1746444667"
+ },
+ "~:v2-info-shown": true,
+ "~:viewed-tutorial?": false,
+ "~:viewed-walkthrough?": false
+ }
+}
diff --git a/frontend/playwright/data/subscription/get-profile-enterprise-subscription.json b/frontend/playwright/data/subscription/get-profile-enterprise-subscription.json
new file mode 100644
index 0000000000..75fc221631
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-profile-enterprise-subscription.json
@@ -0,0 +1,25 @@
+{
+ "~: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": {
+ "~:subscription": {
+ "~:quantity": 2,
+ "~:status": "trialing",
+ "~:type": "enterprise",
+ "~start-date": "~m1746444667"
+ },
+ "~:v2-info-shown": true,
+ "~:viewed-tutorial?": false,
+ "~:viewed-walkthrough?": false
+ }
+}
diff --git a/frontend/playwright/data/subscription/get-profile-unlimited-subscription.json b/frontend/playwright/data/subscription/get-profile-unlimited-subscription.json
new file mode 100644
index 0000000000..5d56af4ac9
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-profile-unlimited-subscription.json
@@ -0,0 +1,25 @@
+{
+ "~: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": {
+ "~:subscription": {
+ "~:quantity": 2,
+ "~:status": "trialing",
+ "~:type": "unlimited",
+ "~start-date": "~m1746444667"
+ },
+ "~:v2-info-shown": true,
+ "~:viewed-tutorial?": false,
+ "~:viewed-walkthrough?": false
+ }
+}
diff --git a/frontend/playwright/data/subscription/get-profile-unlimited-unpaid-subscription.json b/frontend/playwright/data/subscription/get-profile-unlimited-unpaid-subscription.json
new file mode 100644
index 0000000000..34178320d9
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-profile-unlimited-unpaid-subscription.json
@@ -0,0 +1,25 @@
+{
+ "~: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": {
+ "~:subscription": {
+ "~:quantity": 2,
+ "~:status": "unpaid",
+ "~:type": "unlimited",
+ "~start-date": "~m1746444667"
+ },
+ "~:v2-info-shown": true,
+ "~:viewed-tutorial?": false,
+ "~:viewed-walkthrough?": false
+ }
+}
diff --git a/frontend/playwright/data/subscription/get-team-info-subscription.json b/frontend/playwright/data/subscription/get-team-info-subscription.json
new file mode 100644
index 0000000000..c3ea011ec0
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-team-info-subscription.json
@@ -0,0 +1,4 @@
+{
+ "~:id": "~uc7ce0794-0992-8105-8004-38e630f7920a",
+ "~:is-default": false
+}
diff --git a/frontend/playwright/data/subscription/get-team-members-more-than-8.json b/frontend/playwright/data/subscription/get-team-members-more-than-8.json
new file mode 100644
index 0000000000..5679420fb1
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-team-members-more-than-8.json
@@ -0,0 +1,128 @@
+[
+ {
+ "~:is-admin": true,
+ "~:email": "bar@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Han Solo",
+ "~:fullname": "Han Solo",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u1e162163-87b7-805b-8005-5fd05514b6d3",
+ "~:profile-id": "~u1e162163-87b7-805b-8005-5fd05514b6d3",
+ "~:created-at": "~m1733324626956"
+ },
+ {
+ "~:is-admin": true,
+ "~:email": "foo@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Princesa Leia",
+ "~:fullname": "Princesa Leia",
+ "~:is-owner": true,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
+ "~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b",
+ "~:created-at": "~m1713533116365"
+ },
+ {
+ "~:is-admin": false,
+ "~:email": "luke@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Luke Skywalker",
+ "~:fullname": "Luke Skywalker",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u3a1b2c3d-1234-5678-8001-abcdefabcdef",
+ "~:profile-id": "~u3a1b2c3d-1234-5678-8001-abcdefabcdef",
+ "~:created-at": "~m1713533116365"
+ },
+ {
+ "~:is-admin": false,
+ "~:email": "chewie@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Chewbacca",
+ "~:fullname": "Chewbacca",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u4b2c3d4e-2345-6789-8002-bcdefabcdefa",
+ "~:profile-id": "~u4b2c3d4e-2345-6789-8002-bcdefabcdefa",
+ "~:created-at": "~m1713533116365"
+ },
+ {
+ "~:is-admin": false,
+ "~:email": "lando@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Lando Calrissian",
+ "~:fullname": "Lando Calrissian",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u5c3d4e5f-3456-7890-8003-cdefabcdefab",
+ "~:profile-id": "~u5c3d4e5f-3456-7890-8003-cdefabcdefab",
+ "~:created-at": "~m1713533116365"
+ },
+ {
+ "~:is-admin": false,
+ "~:email": "r2d2@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "R2-D2",
+ "~:fullname": "R2-D2",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u6d4e5f6a-4567-8901-8004-defabcdefabc",
+ "~:profile-id": "~u6d4e5f6a-4567-8901-8004-defabcdefabc",
+ "~:created-at": "~m1713533116365"
+ },
+ {
+ "~:is-admin": false,
+ "~:email": "c3po@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "C-3PO",
+ "~:fullname": "C-3PO",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u7e5f6a7b-5678-9012-8005-efabcdefabcd",
+ "~:profile-id": "~u7e5f6a7b-5678-9012-8005-efabcdefabcd",
+ "~:created-at": "~m1713533116365"
+ },
+ {
+ "~:is-admin": false,
+ "~:email": "ben@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Ben Kenobi",
+ "~:fullname": "Ben Kenobi",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u8f6a7b8c-6789-0123-8006-fabcdefabcde",
+ "~:profile-id": "~u8f6a7b8c-6789-0123-8006-fabcdefabcde",
+ "~:created-at": "~m1713533116365"
+ },
+ {
+ "~:is-admin": false,
+ "~:email": "yoda@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Yoda",
+ "~:fullname": "Yoda",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u9a7b8c9d-7890-1234-8007-abcdefabcdef",
+ "~:profile-id": "~u9a7b8c9d-7890-1234-8007-abcdefabcdef",
+ "~:created-at": "~m1713533116365"
+ }
+]
diff --git a/frontend/playwright/data/subscription/get-team-members-subscription-member.json b/frontend/playwright/data/subscription/get-team-members-subscription-member.json
new file mode 100644
index 0000000000..6296730aa5
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-team-members-subscription-member.json
@@ -0,0 +1,44 @@
+[
+ {
+ "~:is-admin": true,
+ "~:email": "bar@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Han Solo",
+ "~:fullname": "Han Solo",
+ "~:is-owner": true,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u1e162163-87b7-805b-8005-5fd05514b6d3",
+ "~:profile-id": "~u1e162163-87b7-805b-8005-5fd05514b6d3",
+ "~:created-at": "~m1733324626956"
+ },
+ {
+ "~:is-admin": true,
+ "~:email": "foo@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Princesa Leia",
+ "~:fullname": "Princesa Leia",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
+ "~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b",
+ "~:created-at": "~m1713533116365"
+ },
+ {
+ "~:is-admin": false,
+ "~:email": "luke@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Luke Skywalker",
+ "~:fullname": "Luke Skywalker",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u123456789-0000-0000-0000-abcdefabcdef",
+ "~:profile-id": "~u123456789-0000-0000-0000-abcdefabcdef",
+ "~:created-at": "~m1713533116365"
+ }
+]
diff --git a/frontend/playwright/data/subscription/get-team-members-subscription-owner.json b/frontend/playwright/data/subscription/get-team-members-subscription-owner.json
new file mode 100644
index 0000000000..7e47c4f6aa
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-team-members-subscription-owner.json
@@ -0,0 +1,44 @@
+[
+ {
+ "~:is-admin": true,
+ "~:email": "bar@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Han Solo",
+ "~:fullname": "Han Solo",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u1e162163-87b7-805b-8005-5fd05514b6d3",
+ "~:profile-id": "~u1e162163-87b7-805b-8005-5fd05514b6d3",
+ "~:created-at": "~m1733324626956"
+ },
+ {
+ "~:is-admin": true,
+ "~:email": "foo@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Princesa Leia",
+ "~:fullname": "Princesa Leia",
+ "~:is-owner": true,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
+ "~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b",
+ "~:created-at": "~m1713533116365"
+ },
+ {
+ "~:is-admin": false,
+ "~:email": "luke@example.com",
+ "~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:name": "Luke Skywalker",
+ "~:fullname": "Luke Skywalker",
+ "~:is-owner": false,
+ "~:modified-at": "~m1713533116365",
+ "~:can-edit": true,
+ "~:is-active": true,
+ "~:id": "~u123456789-0000-0000-0000-abcdefabcdef",
+ "~:profile-id": "~u123456789-0000-0000-0000-abcdefabcdef",
+ "~:created-at": "~m1713533116365"
+ }
+]
diff --git a/frontend/playwright/data/subscription/get-teams-enterprise-one-team.json b/frontend/playwright/data/subscription/get-teams-enterprise-one-team.json
new file mode 100644
index 0000000000..e920218115
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-teams-enterprise-one-team.json
@@ -0,0 +1,28 @@
+[{
+ "~:features": {
+ "~#set": [
+ "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
+ },
+ "~:subscription": {
+ "~:type": "enterprise",
+ "~:status": "trialing",
+ "~:seats": null
+ },
+ "~:name": "Default",
+ "~:modified-at": "~m1713533116375",
+ "~:id": "~uc7ce0794-0992-8105-8004-38e630f7920a",
+ "~:created-at": "~m1713533116375",
+ "~:is-default": true
+}]
diff --git a/frontend/playwright/data/subscription/get-teams-professional-subscription-owner.json b/frontend/playwright/data/subscription/get-teams-professional-subscription-owner.json
new file mode 100644
index 0000000000..ff228ab2f3
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-teams-professional-subscription-owner.json
@@ -0,0 +1,53 @@
+[
+ {
+ "~:features": {
+ "~#set": [
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:permissions": {
+ "~:type": "~:owner",
+ "~:is-owner": true,
+ "~:is-admin": true,
+ "~:can-edit": true
+ },
+ "~:name": "Default",
+ "~:modified-at": "~m1713533116375",
+ "~:id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
+ "~:created-at": "~m1713533116375",
+ "~:is-default": true
+ },
+ {
+ "~:features": {
+ "~#set": [
+ "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
+ },
+ "~:subscription": {
+ "~:type": "professional",
+ "~:status": "active",
+ "~:seats": null
+ },
+ "~:name": "Second team",
+ "~:modified-at": "~m1701164272671",
+ "~:id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:created-at": "~m1701164272671",
+ "~:is-default": false
+ }
+]
diff --git a/frontend/playwright/data/subscription/get-teams-unlimited-one-team.json b/frontend/playwright/data/subscription/get-teams-unlimited-one-team.json
new file mode 100644
index 0000000000..a43392b9ee
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-teams-unlimited-one-team.json
@@ -0,0 +1,28 @@
+[{
+ "~:features": {
+ "~#set": [
+ "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
+ },
+ "~:subscription": {
+ "~:type": "unlimited",
+ "~:status": "trialing",
+ "~:seats": 2
+ },
+ "~:name": "Default",
+ "~:modified-at": "~m1713533116375",
+ "~:id": "~uc7ce0794-0992-8105-8004-38e630f7920a",
+ "~:created-at": "~m1713533116375",
+ "~:is-default": true
+}]
diff --git a/frontend/playwright/data/subscription/get-teams-unlimited-subscription-member.json b/frontend/playwright/data/subscription/get-teams-unlimited-subscription-member.json
new file mode 100644
index 0000000000..59dc7071b8
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-teams-unlimited-subscription-member.json
@@ -0,0 +1,53 @@
+[
+ {
+ "~:features": {
+ "~#set": [
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:permissions": {
+ "~:type": "~:owner",
+ "~:is-owner": true,
+ "~:is-admin": true,
+ "~:can-edit": true
+ },
+ "~:name": "Default",
+ "~:modified-at": "~m1713533116375",
+ "~:id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
+ "~:created-at": "~m1713533116375",
+ "~:is-default": true
+ },
+ {
+ "~:features": {
+ "~#set": [
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:permissions": {
+ "~:type": "~:membership",
+ "~:is-owner": false,
+ "~:is-admin": true,
+ "~:can-edit": true
+ },
+ "~:subscription": {
+ "~:type": "unlimited",
+ "~:status": "trialing",
+ "~:seats": 2
+ },
+ "~:name": "Second team",
+ "~:modified-at": "~m1701164272671",
+ "~:id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:created-at": "~m1701164272671",
+ "~:is-default": false
+ }
+]
diff --git a/frontend/playwright/data/subscription/get-teams-unlimited-subscription-owner.json b/frontend/playwright/data/subscription/get-teams-unlimited-subscription-owner.json
new file mode 100644
index 0000000000..b211cd6ddc
--- /dev/null
+++ b/frontend/playwright/data/subscription/get-teams-unlimited-subscription-owner.json
@@ -0,0 +1,53 @@
+[
+ {
+ "~:features": {
+ "~#set": [
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:permissions": {
+ "~:type": "~:owner",
+ "~:is-owner": true,
+ "~:is-admin": true,
+ "~:can-edit": true
+ },
+ "~:name": "Default",
+ "~:modified-at": "~m1713533116375",
+ "~:id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
+ "~:created-at": "~m1713533116375",
+ "~:is-default": true
+ },
+ {
+ "~:features": {
+ "~#set": [
+ "layout/grid",
+ "styles/v2",
+ "fdata/pointer-map",
+ "fdata/objects-map",
+ "components/v2",
+ "fdata/shape-data-type"
+ ]
+ },
+ "~:permissions": {
+ "~:type": "~:owner",
+ "~:is-owner": true,
+ "~:is-admin": true,
+ "~:can-edit": true
+ },
+ "~:subscription": {
+ "~:type": "unlimited",
+ "~:status": "trialing",
+ "~:seats": 2
+ },
+ "~:name": "Second team",
+ "~:modified-at": "~m1701164272671",
+ "~:id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
+ "~:created-at": "~m1701164272671",
+ "~:is-default": false
+ }
+]
diff --git a/frontend/playwright/ui/pages/BasePage.js b/frontend/playwright/ui/pages/BasePage.js
index 155d7150b6..e4cea55f8b 100644
--- a/frontend/playwright/ui/pages/BasePage.js
+++ b/frontend/playwright/ui/pages/BasePage.js
@@ -23,6 +23,65 @@ export class BasePage {
);
}
+ static async mockFileMediaAsset(page, assetId, assetFilename, options) {
+ const ids = Array.isArray(assetId) ? assetId : [assetId];
+
+ for (const id of ids) {
+ const url = `**/assets/by-file-media-id/${id}`;
+
+ await page.route(url, (route) =>
+ route.fulfill({
+ path: `playwright/data/${assetFilename}`,
+ status: 200,
+ ...options,
+ }),
+ );
+ }
+ }
+
+ static async mockAsset(page, assetId, assetFilename, options) {
+ const ids = Array.isArray(assetId) ? assetId : [assetId];
+
+ for (const id of ids) {
+ const url = `**/assets/by-id/${id}`;
+
+ await page.route(url, (route) =>
+ route.fulfill({
+ path: `playwright/data/${assetFilename}`,
+ status: 200,
+ ...options,
+ }),
+ );
+ }
+ }
+
+ static async mockFileMediaAsset(page, assetId, assetFilename, options) {
+ const ids = Array.isArray(assetId) ? assetId : [assetId];
+
+ for (const id of ids) {
+ const url = `**/assets/by-file-media-id/${id}`;
+
+ await page.route(url, (route) =>
+ route.fulfill({
+ path: `playwright/data/${assetFilename}`,
+ status: 200,
+ ...options,
+ }),
+ );
+ }
+ }
+
+ static async mockConfigFlags(page, flags) {
+ const url = "**/js/config.js?ts=*";
+ return await page.route(url, (route) =>
+ route.fulfill({
+ status: 200,
+ contentType: "application/javascript",
+ body: `var penpotFlags = "${flags.join(" ")}";`,
+ }),
+ );
+ }
+
#page = null;
constructor(page) {
@@ -38,15 +97,21 @@ export class BasePage {
}
async mockConfigFlags(flags) {
- const url = "**/js/config.js?ts=*";
- return await this.page.route(url, (route) =>
- route.fulfill({
- status: 200,
- contentType: "application/javascript",
- body: `var penpotFlags = "${flags.join(" ")}";`,
- }),
+ return BasePage.mockConfigFlags(this.page, flags);
+ }
+
+ async mockFileMediaAsset(assetId, assetFilename, options) {
+ return BasePage.mockFileMediaAsset(
+ this.page,
+ assetId,
+ assetFilename,
+ options,
);
}
+
+ async mockAsset(assetId, assetFilename, options) {
+ return BasePage.mockAsset(this.page, assetId, assetFilename, options);
+ }
}
export default BasePage;
diff --git a/frontend/playwright/ui/pages/RegisterPage.js b/frontend/playwright/ui/pages/RegisterPage.js
new file mode 100644
index 0000000000..945a3d6c42
--- /dev/null
+++ b/frontend/playwright/ui/pages/RegisterPage.js
@@ -0,0 +1,35 @@
+import { BasePage } from "./BasePage";
+
+export class RegisterPage extends BasePage {
+ constructor(page) {
+ super(page);
+ this.registerButton = page.getByRole("button", { name: "Create an account" });
+ this.password = page.getByLabel("Password");
+ this.email = page.getByLabel("Work email");
+ this.fullName = page.getByLabel("Full name");
+ }
+
+ async fillRegisterFormInputs(name, email, password) {
+ await this.fullName.fill(name);
+ await this.email.fill(email);
+ await this.password.fill(password);
+ }
+
+ async clickRegisterButton() {
+ await this.registerButton.click();
+ }
+
+ async setupMismatchedEmailError() {
+ await this.mockRPC(
+ "prepare-register-profile",
+ "register/prepare-register-profile-email-mismatch.json",
+ { status: 400 },
+ );
+ }
+
+ static async initWithLoggedOutUser(page) {
+ await this.mockRPC(page, "get-profile", "get-profile-anonymous.json");
+ }
+}
+
+export default RegisterPage;
diff --git a/frontend/playwright/ui/pages/SubscriptionProfilePage.js b/frontend/playwright/ui/pages/SubscriptionProfilePage.js
new file mode 100644
index 0000000000..304856fdb4
--- /dev/null
+++ b/frontend/playwright/ui/pages/SubscriptionProfilePage.js
@@ -0,0 +1,30 @@
+import { expect } from "@playwright/test";
+import { DashboardPage } from "./DashboardPage";
+
+export class SubscriptionProfilePage extends DashboardPage {
+ static async init(page) {
+ await DashboardPage.initWebSockets(page);
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-owned-teams",
+ "subscription/get-owned-teams.json",
+ );
+ }
+
+ constructor(page) {
+ super(page);
+
+ this.mainHeading = page.getByRole("heading", {
+ name: "Subscription",
+ level: 2,
+ });
+ }
+
+ async goToSubscriptions() {
+ await this.page.goto(`#/settings/subscriptions`);
+ await expect(this.mainHeading).toBeVisible();
+ }
+}
+
+export default SubscriptionProfilePage;
diff --git a/frontend/playwright/ui/pages/WasmWorkspacePage.js b/frontend/playwright/ui/pages/WasmWorkspacePage.js
new file mode 100644
index 0000000000..8d3c3aec62
--- /dev/null
+++ b/frontend/playwright/ui/pages/WasmWorkspacePage.js
@@ -0,0 +1,62 @@
+import { expect } from "@playwright/test";
+import { WorkspacePage } from "./WorkspacePage";
+
+export class WasmWorkspacePage extends WorkspacePage {
+ static async init(page) {
+ await super.init(page);
+ await WorkspacePage.mockConfigFlags(page, [
+ "enable-feature-render-wasm",
+ "enable-render-wasm-dpr",
+ ]);
+
+ await page.addInitScript(() => {
+ document.addEventListener("wasm:set-objects-finished", () => {
+ window.wasmSetObjectsFinished = true;
+ });
+ });
+ }
+
+ constructor(page) {
+ super(page);
+ this.canvas = page.getByTestId("canvas-wasm-shapes");
+ }
+
+ async waitForFirstRender(config = {}) {
+ const options = { hideUI: true, ...config };
+
+ await expect(this.pageName).toHaveText("Page 1");
+ if (options.hideUI) {
+ await this.hideUI();
+ }
+ await this.canvas.waitFor({ state: "visible" });
+ await this.page.waitForFunction(() => {
+ return window.wasmSetObjectsFinished;
+ });
+ }
+
+ async hideUI() {
+ await this.page.keyboard.press("\\");
+ await expect(this.pageName).not.toBeVisible();
+ }
+
+ static async mockGoogleFont(page, fontSlug, assetFilename, options = {}) {
+ const url = new RegExp(`/internal/gfonts/font/${fontSlug}`);
+ return await page.route(url, (route) =>
+ route.fulfill({
+ status: 200,
+ path: `playwright/data/${assetFilename}`,
+ contentType: "application/font-ttf",
+ ...options,
+ }),
+ );
+ }
+
+ async mockGoogleFont(fontSlug, assetFilename, options) {
+ return WasmWorkspacePage.mockGoogleFont(
+ this.page,
+ fontSlug,
+ assetFilename,
+ options,
+ );
+ }
+}
diff --git a/frontend/playwright/ui/pages/WorkspacePage.js b/frontend/playwright/ui/pages/WorkspacePage.js
index 9f28446928..c85010edc1 100644
--- a/frontend/playwright/ui/pages/WorkspacePage.js
+++ b/frontend/playwright/ui/pages/WorkspacePage.js
@@ -99,7 +99,7 @@ export class WorkspacePage extends BaseWebSocketPage {
this.tokenThemeUpdateCreateModal = page.getByTestId(
"token-theme-update-create-modal",
);
- this.tokenThemesSetsSidebar = page.getByTestId("token-themes-sets-sidebar");
+ this.tokenThemesSetsSidebar = page.getByTestId("token-management-sidebar");
this.tokensSidebar = page.getByTestId("tokens-sidebar");
this.tokenSetItems = page.getByTestId("tokens-set-item");
this.tokenSetGroupItems = page.getByTestId("tokens-set-group-item");
@@ -178,6 +178,14 @@ export class WorkspacePage extends BaseWebSocketPage {
);
}
+ async mockGetFile(jsonFile) {
+ await this.mockRPC(/get\-file\?/, jsonFile);
+ }
+
+ async mockGetAsset(regex, asset) {
+ await this.mockRPC(new RegExp(regex), asset);
+ }
+
async setupFileWithComments() {
await this.mockRPC(
"get-comment-threads?file-id=*",
@@ -309,3 +317,5 @@ export class WorkspacePage extends BaseWebSocketPage {
.click(clickOptions);
}
}
+
+export default WorkspacePage;
\ No newline at end of file
diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js
new file mode 100644
index 0000000000..5cabe96c27
--- /dev/null
+++ b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js
@@ -0,0 +1,140 @@
+import { test, expect } from "@playwright/test";
+import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
+
+test.beforeEach(async ({ page }) => {
+ await WasmWorkspacePage.init(page);
+ await WasmWorkspacePage.mockConfigFlags(page, [
+ "enable-feature-render-wasm",
+ "enable-render-wasm-dpr",
+ ]);
+});
+
+test("Renders a file with basic shapes, boards and groups", async ({
+ page,
+}) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-shapes-groups-boards.json");
+
+ await workspace.goToWorkspace({
+ id: "53a7ff09-2228-81d3-8006-4b5eac177245",
+ pageId: "53a7ff09-2228-81d3-8006-4b5eac177246",
+ });
+ await workspace.waitForFirstRender();
+
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with solid, gradient and image fills", async ({
+ page,
+}) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockFileMediaAsset(
+ [
+ "1ebcea38-f1bf-8101-8006-4c8fd68e7c84",
+ "1ebcea38-f1bf-8101-8006-4c8f579da49c",
+ ],
+ "render-wasm/assets/penguins.jpg",
+ );
+ await workspace.mockGetFile("render-wasm/get-file-shapes-fills.json");
+
+ await workspace.goToWorkspace({
+ id: "1ebcea38-f1bf-8101-8006-4c8ec4a9bffe",
+ pageId: "1ebcea38-f1bf-8101-8006-4c8ec4a9bfff",
+ });
+ await workspace.waitForFirstRender();
+
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with strokes", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockFileMediaAsset(
+ [
+ "202c1104-9385-81d3-8006-5074e4682cac",
+ "202c1104-9385-81d3-8006-5074c50339b6",
+ "202c1104-9385-81d3-8006-507560ce29e3",
+ ],
+ "render-wasm/assets/penguins.jpg",
+ );
+ await workspace.mockGetFile("render-wasm/get-file-shapes-strokes.json");
+
+ await workspace.goToWorkspace({
+ id: "202c1104-9385-81d3-8006-507413ff2c99",
+ pageId: "202c1104-9385-81d3-8006-507413ff2c9a",
+ });
+ await workspace.waitForFirstRender();
+
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with mutliple strokes", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-multiple-strokes.json");
+
+ await workspace.goToWorkspace({
+ id: "c0939f58-37bc-805d-8006-51cc78297208",
+ pageId: "c0939f58-37bc-805d-8006-51cc78297209",
+ });
+ await workspace.waitForFirstRender();
+
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with shapes with multiple fills", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-multiple-fills.json");
+
+ await workspace.goToWorkspace({
+ id: "c0939f58-37bc-805d-8006-51cd3a51c255",
+ pageId: "c0939f58-37bc-805d-8006-51cd3a51c256",
+ });
+ await workspace.waitForFirstRender();
+
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+// TODO: update the screenshots for this test once Taiga #11325 is fixed
+// https://tree.taiga.io/project/penpot/task/11325
+test("Renders shapes taking into account blend modes", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-blend-modes.json");
+
+ await workspace.goToWorkspace({
+ id: "c0939f58-37bc-805d-8006-51cdf8e18e76",
+ pageId: "c0939f58-37bc-805d-8006-51cdf8e18e77",
+ });
+ await workspace.waitForFirstRender();
+
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders shapes with exif rotated images fills and strokes", async ({
+ page,
+}) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockFileMediaAsset(
+ [
+ "27270c45-35b4-80f3-8006-63a39cf292e7",
+ "27270c45-35b4-80f3-8006-63a41d147866",
+ "27270c45-35b4-80f3-8006-63a43dc4984b",
+ "27270c45-35b4-80f3-8006-63a3ea82557f"
+ ],
+ "render-wasm/assets/landscape.jpg",
+ );
+ await workspace.mockGetFile("render-wasm/get-file-shapes-exif-rotated-fills.json");
+
+ await workspace.goToWorkspace({
+ id: "27270c45-35b4-80f3-8006-63a3912bdce8",
+ pageId: "27270c45-35b4-80f3-8006-63a3912bdce9",
+ });
+ await workspace.waitForFirstRender();
+
+ await expect(workspace.canvas).toHaveScreenshot();
+});
diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-basic-shapes-boards-and-groups-1.png b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-basic-shapes-boards-and-groups-1.png
new file mode 100644
index 0000000000..3a97677c9d
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-basic-shapes-boards-and-groups-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-mutliple-strokes-1.png b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-mutliple-strokes-1.png
new file mode 100644
index 0000000000..3adb6fa54d
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-mutliple-strokes-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-shapes-with-multiple-fills-1.png b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-shapes-with-multiple-fills-1.png
new file mode 100644
index 0000000000..5f0e40d500
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-shapes-with-multiple-fills-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-solid-gradient-and-image-fills-1.png b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-solid-gradient-and-image-fills-1.png
new file mode 100644
index 0000000000..263c4d87e1
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-solid-gradient-and-image-fills-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-strokes-1.png b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-strokes-1.png
new file mode 100644
index 0000000000..f11e7402ba
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-a-file-with-strokes-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-shapes-taking-into-account-blend-modes-1.png b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-shapes-taking-into-account-blend-modes-1.png
new file mode 100644
index 0000000000..570536f930
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-shapes-taking-into-account-blend-modes-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-shapes-with-exif-rotated-images-fills-and-strokes-1.png b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-shapes-with-exif-rotated-images-fills-and-strokes-1.png
new file mode 100644
index 0000000000..d1d5459015
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/shapes.spec.js-snapshots/Renders-shapes-with-exif-rotated-images-fills-and-strokes-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js b/frontend/playwright/ui/render-wasm-specs/texts.spec.js
new file mode 100644
index 0000000000..8f810dadc2
--- /dev/null
+++ b/frontend/playwright/ui/render-wasm-specs/texts.spec.js
@@ -0,0 +1,336 @@
+import { test, expect } from "@playwright/test";
+import { WasmWorkspacePage } from "../pages/WasmWorkspacePage";
+
+test.beforeEach(async ({ page }) => {
+ await WasmWorkspacePage.init(page);
+ await WasmWorkspacePage.mockConfigFlags(page, [
+ "enable-feature-render-wasm",
+ "enable-render-wasm-dpr",
+ ]);
+});
+
+async function mockGetEmojiFont(workspace) {
+ await workspace.mockGetAsset(
+ /notocoloremoji.*\.ttf$/,
+ "render-wasm/assets/notocoloremojisubset.ttf"
+ );
+}
+
+async function mockGetJapaneseFont(workspace) {
+ await workspace.mockGetAsset(
+ /notosansjp.*\.ttf$/,
+ "render-wasm/assets/notosansjpsubset.ttf"
+ );
+ await workspace.mockGetAsset(
+ /notosanssc.*\.ttf$/,
+ "render-wasm/assets/notosansjpsubset.ttf"
+ );
+}
+
+
+test("Renders a file with texts", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-text.json");
+
+ await workspace.goToWorkspace({
+ id: "3b0d758a-8c9d-8013-8006-52c8337e5c72",
+ pageId: "3b0d758a-8c9d-8013-8006-52c8337e5c73",
+ });
+ await workspace.waitForFirstRender();
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Updates a text font", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-text.json");
+
+ await workspace.goToWorkspace({
+ id: "3b0d758a-8c9d-8013-8006-52c8337e5c72",
+ pageId: "3b0d758a-8c9d-8013-8006-52c8337e5c73",
+ });
+ await workspace.waitForFirstRender({ hideUI: false });
+
+ await workspace.clickLeafLayer("this is a text");
+ const fontStyle = workspace.page.getByTitle("Font Style");
+ await fontStyle.click();
+ const boldOption = fontStyle.getByText("bold").first();
+ await boldOption.click();
+
+ await workspace.hideUI();
+
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with texts that use google fonts", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-text-google-fonts.json");
+ await workspace.mockGoogleFont(
+ "ebgaramond",
+ "render-wasm/assets/ebgaramond.ttf",
+ );
+ await workspace.mockGoogleFont("firacode", "render-wasm/assets/firacode.ttf");
+
+ await workspace.goToWorkspace({
+ id: "434b0541-fa2f-802f-8006-5981e47bd732",
+ pageId: "434b0541-fa2f-802f-8006-5981e47bd733",
+ });
+ await workspace.waitForFirstRender();
+
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with texts that use custom fonts", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-text-custom-fonts.json");
+ await workspace.mockRPC(
+ "get-font-variants?team-id=*",
+ "render-wasm/get-font-variants-custom-fonts.json",
+ );
+ await workspace.mockAsset(
+ "2d1ffeb6-e70b-4027-bbcc-910248ba45f8",
+ "render-wasm/assets/mreaves.ttf",
+ );
+ await workspace.mockAsset(
+ "69e76833-0816-49fa-8c7b-4b97c71c6f1a",
+ "render-wasm/assets/nodesto-condensed.ttf",
+ );
+
+ await workspace.goToWorkspace({
+ id: "434b0541-fa2f-802f-8006-59827d964a9b",
+ pageId: "434b0541-fa2f-802f-8006-59827d964a9c",
+ });
+ await workspace.waitForFirstRender();
+
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with styled texts", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-text-styles.json");
+
+ await workspace.goToWorkspace({
+ id: "6bd7c17d-4f59-815e-8006-5c2559af4939",
+ pageId: "6bd7c17d-4f59-815e-8006-5c2559af493a",
+ });
+ await workspace.waitForFirstRender();
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with texts with images", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockFileMediaAsset(
+ [
+ "6bd7c17d-4f59-815e-8006-5e9765e0fabd",
+ "6bd7c17d-4f59-815e-8006-5e97441071cc"
+ ],
+ "render-wasm/assets/pattern.png",
+ );
+ await mockGetEmojiFont(workspace);
+ await mockGetJapaneseFont(workspace);
+
+ await workspace.mockGetFile("render-wasm/get-file-text-images.json");
+
+ await workspace.goToWorkspace({
+ id: "6bd7c17d-4f59-815e-8006-5e96453952b0",
+ pageId: "6bd7c17d-4f59-815e-8006-5e96453952b1",
+ });
+ await workspace.waitForFirstRender();
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with text decoration", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockFileMediaAsset(
+ [
+ "d6c33e7b-7b64-80f3-8006-78509a3a2d21",
+ ],
+ "render-wasm/assets/pattern.png",
+ );
+ await mockGetEmojiFont(workspace);
+ await mockGetJapaneseFont(workspace);
+
+ await workspace.mockGetFile("render-wasm/get-file-text-decoration.json");
+
+ await workspace.goToWorkspace({
+ id: "d6c33e7b-7b64-80f3-8006-785098582f1d",
+ pageId: "d6c33e7b-7b64-80f3-8006-785098582f1e",
+ });
+ await workspace.waitForFirstRender();
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with multiple emoji", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-text-emoji-board.json");
+
+ await mockGetEmojiFont(workspace);
+
+ await workspace.goToWorkspace({
+ id: "6bd7c17d-4f59-815e-8006-5e999f38f210",
+ pageId: "6bd7c17d-4f59-815e-8006-5e999f38f211",
+ });
+
+ await workspace.waitForFirstRender();
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Renders a file with texts with different alignments", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-file-text-align.json");
+
+ await workspace.goToWorkspace({
+ id: "692f368b-63ca-8141-8006-62925640b827",
+ pageId: "692f368b-63ca-8141-8006-62925640b828",
+ });
+ await workspace.waitForFirstRender();
+ await expect(workspace.canvas).toHaveScreenshot();
+});
+
+test("Updates text alignment edition - part 1", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-multiple-texts-base.json");
+
+ await workspace.goToWorkspace({
+ id: "6bd7c17d-4f59-815e-8006-5c1f68846e43",
+ pageId: "f8b42814-8653-81cf-8006-638aacdc3ffb",
+ });
+ await workspace.waitForFirstRender({ hideUI: false });
+ await workspace.clickLeafLayer("Text 1");
+
+ const textOptionsButton = workspace.page.getByTestId("text-align-options-button");
+ const autoWidthButton = workspace.page.getByTitle("Auto width");
+ const autoHeightButton = workspace.page.getByTitle("Auto height");
+ const alignMiddleButton = workspace.page.getByTitle("Align middle");
+ const alignBottomButton = workspace.page.getByTitle("Align bottom");
+ const alignRightButton = workspace.page.getByTitle("Align right (Ctrl+Alt+R)");
+
+ await textOptionsButton.click();
+
+ await workspace.clickLeafLayer("Text 1");
+ await autoWidthButton.click();
+
+ await workspace.clickLeafLayer("Text 2");
+ await autoHeightButton.click();
+
+ await workspace.clickLeafLayer("Text 3");
+ await alignMiddleButton.click();
+ await alignRightButton.click();
+
+ await workspace.clickLeafLayer("Text 4");
+ await alignBottomButton.click();
+
+ await workspace.page.keyboard.press("Escape");
+ await workspace.hideUI();
+
+ await expect(workspace.canvas).toHaveScreenshot({timeout: 10000});
+});
+
+test("Updates text alignment edition - part 2", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-multiple-texts-base.json");
+
+ await workspace.goToWorkspace({
+ id: "6bd7c17d-4f59-815e-8006-5c1f68846e43",
+ pageId: "f8b42814-8653-81cf-8006-638aacdc3ffb",
+ });
+ await workspace.waitForFirstRender({ hideUI: false });
+ await workspace.clickLeafLayer("Text 1");
+
+ const textOptionsButton = workspace.page.getByTestId("text-align-options-button");
+ const alignTopButton = workspace.page.getByTitle("Align top");
+ const alignMiddleButton = workspace.page.getByTitle("Align middle");
+ const alignBottomButton = workspace.page.getByTitle("Align bottom");
+ const alignCenterButton = workspace.page.getByTitle("Align center (Ctrl+Alt+T)");
+ const alignJustifyButton = workspace.page.getByTitle("Justify (Ctrl+Alt+J)");
+ const LTRButton = workspace.page.getByTitle("LTR");
+ const RTLButton = workspace.page.getByTitle("RTL");
+
+ await textOptionsButton.click();
+
+ await workspace.clickLeafLayer("Text 5");
+ await alignBottomButton.click();
+ await alignTopButton.click();
+ await alignCenterButton.click();
+
+ await workspace.clickLeafLayer("Text 6");
+ await alignJustifyButton.click();
+ await RTLButton.click();
+
+ await workspace.clickLeafLayer("Text 7");
+ await alignJustifyButton.click();
+ await RTLButton.click();
+ await LTRButton.click();
+
+ await workspace.clickLeafLayer("Text 8");
+ await alignMiddleButton.click();
+ await alignJustifyButton.click();
+ await RTLButton.click();
+
+ await workspace.page.keyboard.press("Escape");
+ await workspace.hideUI();
+
+ await expect(workspace.canvas).toHaveScreenshot({timeout: 10000});
+});
+
+test("Updates text alignment edition - part 3", async ({ page }) => {
+ const workspace = new WasmWorkspacePage(page);
+ await workspace.setupEmptyFile();
+ await workspace.mockGetFile("render-wasm/get-multiple-texts-base.json");
+
+ await workspace.goToWorkspace({
+ id: "6bd7c17d-4f59-815e-8006-5c1f68846e43",
+ pageId: "f8b42814-8653-81cf-8006-638aacdc3ffb",
+ });
+ await workspace.waitForFirstRender({ hideUI: false });
+ await workspace.clickLeafLayer("Text 1");
+
+ const textOptionsButton = workspace.page.getByTestId("text-align-options-button");
+ const autoWidthButton = workspace.page.getByTitle("Auto width");
+ const autoHeightButton = workspace.page.getByTitle("Auto height");
+ const alignMiddleButton = workspace.page.getByTitle("Align middle");
+ const alignBottomButton = workspace.page.getByTitle("Align bottom");
+ const alignLeftButton = workspace.page.getByTitle("Align left (Ctrl+Alt+L)");
+ const alignCenterButton = workspace.page.getByTitle("Align center (Ctrl+Alt+T)");
+ const alignJustifyButton = workspace.page.getByTitle("Justify (Ctrl+Alt+J)");
+ const RTLButton = workspace.page.getByTitle("RTL");
+
+ await textOptionsButton.click();
+
+ await workspace.clickLeafLayer("Text 9");
+ await autoHeightButton.click();
+ await alignBottomButton.click();
+ await alignJustifyButton.click();
+ await RTLButton.click();
+
+ await workspace.clickLeafLayer("Text 10");
+ await alignBottomButton.click();
+ await alignJustifyButton.click();
+ await RTLButton.click();
+ await autoWidthButton.click();
+
+ await workspace.clickLeafLayer("Text 11");
+ await alignCenterButton.click();
+ await alignBottomButton.click();
+
+ await workspace.clickLeafLayer("Text 12");
+ await alignCenterButton.click();
+ await alignLeftButton.click();
+ await alignMiddleButton.click();
+
+ await workspace.page.keyboard.press("Escape");
+ await workspace.hideUI();
+
+ await expect(workspace.canvas).toHaveScreenshot({timeout: 10000});
+});
\ No newline at end of file
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-multiple-emoji-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-multiple-emoji-1.png
new file mode 100644
index 0000000000..dd299bfd72
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-multiple-emoji-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-styled-texts-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-styled-texts-1.png
new file mode 100644
index 0000000000..e3f1130431
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-styled-texts-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-text-decoration-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-text-decoration-1.png
new file mode 100644
index 0000000000..de5e1b4d8b
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-text-decoration-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-1.png
new file mode 100644
index 0000000000..e90590339b
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-that-use-custom-fonts-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-that-use-custom-fonts-1.png
new file mode 100644
index 0000000000..88c1f8e7aa
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-that-use-custom-fonts-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-that-use-google-fonts-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-that-use-google-fonts-1.png
new file mode 100644
index 0000000000..054fb9ddaf
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-that-use-google-fonts-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-with-different-alignments-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-with-different-alignments-1.png
new file mode 100644
index 0000000000..19b96e2375
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-with-different-alignments-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-with-images-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-with-images-1.png
new file mode 100644
index 0000000000..a4b5ea307f
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Renders-a-file-with-texts-with-images-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-a-text-font-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-a-text-font-1.png
new file mode 100644
index 0000000000..bdf401d309
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-a-text-font-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-text-alignment-edition---part-1-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-text-alignment-edition---part-1-1.png
new file mode 100644
index 0000000000..6925ea8fa8
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-text-alignment-edition---part-1-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-text-alignment-edition---part-2-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-text-alignment-edition---part-2-1.png
new file mode 100644
index 0000000000..d364d1e16b
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-text-alignment-edition---part-2-1.png differ
diff --git a/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-text-alignment-edition---part-3-1.png b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-text-alignment-edition---part-3-1.png
new file mode 100644
index 0000000000..30d418f85e
Binary files /dev/null and b/frontend/playwright/ui/render-wasm-specs/texts.spec.js-snapshots/Updates-text-alignment-edition---part-3-1.png differ
diff --git a/frontend/playwright/ui/specs/colorpicker.spec.js b/frontend/playwright/ui/specs/colorpicker.spec.js
index e847e659bf..012bb52379 100644
--- a/frontend/playwright/ui/specs/colorpicker.spec.js
+++ b/frontend/playwright/ui/specs/colorpicker.spec.js
@@ -61,24 +61,28 @@ test("Create a LINEAR gradient", async ({ page }) => {
const removeBtn = workspacePage.colorpicker
.getByRole("button", { name: "Remove color" })
- .nth(2);
+ .last();
await removeBtn.click();
await removeBtn.click();
const inputColor1 = workspacePage.colorpicker
- .getByPlaceholder("Mixed")
- .nth(1);
- await inputColor1.fill("fabada");
+ .getByRole("textbox", { name: "Color" })
+ .first();
+ await inputColor1.fill("#fabada");
- const inputOpacity1 = workspacePage.colorpicker.getByPlaceholder("--").nth(1);
+ const inputOpacity1 = workspacePage.colorpicker
+ .getByTestId("opacity-input")
+ .first();
await inputOpacity1.fill("100");
const inputColor2 = workspacePage.colorpicker
- .getByPlaceholder("Mixed")
- .nth(2);
- await inputColor2.fill("red");
+ .getByRole("textbox", { name: "Color" })
+ .last();
+ await inputColor2.fill("#ff0000");
- const inputOpacity2 = workspacePage.colorpicker.getByPlaceholder("--").nth(2);
+ const inputOpacity2 = workspacePage.colorpicker
+ .getByTestId("opacity-input")
+ .last();
await inputOpacity2.fill("40");
const inputOpacityGlobal = workspacePage.colorpicker.getByTestId(
@@ -139,20 +143,28 @@ test("Create a RADIAL gradient", async ({ page }) => {
const removeBtn = workspacePage.colorpicker
.getByRole("button", { name: "Remove color" })
- .nth(2);
+ .last();
await removeBtn.click();
await removeBtn.click();
- const inputColor1 = workspacePage.page.getByPlaceholder("Mixed").nth(1);
- await inputColor1.fill("fabada");
+ const inputColor1 = workspacePage.page
+ .getByRole("textbox", { name: "Color" })
+ .first();
+ await inputColor1.fill("#fabada");
- const inputOpacity1 = workspacePage.colorpicker.getByPlaceholder("--").nth(1);
+ const inputOpacity1 = workspacePage.colorpicker
+ .getByTestId("opacity-input")
+ .first();
await inputOpacity1.fill("100");
- const inputColor2 = workspacePage.page.getByPlaceholder("Mixed").nth(2);
- await inputColor2.fill("red");
+ const inputColor2 = workspacePage.page
+ .getByRole("textbox", { name: "Color" })
+ .last();
+ await inputColor2.fill("#ff0000");
- const inputOpacity2 = workspacePage.colorpicker.getByPlaceholder("--").nth(2);
+ const inputOpacity2 = workspacePage.colorpicker
+ .getByTestId("opacity-input")
+ .last();
await inputOpacity2.fill("100");
const inputOpacityGlobal = workspacePage.colorpicker.getByTestId(
diff --git a/frontend/playwright/ui/specs/register.spec.js b/frontend/playwright/ui/specs/register.spec.js
new file mode 100644
index 0000000000..301701a225
--- /dev/null
+++ b/frontend/playwright/ui/specs/register.spec.js
@@ -0,0 +1,21 @@
+import { test, expect } from "@playwright/test";
+import { RegisterPage } from "../pages/RegisterPage";
+
+test.beforeEach(async ({ page }) => {
+ await RegisterPage.initWithLoggedOutUser(page);
+ await page.goto("/#/auth/register");
+});
+
+test.describe("Register form errors", () => {
+ test("User gets error message when email does not match invitation", async ({ page }) => {
+ const registerPage = new RegisterPage(page);
+ await registerPage.setupMismatchedEmailError();
+
+ await registerPage.fillRegisterFormInputs("John Doe", "john.doe@example.com", "password123");
+ await registerPage.clickRegisterButton();
+
+ await expect(page.getByText(
+ "Email does not match the invitation.",
+ )).toBeVisible();
+ });
+});
diff --git a/frontend/playwright/ui/specs/subscriptions-dashboard.spec.js b/frontend/playwright/ui/specs/subscriptions-dashboard.spec.js
new file mode 100644
index 0000000000..62cb1a718e
--- /dev/null
+++ b/frontend/playwright/ui/specs/subscriptions-dashboard.spec.js
@@ -0,0 +1,502 @@
+import { test, expect } from "@playwright/test";
+import DashboardPage from "../pages/DashboardPage";
+
+test.beforeEach(async ({ page }) => {
+ await DashboardPage.init(page);
+ await DashboardPage.mockConfigFlags(page, [
+ "enable-subscriptions",
+ "disable-onboarding",
+ ]);
+});
+
+test.describe("Subscriptions: dashboard", () => {
+ test("Team with unlimited subscription has specific icon in menu", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-unlimited-subscription.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-subscription-owner.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-projects?team-id=*",
+ "dashboard/get-projects-second-team.json",
+ );
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+ await dashboardPage.goToSecondTeamDashboard();
+ await expect(page.getByTestId("subscription-icon")).toBeVisible();
+ });
+
+ test("The Unlimited subscription has its name in the sidebar dropdown", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-unlimited-subscription.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-subscription-owner.json",
+ );
+
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+ await dashboardPage.goToDashboard();
+
+ await expect(page.getByTestId("subscription-name")).toHaveText(
+ "Unlimited plan (trial)",
+ );
+ });
+
+ test("When the subscription status is unpaid, the sidebar dropdown displays the name Professional for the Unlimited subscription", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-unlimited-unpaid-subscription.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-subscription-owner.json",
+ );
+
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+ await dashboardPage.goToDashboard();
+
+ await expect(page.getByTestId("subscription-name")).toHaveText(
+ "Professional plan",
+ );
+ });
+
+ test("When the subscription status is canceled, the sidebar dropdown displays the name Professional for the Enterprise subscription", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-enterprise-canceled-subscription.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-subscription-owner.json",
+ );
+
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+ await dashboardPage.goToDashboard();
+
+ await expect(page.getByTestId("subscription-name")).toHaveText(
+ "Professional plan",
+ );
+ });
+});
+
+test.describe("Subscriptions: team members and invitations", () => {
+ test("Team settings has susbscription name and no manage subscription link when is member", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "logged-in-user/get-profile-logged-in.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-subscription-member.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-projects?team-id=*",
+ "dashboard/get-projects-second-team.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-members?team-id=*",
+ "subscription/get-team-members-subscription-member.json",
+ );
+
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+
+ await dashboardPage.goToSecondTeamSettingsSection();
+ await expect(page.getByText("Unlimited (trial)")).toBeVisible();
+ await expect(
+ page.getByRole("button", { name: "Manage your subscription" }),
+ ).not.toBeVisible();
+ });
+
+ test("Team settings has susbscription name and manage subscription link when is owner", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-unlimited-subscription.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-subscription-owner.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-projects?team-id=*",
+ "dashboard/get-projects-second-team.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-members?team-id=*",
+ "subscription/get-team-members-subscription-owner.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-stats?team-id=*",
+ "dashboard/get-team-stats.json",
+ );
+
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+
+ await dashboardPage.goToSecondTeamSettingsSection();
+
+ await expect(page.getByText("Unlimited (trial)")).toBeVisible();
+ await expect(
+ page.getByRole("button", { name: "Manage your subscription" }),
+ ).toBeVisible();
+ });
+
+ test("Members tab has warning message when team has more members than subscriptions. Subscribe link is shown for owners.", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-unlimited-subscription.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-subscription-owner.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-projects?team-id=*",
+ "dashboard/get-projects-second-team.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-members?team-id=*",
+ "subscription/get-team-members-subscription-owner.json",
+ );
+
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+
+ await dashboardPage.goToSecondTeamMembersSection();
+ await expect(page.getByTestId("cta")).toBeVisible();
+ await expect(page.getByText("Subscribe now.")).toBeVisible();
+ });
+
+ test("Members tab has warning message when team has more members than subscriptions. Contact to owner is shown for members.", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "logged-in-user/get-profile-logged-in.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-subscription-member.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-projects?team-id=*",
+ "dashboard/get-projects-second-team.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-members?team-id=*",
+ "subscription/get-team-members-subscription-member.json",
+ );
+
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+
+ await dashboardPage.goToSecondTeamMembersSection();
+ await expect(page.getByTestId("cta")).toBeVisible();
+ await expect(page.getByText("Contact with the team owner")).toBeVisible();
+ });
+
+ test("Members tab has warning message when has professional subscription and more than 8 members.", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "logged-in-user/get-profile-logged-in.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-professional-subscription-owner.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-projects?team-id=*",
+ "dashboard/get-projects-second-team.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-members?team-id=*",
+ "subscription/get-team-members-more-than-8.json",
+ );
+
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+
+ await dashboardPage.goToSecondTeamMembersSection();
+ await expect(page.getByTestId("cta")).toBeVisible();
+ await expect(
+ page.getByText(
+ "The Professional plan is designed for teams of up to 8 editors (owner, admin, and editor).",
+ ),
+ ).toBeVisible();
+ });
+
+ test("Invitations tab has warning message when team has more members than subscriptions", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-unlimited-subscription.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-subscription-owner.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-projects?team-id=*",
+ "dashboard/get-projects-second-team.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-members?team-id=*",
+ "subscription/get-team-members-subscription-owner.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-invitations?team-id=*",
+ "dashboard/get-team-invitations-empty.json",
+ );
+
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+
+ await dashboardPage.goToSecondTeamInvitationsSection();
+ await expect(page.getByTestId("cta")).toBeVisible();
+ await expect(
+ page.getByText(
+ "Looks like your team has grown! Your plan includes 2 seats, but you're now using 3",
+ ),
+ ).toBeVisible();
+ });
+
+ test("Invitations tab has warning message when has professional subscription and more than 8 members.", async ({
+ page,
+ }) => {
+ await DashboardPage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-unlimited-subscription.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-info",
+ "subscription/get-team-info-subscriptions.json",
+ );
+
+ const dashboardPage = new DashboardPage(page);
+ await dashboardPage.setupDashboardFull();
+ await DashboardPage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-subscription-owner.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-projects?team-id=*",
+ "dashboard/get-projects-second-team.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-members?team-id=*",
+ "subscription/get-team-members-more-than-8.json",
+ );
+
+ await DashboardPage.mockRPC(
+ page,
+ "get-team-invitations?team-id=*",
+ "dashboard/get-team-invitations-empty.json",
+ );
+
+ await dashboardPage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+
+ await dashboardPage.goToSecondTeamInvitationsSection();
+ await expect(page.getByTestId("cta")).toBeVisible();
+ await expect(
+ page.getByText(
+ "Looks like your team has grown! Your plan includes 2 seats, but you're now using 9",
+ ),
+ ).toBeVisible();
+ });
+});
diff --git a/frontend/playwright/ui/specs/subscriptions-profile.spec.js b/frontend/playwright/ui/specs/subscriptions-profile.spec.js
new file mode 100644
index 0000000000..4f93070fcb
--- /dev/null
+++ b/frontend/playwright/ui/specs/subscriptions-profile.spec.js
@@ -0,0 +1,111 @@
+import { test, expect } from "@playwright/test";
+import SubscriptionProfilePage from "../pages/SubscriptionProfilePage";
+
+test.beforeEach(async ({ page }) => {
+ await SubscriptionProfilePage.init(page);
+
+ await SubscriptionProfilePage.mockConfigFlags(page, [
+ "enable-subscriptions",
+ "disable-onboarding",
+ ]);
+});
+
+test.describe("Subscriptions: profile", () => {
+ test("When subscription is professional there is no manage subscription link", async ({
+ page,
+ }) => {
+ await SubscriptionProfilePage.mockRPC(
+ page,
+ "get-profile",
+ "logged-in-user/get-profile-logged-in.json",
+ );
+
+ const subscriptionProfilePage = new SubscriptionProfilePage(page);
+
+ await subscriptionProfilePage.goToSubscriptions();
+
+ await expect(
+ page.getByRole("button", { name: "Manage your subscription" }),
+ ).not.toBeVisible();
+
+ await expect(
+ page.getByRole("heading", { name: "Other Penpot plans", level: 3 }),
+ ).toBeVisible();
+
+ await expect(page.getByText("$7")).toBeVisible();
+
+ await expect(page.getByText("$950")).toBeVisible();
+
+ await expect(
+ page.getByRole("button", { name: "Try it free for 14 days" }).first(),
+ ).toBeVisible();
+ });
+
+ test("When subscription is unlimited there is manage subscription link", async ({
+ page,
+ }) => {
+ await SubscriptionProfilePage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-unlimited-subscription.json",
+ );
+
+ const subscriptionProfilePage = new SubscriptionProfilePage(page);
+
+ await subscriptionProfilePage.goToSubscriptions();
+
+ await expect(
+ page.getByRole("button", { name: "Manage your subscription" }),
+ ).toBeVisible();
+
+ await expect(
+ page.getByRole("heading", { name: "Other Penpot plans", level: 3 }),
+ ).toBeVisible();
+
+ await expect(page.getByText("$0")).toBeVisible();
+
+ await expect(page.getByText("$950")).toBeVisible();
+
+ await expect(
+ page.getByRole("button", { name: "Try it free for 14 days" }).first(),
+ ).not.toBeVisible();
+
+ await expect(
+ page.getByRole("button", { name: "Subscribe" }).first(),
+ ).toBeVisible();
+ });
+
+ test("When subscription is enteprise there is manage subscription link", async ({
+ page,
+ }) => {
+ await SubscriptionProfilePage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-enterprise-subscription.json",
+ );
+
+ const subscriptionProfilePage = new SubscriptionProfilePage(page);
+
+ await subscriptionProfilePage.goToSubscriptions();
+
+ await expect(
+ page.getByRole("button", { name: "Manage your subscription" }),
+ ).toBeVisible();
+
+ await expect(
+ page.getByRole("heading", { name: "Other Penpot plans", level: 3 }),
+ ).toBeVisible();
+
+ await expect(page.getByText("$0")).toBeVisible();
+
+ await expect(page.getByText("$7")).toBeVisible();
+
+ await expect(
+ page.getByRole("button", { name: "Try it free for 14 days" }).first(),
+ ).not.toBeVisible();
+
+ await expect(
+ page.getByRole("button", { name: "Subscribe" }).first(),
+ ).toBeVisible();
+ });
+});
diff --git a/frontend/playwright/ui/specs/subscriptions-workspace.spec.js b/frontend/playwright/ui/specs/subscriptions-workspace.spec.js
new file mode 100644
index 0000000000..237f4354b3
--- /dev/null
+++ b/frontend/playwright/ui/specs/subscriptions-workspace.spec.js
@@ -0,0 +1,152 @@
+import { test, expect } from "@playwright/test";
+import WorkspacePage from "../pages/WorkspacePage";
+
+test.beforeEach(async ({ page }) => {
+ await WorkspacePage.init(page);
+ await WorkspacePage.mockConfigFlags(page, [
+ "enable-subscriptions",
+ "disable-onboarding",
+ ]);
+});
+
+test.describe("Subscriptions: workspace", () => {
+ test("Unlimited team should have 'Power up your plan' link in main menu", async ({
+ page,
+ }) => {
+ const workspacePage = new WorkspacePage(page);
+ await workspacePage.setupEmptyFile();
+
+ await WorkspacePage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-unlimited-subscription.json",
+ );
+
+ await workspacePage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+ await workspacePage.goToWorkspace();
+ await page.getByRole("button", { name: "Main menu" }).click();
+
+ await expect(page.getByText("Power up your plan")).toBeVisible();
+ });
+
+ test("Enterprise team should not have 'Power up your plan' link in main menu", async ({
+ page,
+ }) => {
+ const workspacePage = new WorkspacePage(page);
+ await workspacePage.setupEmptyFile();
+
+ await WorkspacePage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-enterprise-subscription.json",
+ );
+
+ await workspacePage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+ await workspacePage.goToWorkspace();
+ await page.getByRole("button", { name: "Main menu" }).click();
+
+ await expect(page.getByText("Power up your plan")).not.toBeVisible();
+ });
+
+ test("Professional team should have 7 days autosaved versions", async ({
+ page,
+ }) => {
+ const workspacePage = new WorkspacePage(page);
+ await workspacePage.setupEmptyFile();
+
+ await workspacePage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+ await workspacePage.goToWorkspace();
+
+ await workspacePage.mockRPC(
+ "get-file-snapshots?file-id=*",
+ "workspace/versions-snapshot-1.json",
+ );
+
+ await page.getByLabel("History").click();
+
+ await expect(
+ page.getByText("Autosaved versions will be kept for 7 days."),
+ ).toBeVisible();
+ });
+
+ test("Unlimited team should have 30 days autosaved versions", async ({
+ page,
+ }) => {
+ const workspacePage = new WorkspacePage(page);
+ await workspacePage.setupEmptyFile();
+
+ await WorkspacePage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-unlimited-subscription.json",
+ );
+
+ await WorkspacePage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-unlimited-one-team.json",
+ );
+
+ await workspacePage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+ await workspacePage.goToWorkspace();
+
+ await workspacePage.mockRPC(
+ "get-file-snapshots?file-id=*",
+ "workspace/versions-snapshot-1.json",
+ );
+
+ await page.getByLabel("History").click();
+
+ await expect(
+ page.getByText("Autosaved versions will be kept for 30 days."),
+ ).toBeVisible();
+ });
+
+ test("Unlimited team should have 90 days autosaved versions", async ({
+ page,
+ }) => {
+ const workspacePage = new WorkspacePage(page);
+ await workspacePage.setupEmptyFile();
+
+ await WorkspacePage.mockRPC(
+ page,
+ "get-profile",
+ "subscription/get-profile-enterprise-subscription.json",
+ );
+
+ await WorkspacePage.mockRPC(
+ page,
+ "get-teams",
+ "subscription/get-teams-enterprise-one-team.json",
+ );
+
+ await workspacePage.mockRPC(
+ "push-audit-events",
+ "workspace/audit-event-empty.json",
+ );
+ await workspacePage.goToWorkspace();
+
+ await workspacePage.mockRPC(
+ "get-file-snapshots?file-id=*",
+ "workspace/versions-snapshot-1.json",
+ );
+
+ await page.getByLabel("History").click();
+
+ await expect(
+ page.getByText("Autosaved versions will be kept for 90 days."),
+ ).toBeVisible();
+ });
+});
diff --git a/frontend/playwright/ui/specs/tokens.spec.js b/frontend/playwright/ui/specs/tokens.spec.js
index e108c3025f..a5b24ff3c2 100644
--- a/frontend/playwright/ui/specs/tokens.spec.js
+++ b/frontend/playwright/ui/specs/tokens.spec.js
@@ -529,6 +529,14 @@ test.describe("Tokens: Sets Tab", () => {
await changeSetInput(sidebar, setName, (finalKey = "Enter"));
};
+ const assertEmptySetsList = async (el) => {
+ const buttons = await el.getByRole("button").allTextContents();
+ const filteredButtons = buttons.filter(
+ (text) => text === "Create one.",
+ );
+ await expect(filteredButtons.length).toEqual(2); // We assume there are no themes, so we have two "Create one" buttons.
+ };
+
const assertSetsList = async (el, sets) => {
const buttons = await el.getByRole("button").allTextContents();
const filteredButtons = buttons.filter(
@@ -623,6 +631,15 @@ test.describe("Tokens: Sets Tab", () => {
"sizes",
"small",
]);
+
+ // User deletes all sets
+ await tokenThemesSetsSidebar
+ .getByRole("button", { name: "core" })
+ .click({ button: "right" });
+ await expect(tokenContextMenuForSet).toBeVisible();
+ await tokenContextMenuForSet.getByText("Delete").click();
+
+ await assertEmptySetsList(tokenThemesSetsSidebar);
});
test("User can create & edit sets and set groups with an identical name", async ({
diff --git a/frontend/resources/images/help-variant-connection.png b/frontend/resources/images/help-variant-connection.png
new file mode 100644
index 0000000000..2d743678f7
Binary files /dev/null and b/frontend/resources/images/help-variant-connection.png differ
diff --git a/frontend/resources/images/icons/logo-subscription-light.svg b/frontend/resources/images/icons/logo-subscription-light.svg
new file mode 100644
index 0000000000..838ca4b8cd
--- /dev/null
+++ b/frontend/resources/images/icons/logo-subscription-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/resources/images/icons/text-font-family.svg b/frontend/resources/images/icons/text-font-family.svg
new file mode 100644
index 0000000000..bc7f333ec2
--- /dev/null
+++ b/frontend/resources/images/icons/text-font-family.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/text-font-size.svg b/frontend/resources/images/icons/text-font-size.svg
new file mode 100644
index 0000000000..bbcdec161c
--- /dev/null
+++ b/frontend/resources/images/icons/text-font-size.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/text-font-weight.svg b/frontend/resources/images/icons/text-font-weight.svg
new file mode 100644
index 0000000000..6f096afc7b
--- /dev/null
+++ b/frontend/resources/images/icons/text-font-weight.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/text-typography.svg b/frontend/resources/images/icons/text-typography.svg
new file mode 100644
index 0000000000..522e63915f
--- /dev/null
+++ b/frontend/resources/images/icons/text-typography.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/plugins-runtime/index.js b/frontend/resources/plugins-runtime/index.js
deleted file mode 100644
index 22419ffeb9..0000000000
--- a/frontend/resources/plugins-runtime/index.js
+++ /dev/null
@@ -1,8601 +0,0 @@
-var qn = (t) => {
- throw TypeError(t);
-};
-var Kn = (t, e, r) => e.has(t) || qn("Cannot " + r);
-var at = (t, e, r) => (Kn(t, e, "read from private field"), r ? r.call(t) : e.get(t)), tn = (t, e, r) => e.has(t) ? qn("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(t) : e.set(t, r), fr = (t, e, r, n) => (Kn(t, e, "write to private field"), n ? n.call(t, r) : e.set(t, r), r);
-const S = globalThis, {
- Array: oa,
- ArrayBuffer: To,
- Date: sa,
- FinalizationRegistry: $t,
- Float32Array: aa,
- JSON: ia,
- Map: $e,
- Math: ca,
- Number: Io,
- Object: Tn,
- Promise: la,
- Proxy: Ur,
- Reflect: ua,
- RegExp: Xe,
- Set: Dt,
- String: _e,
- Symbol: At,
- Uint8Array: mn,
- WeakMap: ze,
- WeakSet: Ut
-} = globalThis, {
- // The feral Error constructor is safe for internal use, but must not be
- // revealed to post-lockdown code in any compartment including the start
- // compartment since in V8 at least it bears stack inspection capabilities.
- Error: le,
- RangeError: da,
- ReferenceError: Wt,
- SyntaxError: ir,
- TypeError: _,
- AggregateError: rn
-} = globalThis, {
- assign: jr,
- create: H,
- defineProperties: B,
- entries: me,
- freeze: y,
- getOwnPropertyDescriptor: ee,
- getOwnPropertyDescriptors: Be,
- getOwnPropertyNames: Nt,
- getPrototypeOf: G,
- is: Zr,
- isFrozen: fu,
- isSealed: pu,
- isExtensible: hu,
- keys: Co,
- prototype: zr,
- seal: mu,
- preventExtensions: fa,
- setPrototypeOf: wr,
- values: Ro,
- fromEntries: bt
-} = Tn, {
- species: nn,
- toStringTag: Qe,
- iterator: De,
- matchAll: $o,
- unscopables: pa,
- keyFor: ha,
- for: ma
-} = At, { isInteger: ga } = Io, { stringify: No } = ia, { defineProperty: ya } = Tn, D = (t, e, r) => {
- const n = ya(t, e, r);
- if (n !== t)
- throw _(
- `Please report that the original defineProperty silently failed to set ${No(
- _e(e)
- )}. (SES_DEFINE_PROPERTY_FAILED_SILENTLY)`
- );
- return n;
-}, {
- apply: ue,
- construct: xr,
- get: _a,
- getOwnPropertyDescriptor: va,
- has: Oo,
- isExtensible: ba,
- ownKeys: qe,
- preventExtensions: wa,
- set: Mo
-} = ua, { isArray: pt, prototype: ve } = oa, { prototype: Sr } = To, { prototype: jt } = $e, { prototype: Br } = RegExp, { prototype: cr } = Dt, { prototype: Ge } = _e, { prototype: Gr } = ze, { prototype: Lo } = Ut, { prototype: Vr } = Function, { prototype: Fo } = la, { prototype: Do } = G(
- // eslint-disable-next-line no-empty-function, func-names
- function* () {
- }
-), on = G(
- // eslint-disable-next-line @endo/no-polymorphic-call
- G(ve.values())
-), Uo = G(mn.prototype), { bind: gn } = Vr, A = gn.bind(gn.call), Q = A(zr.hasOwnProperty), et = A(ve.filter), ht = A(ve.forEach), Hr = A(ve.includes), Zt = A(ve.join), de = (
- /** @type {any} */
- A(ve.map)
-), jo = (
- /** @type {any} */
- A(ve.flatMap)
-), Er = A(ve.pop), ne = A(ve.push), xa = A(ve.slice), Zo = A(ve.some), zo = A(ve.sort), Sa = A(ve[De]), Ea = A(Sr.slice), ka = A(
- // @ts-expect-error we know it is there on all conforming platforms
- ee(Sr, "byteLength").get
-), Pa = A(Uo.set), pe = A(jt.set), Ke = A(jt.get), Wr = A(jt.has), Aa = A(jt.delete), Ta = A(jt.entries), Ia = A(jt[De]), In = A(cr.add);
-A(cr.delete);
-const Yn = A(cr.forEach), Cn = A(cr.has), Ca = A(cr[De]), Rn = A(Br.test), $n = A(Br.exec), Ra = A(Br[$o]), Bo = A(Ge.endsWith), Go = A(Ge.includes), $a = A(Ge.indexOf);
-A(Ge.match);
-const kr = A(Do.next), Vo = A(Do.throw), Pr = (
- /** @type {any} */
- A(Ge.replace)
-), Na = A(Ge.search), Nn = A(Ge.slice), On = (
- /** @type {(thisArg: string, splitter: string | RegExp | { [Symbol.split](string: string, limit?: number): string[]; }, limit?: number) => string[]} */
- A(Ge.split)
-), Ho = A(Ge.startsWith), Oa = A(Ge[De]), Ma = A(Gr.delete), z = A(Gr.get), Tt = A(Gr.has), he = A(Gr.set), qr = A(Lo.add), lr = A(Lo.has), La = A(Vr.toString), Wo = A(gn);
-A(Fo.catch);
-const qo = (
- /** @type {any} */
- A(Fo.then)
-), Fa = $t && A($t.prototype.register);
-$t && A($t.prototype.unregister);
-const Mn = y(H(null)), ke = (t) => Tn(t) === t, Kr = (t) => t instanceof le, Ko = eval, Ee = Function, Da = () => {
- throw _('Cannot eval with evalTaming set to "noEval" (SES_NO_EVAL)');
-}, Je = ee(Error("er1"), "stack"), sn = ee(_("er2"), "stack");
-let Yo, Jo;
-if (Je && sn && Je.get)
- if (
- // In the v8 case as we understand it, all errors have an own stack
- // accessor property, but within the same realm, all these accessor
- // properties have the same getter and have the same setter.
- // This is therefore the case that we repair.
- typeof Je.get == "function" && Je.get === sn.get && typeof Je.set == "function" && Je.set === sn.set
- )
- Yo = y(Je.get), Jo = y(Je.set);
- else
- throw _(
- "Unexpected Error own stack accessor functions (SES_UNEXPECTED_ERROR_OWN_STACK_ACCESSOR)"
- );
-const an = Yo, Ua = Jo;
-function ja() {
- return this;
-}
-if (ja())
- throw _("SES failed to initialize, sloppy mode (SES_NO_SLOPPY)");
-const { freeze: ut } = Object, { apply: Za } = Reflect, Ln = (t) => (e, ...r) => Za(t, e, r), za = Ln(Array.prototype.push), Jn = Ln(Array.prototype.includes), Ba = Ln(String.prototype.split), it = JSON.stringify, pr = (t, ...e) => {
- let r = t[0];
- for (let n = 0; n < e.length; n += 1)
- r = `${r}${e[n]}${t[n + 1]}`;
- throw Error(r);
-}, Xo = (t, e = !1) => {
- const r = [], n = (c, l, u = void 0) => {
- typeof c == "string" || pr`Environment option name ${it(c)} must be a string.`, typeof l == "string" || pr`Environment option default setting ${it(
- l
- )} must be a string.`;
- let d = l;
- const f = t.process || void 0, h = typeof f == "object" && f.env || void 0;
- if (typeof h == "object" && c in h) {
- e || za(r, c);
- const p = h[c];
- typeof p == "string" || pr`Environment option named ${it(
- c
- )}, if present, must have a corresponding string value, got ${it(
- p
- )}`, d = p;
- }
- return u === void 0 || d === l || Jn(u, d) || pr`Unrecognized ${it(c)} value ${it(
- d
- )}. Expected one of ${it([l, ...u])}`, d;
- };
- ut(n);
- const o = (c) => {
- const l = n(c, "");
- return ut(l === "" ? [] : Ba(l, ","));
- };
- ut(o);
- const s = (c, l) => Jn(o(c), l), i = () => ut([...r]);
- return ut(i), ut({
- getEnvironmentOption: n,
- getEnvironmentOptionsList: o,
- environmentOptionsListHas: s,
- getCapturedEnvironmentOptionNames: i
- });
-};
-ut(Xo);
-const {
- getEnvironmentOption: ce,
- getEnvironmentOptionsList: gu,
- environmentOptionsListHas: yu
-} = Xo(globalThis, !0), Ar = (t) => (t = `${t}`, t.length >= 1 && Go("aeiouAEIOU", t[0]) ? `an ${t}` : `a ${t}`);
-y(Ar);
-const Qo = (t, e = void 0) => {
- const r = new Dt(), n = (o, s) => {
- switch (typeof s) {
- case "object": {
- if (s === null)
- return null;
- if (Cn(r, s))
- return "[Seen]";
- if (In(r, s), Kr(s))
- return `[${s.name}: ${s.message}]`;
- if (Qe in s)
- return `[${s[Qe]}]`;
- if (pt(s))
- return s;
- const i = Co(s);
- if (i.length < 2)
- return s;
- let c = !0;
- for (let u = 1; u < i.length; u += 1)
- if (i[u - 1] >= i[u]) {
- c = !1;
- break;
- }
- if (c)
- return s;
- zo(i);
- const l = de(i, (u) => [u, s[u]]);
- return bt(l);
- }
- case "function":
- return `[Function ${s.name || ""}]`;
- case "string":
- return Ho(s, "[") ? `[${s}]` : s;
- case "undefined":
- case "symbol":
- return `[${_e(s)}]`;
- case "bigint":
- return `[${s}n]`;
- case "number":
- return Zr(s, NaN) ? "[NaN]" : s === 1 / 0 ? "[Infinity]" : s === -1 / 0 ? "[-Infinity]" : s;
- default:
- return s;
- }
- };
- try {
- return No(t, n, e);
- } catch {
- return "[Something that failed to stringify]";
- }
-};
-y(Qo);
-const { isSafeInteger: Ga } = Number, { freeze: Et } = Object, { toStringTag: Va } = Symbol, Xn = (t) => {
- const r = {
- next: void 0,
- prev: void 0,
- data: t
- };
- return r.next = r, r.prev = r, r;
-}, Qn = (t, e) => {
- if (t === e)
- throw TypeError("Cannot splice a cell into itself");
- if (e.next !== e || e.prev !== e)
- throw TypeError("Expected self-linked cell");
- const r = e, n = t.next;
- return r.prev = t, r.next = n, t.next = r, n.prev = r, r;
-}, cn = (t) => {
- const { prev: e, next: r } = t;
- e.next = r, r.prev = e, t.prev = t, t.next = t;
-}, es = (t) => {
- if (!Ga(t) || t < 0)
- throw TypeError("keysBudget must be a safe non-negative integer number");
- const e = /* @__PURE__ */ new WeakMap();
- let r = 0;
- const n = Xn(void 0), o = (d) => {
- const f = e.get(d);
- if (!(f === void 0 || f.data === void 0))
- return cn(f), Qn(n, f), f;
- }, s = (d) => o(d) !== void 0;
- Et(s);
- const i = (d) => {
- const f = o(d);
- return f && f.data && f.data.get(d);
- };
- Et(i);
- const c = (d, f) => {
- if (t < 1)
- return u;
- let h = o(d);
- if (h === void 0 && (h = Xn(void 0), Qn(n, h)), !h.data)
- for (r += 1, h.data = /* @__PURE__ */ new WeakMap(), e.set(d, h); r > t; ) {
- const p = n.prev;
- cn(p), p.data = void 0, r -= 1;
- }
- return h.data.set(d, f), u;
- };
- Et(c);
- const l = (d) => {
- const f = e.get(d);
- return f === void 0 || (cn(f), e.delete(d), f.data === void 0) ? !1 : (f.data = void 0, r -= 1, !0);
- };
- Et(l);
- const u = Et({
- has: s,
- get: i,
- set: c,
- delete: l,
- // eslint-disable-next-line jsdoc/check-types
- [
- /** @type {typeof Symbol.toStringTag} */
- Va
- ]: "LRUCacheMap"
- });
- return u;
-};
-Et(es);
-const { freeze: vr } = Object, { isSafeInteger: Ha } = Number, Wa = 1e3, qa = 100, ts = (t = Wa, e = qa) => {
- if (!Ha(e) || e < 1)
- throw TypeError(
- "argsPerErrorBudget must be a safe positive integer number"
- );
- const r = es(t), n = (s, i) => {
- const c = r.get(s);
- c !== void 0 ? (c.length >= e && c.shift(), c.push(i)) : r.set(s, [i]);
- };
- vr(n);
- const o = (s) => {
- const i = r.get(s);
- return r.delete(s), i;
- };
- return vr(o), vr({
- addLogArgs: n,
- takeLogArgsArray: o
- });
-};
-vr(ts);
-const Ot = new ze(), U = (t, e = void 0) => {
- const r = y({
- toString: y(() => Qo(t, e))
- });
- return he(Ot, r, t), r;
-};
-y(U);
-const Ka = y(/^[\w:-]( ?[\w:-])*$/), Tr = (t, e = void 0) => {
- if (typeof t != "string" || !Rn(Ka, t))
- return U(t, e);
- const r = y({
- toString: y(() => t)
- });
- return he(Ot, r, t), r;
-};
-y(Tr);
-const Yr = new ze(), rs = ({ template: t, args: e }) => {
- const r = [t[0]];
- for (let n = 0; n < e.length; n += 1) {
- const o = e[n];
- let s;
- Tt(Ot, o) ? s = `${o}` : Kr(o) ? s = `(${Ar(o.name)})` : s = `(${Ar(typeof o)})`, ne(r, s, t[n + 1]);
- }
- return Zt(r, "");
-}, ns = y({
- toString() {
- const t = z(Yr, this);
- return t === void 0 ? "[Not a DetailsToken]" : rs(t);
- }
-});
-y(ns.toString);
-const re = (t, ...e) => {
- const r = y({ __proto__: ns });
- return he(Yr, r, { template: t, args: e }), /** @type {DetailsToken} */
- /** @type {unknown} */
- r;
-};
-y(re);
-const os = (t, ...e) => (e = de(
- e,
- (r) => Tt(Ot, r) ? r : U(r)
-), re(t, ...e));
-y(os);
-const ss = ({ template: t, args: e }) => {
- const r = [t[0]];
- for (let n = 0; n < e.length; n += 1) {
- let o = e[n];
- Tt(Ot, o) && (o = z(Ot, o));
- const s = Pr(Er(r) || "", / $/, "");
- s !== "" && ne(r, s);
- const i = Pr(t[n + 1], /^ /, "");
- ne(r, o, i);
- }
- return r[r.length - 1] === "" && Er(r), r;
-}, br = new ze();
-let yn = 0;
-const eo = new ze(), as = (t, e = t.name) => {
- let r = z(eo, t);
- return r !== void 0 || (yn += 1, r = `${e}#${yn}`, he(eo, t, r)), r;
-}, Ya = (t) => {
- const e = Be(t), {
- name: r,
- message: n,
- errors: o = void 0,
- cause: s = void 0,
- stack: i = void 0,
- ...c
- } = e, l = qe(c);
- if (l.length >= 1) {
- for (const d of l)
- delete t[d];
- const u = H(zr, c);
- Jr(
- t,
- re`originally with properties ${U(u)}`
- );
- }
- for (const u of qe(t)) {
- const d = e[u];
- d && Q(d, "get") && D(t, u, {
- value: t[u]
- // invoke the getter to convert to data property
- });
- }
- y(t);
-}, Ce = (t = re`Assert failed`, e = S.Error, {
- errorName: r = void 0,
- cause: n = void 0,
- errors: o = void 0,
- sanitize: s = !0
-} = {}) => {
- typeof t == "string" && (t = re([t]));
- const i = z(Yr, t);
- if (i === void 0)
- throw _(`unrecognized details ${U(t)}`);
- const c = rs(i), l = n && { cause: n };
- let u;
- return typeof rn < "u" && e === rn ? u = rn(o || [], c, l) : (u = /** @type {ErrorConstructor} */
- e(
- c,
- l
- ), o !== void 0 && D(u, "errors", {
- value: o,
- writable: !0,
- enumerable: !1,
- configurable: !0
- })), he(br, u, ss(i)), r !== void 0 && as(u, r), s && Ya(u), u;
-};
-y(Ce);
-const { addLogArgs: Ja, takeLogArgsArray: Xa } = ts(), _n = new ze(), Jr = (t, e) => {
- typeof e == "string" && (e = re([e]));
- const r = z(Yr, e);
- if (r === void 0)
- throw _(`unrecognized details ${U(e)}`);
- const n = ss(r), o = z(_n, t);
- if (o !== void 0)
- for (const s of o)
- s(t, n);
- else
- Ja(t, n);
-};
-y(Jr);
-const Qa = (t) => {
- if (!("stack" in t))
- return "";
- const e = `${t.stack}`, r = $a(e, `
-`);
- return Ho(e, " ") || r === -1 ? e : Nn(e, r + 1);
-}, Ir = {
- getStackString: S.getStackString || Qa,
- tagError: (t) => as(t),
- resetErrorTagNum: () => {
- yn = 0;
- },
- getMessageLogArgs: (t) => z(br, t),
- takeMessageLogArgs: (t) => {
- const e = z(br, t);
- return Ma(br, t), e;
- },
- takeNoteLogArgsArray: (t, e) => {
- const r = Xa(t);
- if (e !== void 0) {
- const n = z(_n, t);
- n ? ne(n, e) : he(_n, t, [e]);
- }
- return r || [];
- }
-};
-y(Ir);
-const Xr = (t = void 0, e = !1) => {
- const r = e ? os : re, n = r`Check failed`, o = (f = n, h = void 0, p = void 0) => {
- const m = Ce(f, h, p);
- throw t !== void 0 && t(m), m;
- };
- y(o);
- const s = (f, ...h) => o(r(f, ...h));
- function i(f, h = void 0, p = void 0, m = void 0) {
- f || o(h, p, m);
- }
- const c = (f, h, p = void 0, m = void 0, k = void 0) => {
- Zr(f, h) || o(
- p || r`Expected ${f} is same as ${h}`,
- m || da,
- k
- );
- };
- y(c);
- const l = (f, h, p) => {
- if (typeof f !== h) {
- if (typeof h == "string" || s`${U(h)} must be a string`, p === void 0) {
- const m = Ar(h);
- p = r`${f} must be ${Tr(m)}`;
- }
- o(p, _);
- }
- };
- y(l);
- const d = jr(i, {
- error: Ce,
- fail: o,
- equal: c,
- typeof: l,
- string: (f, h = void 0) => l(f, "string", h),
- note: Jr,
- details: r,
- Fail: s,
- quote: U,
- bare: Tr,
- makeAssert: Xr
- });
- return y(d);
-};
-y(Xr);
-const Y = Xr(), to = Y.equal, is = ee(
- Uo,
- Qe
-);
-Y(is);
-const cs = is.get;
-Y(cs);
-const ei = (t) => ue(cs, t, []) !== void 0, ti = (t) => {
- const e = +_e(t);
- return ga(e) && _e(e) === t;
-}, ri = (t) => {
- fa(t), ht(qe(t), (e) => {
- const r = ee(t, e);
- Y(r), ti(e) || D(t, e, {
- ...r,
- writable: !1,
- configurable: !1
- });
- });
-}, ni = () => {
- if (typeof S.harden == "function")
- return S.harden;
- const t = new Ut(), { harden: e } = {
- /**
- * @template T
- * @param {T} root
- * @returns {T}
- */
- harden(r) {
- const n = new Dt();
- function o(d) {
- if (!ke(d))
- return;
- const f = typeof d;
- if (f !== "object" && f !== "function")
- throw _(`Unexpected typeof: ${f}`);
- lr(t, d) || Cn(n, d) || In(n, d);
- }
- const s = (d) => {
- ei(d) ? ri(d) : y(d);
- const f = Be(d), h = G(d);
- o(h), ht(qe(f), (p) => {
- const m = f[
- /** @type {string} */
- p
- ];
- Q(m, "value") ? o(m.value) : (o(m.get), o(m.set));
- });
- }, i = an === void 0 && Ua === void 0 ? (
- // On platforms without v8's error own stack accessor problem,
- // don't pay for any extra overhead.
- s
- ) : (d) => {
- if (Kr(d)) {
- const f = ee(d, "stack");
- f && f.get === an && f.configurable && D(d, "stack", {
- // NOTE: Calls getter during harden, which seems dangerous.
- // But we're only calling the problematic getter whose
- // hazards we think we understand.
- // @ts-expect-error TS should know FERAL_STACK_GETTER
- // cannot be `undefined` here.
- // See https://github.com/endojs/endo/pull/2232#discussion_r1575179471
- value: ue(an, d, [])
- });
- }
- return s(d);
- }, c = () => {
- Yn(n, i);
- }, l = (d) => {
- qr(t, d);
- }, u = () => {
- Yn(n, l);
- };
- return o(r), c(), u(), r;
- }
- };
- return e;
-}, ls = (t, e, r, n, { warn: o, error: s }) => {
- r || o(`Removing ${n}`);
- try {
- delete t[e];
- } catch (i) {
- if (Q(t, e)) {
- if (typeof t == "function" && e === "prototype" && (t.prototype = void 0, t.prototype === void 0)) {
- o(`Tolerating undeletable ${n} === undefined`);
- return;
- }
- s(`failed to delete ${n}`, i);
- } else
- s(`deleting ${n} threw`, i);
- throw i;
- }
-}, us = {
- // *** Value Properties of the Global Object
- Infinity: 1 / 0,
- NaN: NaN,
- undefined: void 0
-}, ds = {
- // *** Function Properties of the Global Object
- isFinite: "isFinite",
- isNaN: "isNaN",
- parseFloat: "parseFloat",
- parseInt: "parseInt",
- decodeURI: "decodeURI",
- decodeURIComponent: "decodeURIComponent",
- encodeURI: "encodeURI",
- encodeURIComponent: "encodeURIComponent",
- // *** Constructor Properties of the Global Object
- Array: "Array",
- ArrayBuffer: "ArrayBuffer",
- BigInt: "BigInt",
- BigInt64Array: "BigInt64Array",
- BigUint64Array: "BigUint64Array",
- Boolean: "Boolean",
- DataView: "DataView",
- EvalError: "EvalError",
- // https://github.com/tc39/proposal-float16array
- Float16Array: "Float16Array",
- Float32Array: "Float32Array",
- Float64Array: "Float64Array",
- Int8Array: "Int8Array",
- Int16Array: "Int16Array",
- Int32Array: "Int32Array",
- Map: "Map",
- Number: "Number",
- Object: "Object",
- Promise: "Promise",
- Proxy: "Proxy",
- RangeError: "RangeError",
- ReferenceError: "ReferenceError",
- Set: "Set",
- String: "String",
- SyntaxError: "SyntaxError",
- TypeError: "TypeError",
- Uint8Array: "Uint8Array",
- Uint8ClampedArray: "Uint8ClampedArray",
- Uint16Array: "Uint16Array",
- Uint32Array: "Uint32Array",
- URIError: "URIError",
- WeakMap: "WeakMap",
- WeakSet: "WeakSet",
- // https://github.com/tc39/proposal-iterator-helpers
- Iterator: "Iterator",
- // https://github.com/tc39/proposal-async-iterator-helpers
- AsyncIterator: "AsyncIterator",
- // https://github.com/endojs/endo/issues/550
- AggregateError: "AggregateError",
- // *** Other Properties of the Global Object
- JSON: "JSON",
- Reflect: "Reflect",
- // *** Annex B
- escape: "escape",
- unescape: "unescape",
- // ESNext
- // https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source
- ModuleSource: "ModuleSource",
- lockdown: "lockdown",
- harden: "harden",
- HandledPromise: "HandledPromise"
- // TODO: Until Promise.delegate (see below).
-}, ro = {
- // *** Constructor Properties of the Global Object
- Date: "%InitialDate%",
- Error: "%InitialError%",
- RegExp: "%InitialRegExp%",
- // Omit `Symbol`, because we want the original to appear on the
- // start compartment without passing through the permits mechanism, since
- // we want to preserve all its properties, even if we never heard of them.
- // Symbol: '%InitialSymbol%',
- // *** Other Properties of the Global Object
- Math: "%InitialMath%",
- // ESNext
- // From Error-stack proposal
- // Only on initial global. No corresponding
- // powerless form for other globals.
- getStackString: "%InitialGetStackString%"
- // TODO https://github.com/Agoric/SES-shim/issues/551
- // Need initial WeakRef and FinalizationGroup in
- // start compartment only.
-}, fs = {
- // *** Constructor Properties of the Global Object
- Date: "%SharedDate%",
- Error: "%SharedError%",
- RegExp: "%SharedRegExp%",
- Symbol: "%SharedSymbol%",
- // *** Other Properties of the Global Object
- Math: "%SharedMath%"
-}, ps = [
- EvalError,
- RangeError,
- ReferenceError,
- SyntaxError,
- TypeError,
- URIError
- // https://github.com/endojs/endo/issues/550
- // Commented out to accommodate platforms prior to AggregateError.
- // Instead, conditional push below.
- // AggregateError,
-];
-typeof AggregateError < "u" && ne(ps, AggregateError);
-const vn = {
- "[[Proto]]": "%FunctionPrototype%",
- length: "number",
- name: "string"
- // Do not specify "prototype" here, since only Function instances that can
- // be used as a constructor have a prototype property. For constructors,
- // since prototype properties are instance-specific, we define it there.
-}, oi = {
- // This property is not mentioned in ECMA 262, but is present in V8 and
- // necessary for lockdown to succeed.
- "[[Proto]]": "%AsyncFunctionPrototype%"
-}, a = vn, no = oi, M = {
- get: a,
- set: "undefined"
-}, Le = {
- get: a,
- set: a
-}, oo = (t) => t === M || t === Le;
-function ct(t) {
- return {
- // Properties of the NativeError Constructors
- "[[Proto]]": "%SharedError%",
- // NativeError.prototype
- prototype: t
- };
-}
-function lt(t) {
- return {
- // Properties of the NativeError Prototype Objects
- "[[Proto]]": "%ErrorPrototype%",
- constructor: t,
- message: "string",
- name: "string",
- // Redundantly present only on v8. Safe to remove.
- toString: !1,
- // Superfluously present in some versions of V8.
- // https://github.com/tc39/notes/blob/master/meetings/2021-10/oct-26.md#:~:text=However%2C%20Chrome%2093,and%20node%2016.11.
- cause: !1
- };
-}
-function xe(t) {
- return {
- // Properties of the TypedArray Constructors
- "[[Proto]]": "%TypedArray%",
- BYTES_PER_ELEMENT: "number",
- prototype: t
- };
-}
-function Se(t) {
- return {
- // Properties of the TypedArray Prototype Objects
- "[[Proto]]": "%TypedArrayPrototype%",
- BYTES_PER_ELEMENT: "number",
- constructor: t
- };
-}
-const so = {
- E: "number",
- LN10: "number",
- LN2: "number",
- LOG10E: "number",
- LOG2E: "number",
- PI: "number",
- SQRT1_2: "number",
- SQRT2: "number",
- "@@toStringTag": "string",
- abs: a,
- acos: a,
- acosh: a,
- asin: a,
- asinh: a,
- atan: a,
- atanh: a,
- atan2: a,
- cbrt: a,
- ceil: a,
- clz32: a,
- cos: a,
- cosh: a,
- exp: a,
- expm1: a,
- floor: a,
- fround: a,
- hypot: a,
- imul: a,
- log: a,
- log1p: a,
- log10: a,
- log2: a,
- max: a,
- min: a,
- pow: a,
- round: a,
- sign: a,
- sin: a,
- sinh: a,
- sqrt: a,
- tan: a,
- tanh: a,
- trunc: a,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- idiv: !1,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- idivmod: !1,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- imod: !1,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- imuldiv: !1,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- irem: !1,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- mod: !1,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523#issuecomment-1942904505
- irandom: !1
-}, Cr = {
- // ECMA https://tc39.es/ecma262
- // The intrinsics object has no prototype to avoid conflicts.
- "[[Proto]]": null,
- // %ThrowTypeError%
- "%ThrowTypeError%": a,
- // *** The Global Object
- // *** Value Properties of the Global Object
- Infinity: "number",
- NaN: "number",
- undefined: "undefined",
- // *** Function Properties of the Global Object
- // eval
- "%UniqueEval%": a,
- isFinite: a,
- isNaN: a,
- parseFloat: a,
- parseInt: a,
- decodeURI: a,
- decodeURIComponent: a,
- encodeURI: a,
- encodeURIComponent: a,
- // *** Fundamental Objects
- Object: {
- // Properties of the Object Constructor
- "[[Proto]]": "%FunctionPrototype%",
- assign: a,
- create: a,
- defineProperties: a,
- defineProperty: a,
- entries: a,
- freeze: a,
- fromEntries: a,
- getOwnPropertyDescriptor: a,
- getOwnPropertyDescriptors: a,
- getOwnPropertyNames: a,
- getOwnPropertySymbols: a,
- getPrototypeOf: a,
- hasOwn: a,
- is: a,
- isExtensible: a,
- isFrozen: a,
- isSealed: a,
- keys: a,
- preventExtensions: a,
- prototype: "%ObjectPrototype%",
- seal: a,
- setPrototypeOf: a,
- values: a,
- // https://github.com/tc39/proposal-array-grouping
- groupBy: a,
- // Seen on QuickJS
- __getClass: !1
- },
- "%ObjectPrototype%": {
- // Properties of the Object Prototype Object
- "[[Proto]]": null,
- constructor: "Object",
- hasOwnProperty: a,
- isPrototypeOf: a,
- propertyIsEnumerable: a,
- toLocaleString: a,
- toString: a,
- valueOf: a,
- // Annex B: Additional Properties of the Object.prototype Object
- // See note in header about the difference between [[Proto]] and --proto--
- // special notations.
- "--proto--": Le,
- __defineGetter__: a,
- __defineSetter__: a,
- __lookupGetter__: a,
- __lookupSetter__: a
- },
- "%UniqueFunction%": {
- // Properties of the Function Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%FunctionPrototype%"
- },
- "%InertFunction%": {
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%FunctionPrototype%"
- },
- "%FunctionPrototype%": {
- apply: a,
- bind: a,
- call: a,
- constructor: "%InertFunction%",
- toString: a,
- "@@hasInstance": a,
- // proposed but not yet std. To be removed if there
- caller: !1,
- // proposed but not yet std. To be removed if there
- arguments: !1,
- // Seen on QuickJS. TODO grab getter for use by console
- fileName: !1,
- // Seen on QuickJS. TODO grab getter for use by console
- lineNumber: !1
- },
- Boolean: {
- // Properties of the Boolean Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%BooleanPrototype%"
- },
- "%BooleanPrototype%": {
- constructor: "Boolean",
- toString: a,
- valueOf: a
- },
- "%SharedSymbol%": {
- // Properties of the Symbol Constructor
- "[[Proto]]": "%FunctionPrototype%",
- asyncDispose: "symbol",
- asyncIterator: "symbol",
- dispose: "symbol",
- for: a,
- hasInstance: "symbol",
- isConcatSpreadable: "symbol",
- iterator: "symbol",
- keyFor: a,
- match: "symbol",
- matchAll: "symbol",
- prototype: "%SymbolPrototype%",
- replace: "symbol",
- search: "symbol",
- species: "symbol",
- split: "symbol",
- toPrimitive: "symbol",
- toStringTag: "symbol",
- unscopables: "symbol",
- // Seen at core-js https://github.com/zloirock/core-js#ecmascript-symbol
- useSimple: !1,
- // Seen at core-js https://github.com/zloirock/core-js#ecmascript-symbol
- useSetter: !1,
- // Seen on QuickJS
- operatorSet: !1
- },
- "%SymbolPrototype%": {
- // Properties of the Symbol Prototype Object
- constructor: "%SharedSymbol%",
- description: M,
- toString: a,
- valueOf: a,
- "@@toPrimitive": a,
- "@@toStringTag": "string"
- },
- "%InitialError%": {
- // Properties of the Error Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%ErrorPrototype%",
- // Non standard, v8 only, used by tap
- captureStackTrace: a,
- // Non standard, v8 only, used by tap, tamed to accessor
- stackTraceLimit: Le,
- // Non standard, v8 only, used by several, tamed to accessor
- prepareStackTrace: Le
- },
- "%SharedError%": {
- // Properties of the Error Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%ErrorPrototype%",
- // Non standard, v8 only, used by tap
- captureStackTrace: a,
- // Non standard, v8 only, used by tap, tamed to accessor
- stackTraceLimit: Le,
- // Non standard, v8 only, used by several, tamed to accessor
- prepareStackTrace: Le
- },
- "%ErrorPrototype%": {
- constructor: "%SharedError%",
- message: "string",
- name: "string",
- toString: a,
- // proposed de-facto, assumed TODO
- // Seen on FF Nightly 88.0a1
- at: !1,
- // Seen on FF and XS
- stack: Le,
- // Superfluously present in some versions of V8.
- // https://github.com/tc39/notes/blob/master/meetings/2021-10/oct-26.md#:~:text=However%2C%20Chrome%2093,and%20node%2016.11.
- cause: !1
- },
- // NativeError
- EvalError: ct("%EvalErrorPrototype%"),
- RangeError: ct("%RangeErrorPrototype%"),
- ReferenceError: ct("%ReferenceErrorPrototype%"),
- SyntaxError: ct("%SyntaxErrorPrototype%"),
- TypeError: ct("%TypeErrorPrototype%"),
- URIError: ct("%URIErrorPrototype%"),
- // https://github.com/endojs/endo/issues/550
- AggregateError: ct("%AggregateErrorPrototype%"),
- "%EvalErrorPrototype%": lt("EvalError"),
- "%RangeErrorPrototype%": lt("RangeError"),
- "%ReferenceErrorPrototype%": lt("ReferenceError"),
- "%SyntaxErrorPrototype%": lt("SyntaxError"),
- "%TypeErrorPrototype%": lt("TypeError"),
- "%URIErrorPrototype%": lt("URIError"),
- // https://github.com/endojs/endo/issues/550
- "%AggregateErrorPrototype%": lt("AggregateError"),
- // *** Numbers and Dates
- Number: {
- // Properties of the Number Constructor
- "[[Proto]]": "%FunctionPrototype%",
- EPSILON: "number",
- isFinite: a,
- isInteger: a,
- isNaN: a,
- isSafeInteger: a,
- MAX_SAFE_INTEGER: "number",
- MAX_VALUE: "number",
- MIN_SAFE_INTEGER: "number",
- MIN_VALUE: "number",
- NaN: "number",
- NEGATIVE_INFINITY: "number",
- parseFloat: a,
- parseInt: a,
- POSITIVE_INFINITY: "number",
- prototype: "%NumberPrototype%"
- },
- "%NumberPrototype%": {
- // Properties of the Number Prototype Object
- constructor: "Number",
- toExponential: a,
- toFixed: a,
- toLocaleString: a,
- toPrecision: a,
- toString: a,
- valueOf: a
- },
- BigInt: {
- // Properties of the BigInt Constructor
- "[[Proto]]": "%FunctionPrototype%",
- asIntN: a,
- asUintN: a,
- prototype: "%BigIntPrototype%",
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- bitLength: !1,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- fromArrayBuffer: !1,
- // Seen on QuickJS
- tdiv: !1,
- // Seen on QuickJS
- fdiv: !1,
- // Seen on QuickJS
- cdiv: !1,
- // Seen on QuickJS
- ediv: !1,
- // Seen on QuickJS
- tdivrem: !1,
- // Seen on QuickJS
- fdivrem: !1,
- // Seen on QuickJS
- cdivrem: !1,
- // Seen on QuickJS
- edivrem: !1,
- // Seen on QuickJS
- sqrt: !1,
- // Seen on QuickJS
- sqrtrem: !1,
- // Seen on QuickJS
- floorLog2: !1,
- // Seen on QuickJS
- ctz: !1
- },
- "%BigIntPrototype%": {
- constructor: "BigInt",
- toLocaleString: a,
- toString: a,
- valueOf: a,
- "@@toStringTag": "string"
- },
- "%InitialMath%": {
- ...so,
- // `%InitialMath%.random()` has the standard unsafe behavior
- random: a
- },
- "%SharedMath%": {
- ...so,
- // `%SharedMath%.random()` is tamed to always throw
- random: a
- },
- "%InitialDate%": {
- // Properties of the Date Constructor
- "[[Proto]]": "%FunctionPrototype%",
- now: a,
- parse: a,
- prototype: "%DatePrototype%",
- UTC: a
- },
- "%SharedDate%": {
- // Properties of the Date Constructor
- "[[Proto]]": "%FunctionPrototype%",
- // `%SharedDate%.now()` is tamed to always throw
- now: a,
- parse: a,
- prototype: "%DatePrototype%",
- UTC: a
- },
- "%DatePrototype%": {
- constructor: "%SharedDate%",
- getDate: a,
- getDay: a,
- getFullYear: a,
- getHours: a,
- getMilliseconds: a,
- getMinutes: a,
- getMonth: a,
- getSeconds: a,
- getTime: a,
- getTimezoneOffset: a,
- getUTCDate: a,
- getUTCDay: a,
- getUTCFullYear: a,
- getUTCHours: a,
- getUTCMilliseconds: a,
- getUTCMinutes: a,
- getUTCMonth: a,
- getUTCSeconds: a,
- setDate: a,
- setFullYear: a,
- setHours: a,
- setMilliseconds: a,
- setMinutes: a,
- setMonth: a,
- setSeconds: a,
- setTime: a,
- setUTCDate: a,
- setUTCFullYear: a,
- setUTCHours: a,
- setUTCMilliseconds: a,
- setUTCMinutes: a,
- setUTCMonth: a,
- setUTCSeconds: a,
- toDateString: a,
- toISOString: a,
- toJSON: a,
- toLocaleDateString: a,
- toLocaleString: a,
- toLocaleTimeString: a,
- toString: a,
- toTimeString: a,
- toUTCString: a,
- valueOf: a,
- "@@toPrimitive": a,
- // Annex B: Additional Properties of the Date.prototype Object
- getYear: a,
- setYear: a,
- toGMTString: a
- },
- // Text Processing
- String: {
- // Properties of the String Constructor
- "[[Proto]]": "%FunctionPrototype%",
- fromCharCode: a,
- fromCodePoint: a,
- prototype: "%StringPrototype%",
- raw: a,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- fromArrayBuffer: !1
- },
- "%StringPrototype%": {
- // Properties of the String Prototype Object
- length: "number",
- at: a,
- charAt: a,
- charCodeAt: a,
- codePointAt: a,
- concat: a,
- constructor: "String",
- endsWith: a,
- includes: a,
- indexOf: a,
- lastIndexOf: a,
- localeCompare: a,
- match: a,
- matchAll: a,
- normalize: a,
- padEnd: a,
- padStart: a,
- repeat: a,
- replace: a,
- replaceAll: a,
- // ES2021
- search: a,
- slice: a,
- split: a,
- startsWith: a,
- substring: a,
- toLocaleLowerCase: a,
- toLocaleUpperCase: a,
- toLowerCase: a,
- toString: a,
- toUpperCase: a,
- trim: a,
- trimEnd: a,
- trimStart: a,
- valueOf: a,
- "@@iterator": a,
- // Annex B: Additional Properties of the String.prototype Object
- substr: a,
- anchor: a,
- big: a,
- blink: a,
- bold: a,
- fixed: a,
- fontcolor: a,
- fontsize: a,
- italics: a,
- link: a,
- small: a,
- strike: a,
- sub: a,
- sup: a,
- trimLeft: a,
- trimRight: a,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- compare: !1,
- // https://github.com/tc39/proposal-is-usv-string
- isWellFormed: a,
- toWellFormed: a,
- unicodeSets: a,
- // Seen on QuickJS
- __quote: !1
- },
- "%StringIteratorPrototype%": {
- "[[Proto]]": "%IteratorPrototype%",
- next: a,
- "@@toStringTag": "string"
- },
- "%InitialRegExp%": {
- // Properties of the RegExp Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%RegExpPrototype%",
- "@@species": M,
- // The https://github.com/tc39/proposal-regexp-legacy-features
- // are all optional, unsafe, and omitted
- input: !1,
- $_: !1,
- lastMatch: !1,
- "$&": !1,
- lastParen: !1,
- "$+": !1,
- leftContext: !1,
- "$`": !1,
- rightContext: !1,
- "$'": !1,
- $1: !1,
- $2: !1,
- $3: !1,
- $4: !1,
- $5: !1,
- $6: !1,
- $7: !1,
- $8: !1,
- $9: !1
- },
- "%SharedRegExp%": {
- // Properties of the RegExp Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%RegExpPrototype%",
- "@@species": M
- },
- "%RegExpPrototype%": {
- // Properties of the RegExp Prototype Object
- constructor: "%SharedRegExp%",
- exec: a,
- dotAll: M,
- flags: M,
- global: M,
- hasIndices: M,
- ignoreCase: M,
- "@@match": a,
- "@@matchAll": a,
- multiline: M,
- "@@replace": a,
- "@@search": a,
- source: M,
- "@@split": a,
- sticky: M,
- test: a,
- toString: a,
- unicode: M,
- unicodeSets: M,
- // Annex B: Additional Properties of the RegExp.prototype Object
- compile: !1
- // UNSAFE and suppressed.
- },
- "%RegExpStringIteratorPrototype%": {
- // The %RegExpStringIteratorPrototype% Object
- "[[Proto]]": "%IteratorPrototype%",
- next: a,
- "@@toStringTag": "string"
- },
- // Indexed Collections
- Array: {
- // Properties of the Array Constructor
- "[[Proto]]": "%FunctionPrototype%",
- from: a,
- isArray: a,
- of: a,
- prototype: "%ArrayPrototype%",
- "@@species": M,
- // Stage 3:
- // https://tc39.es/proposal-relative-indexing-method/
- at: a,
- // https://tc39.es/proposal-array-from-async/
- fromAsync: a
- },
- "%ArrayPrototype%": {
- // Properties of the Array Prototype Object
- at: a,
- length: "number",
- concat: a,
- constructor: "Array",
- copyWithin: a,
- entries: a,
- every: a,
- fill: a,
- filter: a,
- find: a,
- findIndex: a,
- flat: a,
- flatMap: a,
- forEach: a,
- includes: a,
- indexOf: a,
- join: a,
- keys: a,
- lastIndexOf: a,
- map: a,
- pop: a,
- push: a,
- reduce: a,
- reduceRight: a,
- reverse: a,
- shift: a,
- slice: a,
- some: a,
- sort: a,
- splice: a,
- toLocaleString: a,
- toString: a,
- unshift: a,
- values: a,
- "@@iterator": a,
- "@@unscopables": {
- "[[Proto]]": null,
- copyWithin: "boolean",
- entries: "boolean",
- fill: "boolean",
- find: "boolean",
- findIndex: "boolean",
- flat: "boolean",
- flatMap: "boolean",
- includes: "boolean",
- keys: "boolean",
- values: "boolean",
- // Failed tc39 proposal
- // Seen on FF Nightly 88.0a1
- at: "boolean",
- // See https://github.com/tc39/proposal-array-find-from-last
- findLast: "boolean",
- findLastIndex: "boolean",
- // https://github.com/tc39/proposal-change-array-by-copy
- toReversed: "boolean",
- toSorted: "boolean",
- toSpliced: "boolean",
- with: "boolean",
- // https://github.com/tc39/proposal-array-grouping
- group: "boolean",
- groupToMap: "boolean",
- groupBy: "boolean"
- },
- // See https://github.com/tc39/proposal-array-find-from-last
- findLast: a,
- findLastIndex: a,
- // https://github.com/tc39/proposal-change-array-by-copy
- toReversed: a,
- toSorted: a,
- toSpliced: a,
- with: a,
- // https://github.com/tc39/proposal-array-grouping
- group: a,
- // Not in proposal? Where?
- groupToMap: a,
- // Not in proposal? Where?
- groupBy: a
- },
- "%ArrayIteratorPrototype%": {
- // The %ArrayIteratorPrototype% Object
- "[[Proto]]": "%IteratorPrototype%",
- next: a,
- "@@toStringTag": "string"
- },
- // *** TypedArray Objects
- "%TypedArray%": {
- // Properties of the %TypedArray% Intrinsic Object
- "[[Proto]]": "%FunctionPrototype%",
- from: a,
- of: a,
- prototype: "%TypedArrayPrototype%",
- "@@species": M
- },
- "%TypedArrayPrototype%": {
- at: a,
- buffer: M,
- byteLength: M,
- byteOffset: M,
- constructor: "%TypedArray%",
- copyWithin: a,
- entries: a,
- every: a,
- fill: a,
- filter: a,
- find: a,
- findIndex: a,
- forEach: a,
- includes: a,
- indexOf: a,
- join: a,
- keys: a,
- lastIndexOf: a,
- length: M,
- map: a,
- reduce: a,
- reduceRight: a,
- reverse: a,
- set: a,
- slice: a,
- some: a,
- sort: a,
- subarray: a,
- toLocaleString: a,
- toString: a,
- values: a,
- "@@iterator": a,
- "@@toStringTag": M,
- // See https://github.com/tc39/proposal-array-find-from-last
- findLast: a,
- findLastIndex: a,
- // https://github.com/tc39/proposal-change-array-by-copy
- toReversed: a,
- toSorted: a,
- with: a
- },
- // The TypedArray Constructors
- BigInt64Array: xe("%BigInt64ArrayPrototype%"),
- BigUint64Array: xe("%BigUint64ArrayPrototype%"),
- // https://github.com/tc39/proposal-float16array
- Float16Array: xe("%Float16ArrayPrototype%"),
- Float32Array: xe("%Float32ArrayPrototype%"),
- Float64Array: xe("%Float64ArrayPrototype%"),
- Int16Array: xe("%Int16ArrayPrototype%"),
- Int32Array: xe("%Int32ArrayPrototype%"),
- Int8Array: xe("%Int8ArrayPrototype%"),
- Uint16Array: xe("%Uint16ArrayPrototype%"),
- Uint32Array: xe("%Uint32ArrayPrototype%"),
- Uint8ClampedArray: xe("%Uint8ClampedArrayPrototype%"),
- Uint8Array: {
- ...xe("%Uint8ArrayPrototype%"),
- // https://github.com/tc39/proposal-arraybuffer-base64
- fromBase64: a,
- // https://github.com/tc39/proposal-arraybuffer-base64
- fromHex: a
- },
- "%BigInt64ArrayPrototype%": Se("BigInt64Array"),
- "%BigUint64ArrayPrototype%": Se("BigUint64Array"),
- // https://github.com/tc39/proposal-float16array
- "%Float16ArrayPrototype%": Se("Float16Array"),
- "%Float32ArrayPrototype%": Se("Float32Array"),
- "%Float64ArrayPrototype%": Se("Float64Array"),
- "%Int16ArrayPrototype%": Se("Int16Array"),
- "%Int32ArrayPrototype%": Se("Int32Array"),
- "%Int8ArrayPrototype%": Se("Int8Array"),
- "%Uint16ArrayPrototype%": Se("Uint16Array"),
- "%Uint32ArrayPrototype%": Se("Uint32Array"),
- "%Uint8ClampedArrayPrototype%": Se("Uint8ClampedArray"),
- "%Uint8ArrayPrototype%": {
- ...Se("Uint8Array"),
- // https://github.com/tc39/proposal-arraybuffer-base64
- setFromBase64: a,
- // https://github.com/tc39/proposal-arraybuffer-base64
- setFromHex: a,
- // https://github.com/tc39/proposal-arraybuffer-base64
- toBase64: a,
- // https://github.com/tc39/proposal-arraybuffer-base64
- toHex: a
- },
- // *** Keyed Collections
- Map: {
- // Properties of the Map Constructor
- "[[Proto]]": "%FunctionPrototype%",
- "@@species": M,
- prototype: "%MapPrototype%",
- // https://github.com/tc39/proposal-array-grouping
- groupBy: a
- },
- "%MapPrototype%": {
- clear: a,
- constructor: "Map",
- delete: a,
- entries: a,
- forEach: a,
- get: a,
- has: a,
- keys: a,
- set: a,
- size: M,
- values: a,
- "@@iterator": a,
- "@@toStringTag": "string"
- },
- "%MapIteratorPrototype%": {
- // The %MapIteratorPrototype% Object
- "[[Proto]]": "%IteratorPrototype%",
- next: a,
- "@@toStringTag": "string"
- },
- Set: {
- // Properties of the Set Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%SetPrototype%",
- "@@species": M,
- // Seen on QuickJS
- groupBy: !1
- },
- "%SetPrototype%": {
- add: a,
- clear: a,
- constructor: "Set",
- delete: a,
- entries: a,
- forEach: a,
- has: a,
- keys: a,
- size: M,
- values: a,
- "@@iterator": a,
- "@@toStringTag": "string",
- // See https://github.com/tc39/proposal-set-methods
- intersection: a,
- // See https://github.com/tc39/proposal-set-methods
- union: a,
- // See https://github.com/tc39/proposal-set-methods
- difference: a,
- // See https://github.com/tc39/proposal-set-methods
- symmetricDifference: a,
- // See https://github.com/tc39/proposal-set-methods
- isSubsetOf: a,
- // See https://github.com/tc39/proposal-set-methods
- isSupersetOf: a,
- // See https://github.com/tc39/proposal-set-methods
- isDisjointFrom: a
- },
- "%SetIteratorPrototype%": {
- // The %SetIteratorPrototype% Object
- "[[Proto]]": "%IteratorPrototype%",
- next: a,
- "@@toStringTag": "string"
- },
- WeakMap: {
- // Properties of the WeakMap Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%WeakMapPrototype%"
- },
- "%WeakMapPrototype%": {
- constructor: "WeakMap",
- delete: a,
- get: a,
- has: a,
- set: a,
- "@@toStringTag": "string"
- },
- WeakSet: {
- // Properties of the WeakSet Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%WeakSetPrototype%"
- },
- "%WeakSetPrototype%": {
- add: a,
- constructor: "WeakSet",
- delete: a,
- has: a,
- "@@toStringTag": "string"
- },
- // *** Structured Data
- ArrayBuffer: {
- // Properties of the ArrayBuffer Constructor
- "[[Proto]]": "%FunctionPrototype%",
- isView: a,
- prototype: "%ArrayBufferPrototype%",
- "@@species": M,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- fromString: !1,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- fromBigInt: !1
- },
- "%ArrayBufferPrototype%": {
- byteLength: M,
- constructor: "ArrayBuffer",
- slice: a,
- "@@toStringTag": "string",
- // See https://github.com/Moddable-OpenSource/moddable/issues/523
- concat: !1,
- // See https://github.com/tc39/proposal-resizablearraybuffer
- transfer: a,
- resize: a,
- resizable: M,
- maxByteLength: M,
- // https://github.com/tc39/proposal-arraybuffer-transfer
- transferToFixedLength: a,
- detached: M
- },
- // SharedArrayBuffer Objects
- SharedArrayBuffer: !1,
- // UNSAFE and purposely suppressed.
- "%SharedArrayBufferPrototype%": !1,
- // UNSAFE and purposely suppressed.
- DataView: {
- // Properties of the DataView Constructor
- "[[Proto]]": "%FunctionPrototype%",
- BYTES_PER_ELEMENT: "number",
- // Non std but undeletable on Safari.
- prototype: "%DataViewPrototype%"
- },
- "%DataViewPrototype%": {
- buffer: M,
- byteLength: M,
- byteOffset: M,
- constructor: "DataView",
- getBigInt64: a,
- getBigUint64: a,
- // https://github.com/tc39/proposal-float16array
- getFloat16: a,
- getFloat32: a,
- getFloat64: a,
- getInt8: a,
- getInt16: a,
- getInt32: a,
- getUint8: a,
- getUint16: a,
- getUint32: a,
- setBigInt64: a,
- setBigUint64: a,
- // https://github.com/tc39/proposal-float16array
- setFloat16: a,
- setFloat32: a,
- setFloat64: a,
- setInt8: a,
- setInt16: a,
- setInt32: a,
- setUint8: a,
- setUint16: a,
- setUint32: a,
- "@@toStringTag": "string"
- },
- // Atomics
- Atomics: !1,
- // UNSAFE and suppressed.
- JSON: {
- parse: a,
- stringify: a,
- "@@toStringTag": "string",
- // https://github.com/tc39/proposal-json-parse-with-source/
- rawJSON: a,
- isRawJSON: a
- },
- // *** Control Abstraction Objects
- // https://github.com/tc39/proposal-iterator-helpers
- Iterator: {
- // Properties of the Iterator Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%IteratorPrototype%",
- from: a
- },
- "%IteratorPrototype%": {
- // The %IteratorPrototype% Object
- "@@iterator": a,
- // https://github.com/tc39/proposal-iterator-helpers
- constructor: "Iterator",
- map: a,
- filter: a,
- take: a,
- drop: a,
- flatMap: a,
- reduce: a,
- toArray: a,
- forEach: a,
- some: a,
- every: a,
- find: a,
- "@@toStringTag": "string",
- // https://github.com/tc39/proposal-async-iterator-helpers
- toAsync: a,
- // See https://github.com/Moddable-OpenSource/moddable/issues/523#issuecomment-1942904505
- "@@dispose": !1
- },
- // https://github.com/tc39/proposal-iterator-helpers
- "%WrapForValidIteratorPrototype%": {
- "[[Proto]]": "%IteratorPrototype%",
- next: a,
- return: a
- },
- // https://github.com/tc39/proposal-iterator-helpers
- "%IteratorHelperPrototype%": {
- "[[Proto]]": "%IteratorPrototype%",
- next: a,
- return: a,
- "@@toStringTag": "string"
- },
- // https://github.com/tc39/proposal-async-iterator-helpers
- AsyncIterator: {
- // Properties of the Iterator Constructor
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%AsyncIteratorPrototype%",
- from: a
- },
- "%AsyncIteratorPrototype%": {
- // The %AsyncIteratorPrototype% Object
- "@@asyncIterator": a,
- // https://github.com/tc39/proposal-async-iterator-helpers
- constructor: "AsyncIterator",
- map: a,
- filter: a,
- take: a,
- drop: a,
- flatMap: a,
- reduce: a,
- toArray: a,
- forEach: a,
- some: a,
- every: a,
- find: a,
- "@@toStringTag": "string",
- // See https://github.com/Moddable-OpenSource/moddable/issues/523#issuecomment-1942904505
- "@@asyncDispose": !1
- },
- // https://github.com/tc39/proposal-async-iterator-helpers
- "%WrapForValidAsyncIteratorPrototype%": {
- "[[Proto]]": "%AsyncIteratorPrototype%",
- next: a,
- return: a
- },
- // https://github.com/tc39/proposal-async-iterator-helpers
- "%AsyncIteratorHelperPrototype%": {
- "[[Proto]]": "%AsyncIteratorPrototype%",
- next: a,
- return: a,
- "@@toStringTag": "string"
- },
- "%InertGeneratorFunction%": {
- // Properties of the GeneratorFunction Constructor
- "[[Proto]]": "%InertFunction%",
- prototype: "%Generator%"
- },
- "%Generator%": {
- // Properties of the GeneratorFunction Prototype Object
- "[[Proto]]": "%FunctionPrototype%",
- constructor: "%InertGeneratorFunction%",
- prototype: "%GeneratorPrototype%",
- "@@toStringTag": "string"
- },
- "%InertAsyncGeneratorFunction%": {
- // Properties of the AsyncGeneratorFunction Constructor
- "[[Proto]]": "%InertFunction%",
- prototype: "%AsyncGenerator%"
- },
- "%AsyncGenerator%": {
- // Properties of the AsyncGeneratorFunction Prototype Object
- "[[Proto]]": "%FunctionPrototype%",
- constructor: "%InertAsyncGeneratorFunction%",
- prototype: "%AsyncGeneratorPrototype%",
- // length prop added here for React Native jsc-android
- // https://github.com/endojs/endo/issues/660
- // https://github.com/react-native-community/jsc-android-buildscripts/issues/181
- length: "number",
- "@@toStringTag": "string"
- },
- "%GeneratorPrototype%": {
- // Properties of the Generator Prototype Object
- "[[Proto]]": "%IteratorPrototype%",
- constructor: "%Generator%",
- next: a,
- return: a,
- throw: a,
- "@@toStringTag": "string"
- },
- "%AsyncGeneratorPrototype%": {
- // Properties of the AsyncGenerator Prototype Object
- "[[Proto]]": "%AsyncIteratorPrototype%",
- constructor: "%AsyncGenerator%",
- next: a,
- return: a,
- throw: a,
- "@@toStringTag": "string"
- },
- // TODO: To be replaced with Promise.delegate
- //
- // The HandledPromise global variable shimmed by `@agoric/eventual-send/shim`
- // implements an initial version of the eventual send specification at:
- // https://github.com/tc39/proposal-eventual-send
- //
- // We will likely change this to add a property to Promise called
- // Promise.delegate and put static methods on it, which will necessitate
- // another permits change to update to the current proposed standard.
- HandledPromise: {
- "[[Proto]]": "Promise",
- applyFunction: a,
- applyFunctionSendOnly: a,
- applyMethod: a,
- applyMethodSendOnly: a,
- get: a,
- getSendOnly: a,
- prototype: "%PromisePrototype%",
- resolve: a
- },
- // https://github.com/tc39/proposal-source-phase-imports?tab=readme-ov-file#js-module-source
- ModuleSource: {
- "[[Proto]]": "%AbstractModuleSource%",
- prototype: "%ModuleSourcePrototype%"
- },
- "%ModuleSourcePrototype%": {
- "[[Proto]]": "%AbstractModuleSourcePrototype%",
- constructor: "ModuleSource",
- "@@toStringTag": "string",
- // https://github.com/tc39/proposal-compartments
- bindings: M,
- needsImport: M,
- needsImportMeta: M
- },
- "%AbstractModuleSource%": {
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%AbstractModuleSourcePrototype%"
- },
- "%AbstractModuleSourcePrototype%": {
- constructor: "%AbstractModuleSource%"
- },
- Promise: {
- // Properties of the Promise Constructor
- "[[Proto]]": "%FunctionPrototype%",
- all: a,
- allSettled: a,
- // https://github.com/Agoric/SES-shim/issues/550
- any: a,
- prototype: "%PromisePrototype%",
- race: a,
- reject: a,
- resolve: a,
- // https://github.com/tc39/proposal-promise-with-resolvers
- withResolvers: a,
- "@@species": M,
- // https://github.com/tc39/proposal-promise-try
- try: a
- },
- "%PromisePrototype%": {
- // Properties of the Promise Prototype Object
- catch: a,
- constructor: "Promise",
- finally: a,
- then: a,
- "@@toStringTag": "string",
- // Non-standard, used in node to prevent async_hooks from breaking
- "UniqueSymbol(async_id_symbol)": Le,
- "UniqueSymbol(trigger_async_id_symbol)": Le,
- "UniqueSymbol(destroyed)": Le
- },
- "%InertAsyncFunction%": {
- // Properties of the AsyncFunction Constructor
- "[[Proto]]": "%InertFunction%",
- prototype: "%AsyncFunctionPrototype%"
- },
- "%AsyncFunctionPrototype%": {
- // Properties of the AsyncFunction Prototype Object
- "[[Proto]]": "%FunctionPrototype%",
- constructor: "%InertAsyncFunction%",
- // length prop added here for React Native jsc-android
- // https://github.com/endojs/endo/issues/660
- // https://github.com/react-native-community/jsc-android-buildscripts/issues/181
- length: "number",
- "@@toStringTag": "string"
- },
- // Reflection
- Reflect: {
- // The Reflect Object
- // Not a function object.
- apply: a,
- construct: a,
- defineProperty: a,
- deleteProperty: a,
- get: a,
- getOwnPropertyDescriptor: a,
- getPrototypeOf: a,
- has: a,
- isExtensible: a,
- ownKeys: a,
- preventExtensions: a,
- set: a,
- setPrototypeOf: a,
- "@@toStringTag": "string"
- },
- Proxy: {
- // Properties of the Proxy Constructor
- "[[Proto]]": "%FunctionPrototype%",
- revocable: a
- },
- // Appendix B
- // Annex B: Additional Properties of the Global Object
- escape: a,
- unescape: a,
- // Proposed
- "%UniqueCompartment%": {
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%CompartmentPrototype%",
- toString: a
- },
- "%InertCompartment%": {
- "[[Proto]]": "%FunctionPrototype%",
- prototype: "%CompartmentPrototype%",
- toString: a
- },
- "%CompartmentPrototype%": {
- constructor: "%InertCompartment%",
- evaluate: a,
- globalThis: M,
- name: M,
- import: no,
- load: no,
- importNow: a,
- module: a,
- "@@toStringTag": "string"
- },
- lockdown: a,
- harden: { ...a, isFake: "boolean" },
- "%InitialGetStackString%": a
-}, si = (t) => typeof t == "function";
-function ai(t, e, r) {
- if (Q(t, e)) {
- const n = ee(t, e);
- if (!n || !Zr(n.value, r.value) || n.get !== r.get || n.set !== r.set || n.writable !== r.writable || n.enumerable !== r.enumerable || n.configurable !== r.configurable)
- throw _(`Conflicting definitions of ${e}`);
- }
- D(t, e, r);
-}
-function ii(t, e) {
- for (const [r, n] of me(e))
- ai(t, r, n);
-}
-function hs(t, e) {
- const r = { __proto__: null };
- for (const [n, o] of me(e))
- Q(t, n) && (r[o] = t[n]);
- return r;
-}
-const ms = (t) => {
- const e = H(null);
- let r;
- const n = (l) => {
- ii(e, Be(l));
- };
- y(n);
- const o = () => {
- for (const [l, u] of me(e)) {
- if (!ke(u) || !Q(u, "prototype"))
- continue;
- const d = Cr[l];
- if (typeof d != "object")
- throw _(`Expected permit object at permits.${l}`);
- const f = d.prototype;
- if (!f) {
- ls(
- u,
- "prototype",
- !1,
- `${l}.prototype`,
- t
- );
- continue;
- }
- if (typeof f != "string" || !Q(Cr, f))
- throw _(`Unrecognized ${l}.prototype permits entry`);
- const h = u.prototype;
- if (Q(e, f)) {
- if (e[f] !== h)
- throw _(`Conflicting bindings of ${f}`);
- continue;
- }
- e[f] = h;
- }
- };
- y(o);
- const s = () => (y(e), r = new Ut(et(Ro(e), si)), e);
- y(s);
- const i = (l) => {
- if (!r)
- throw _(
- "isPseudoNative can only be called after finalIntrinsics"
- );
- return lr(r, l);
- };
- y(i);
- const c = {
- addIntrinsics: n,
- completePrototypes: o,
- finalIntrinsics: s,
- isPseudoNative: i
- };
- return y(c), n(us), n(hs(S, ds)), c;
-}, ci = (t, e) => {
- const { addIntrinsics: r, finalIntrinsics: n } = ms(e);
- return r(hs(t, fs)), n();
-};
-function li(t, e, r) {
- const n = ["undefined", "boolean", "number", "string", "symbol"], o = new $e(
- At ? de(
- et(
- me(Cr["%SharedSymbol%"]),
- ([f, h]) => h === "symbol" && typeof At[f] == "symbol"
- ),
- ([f]) => [At[f], `@@${f}`]
- ) : []
- );
- function s(f, h) {
- if (typeof h == "string")
- return h;
- const p = Ke(o, h);
- if (typeof h == "symbol") {
- if (p)
- return p;
- {
- const m = ha(h);
- return m !== void 0 ? `RegisteredSymbol(${m})` : `Unique${_e(h)}`;
- }
- }
- throw _(`Unexpected property name type ${f} ${h}`);
- }
- function i(f, h, p) {
- if (!ke(h))
- throw _(`Object expected: ${f}, ${h}, ${p}`);
- const m = G(h);
- if (!(m === null && p === null)) {
- if (p !== void 0 && typeof p != "string")
- throw _(`Malformed permit ${f}.__proto__`);
- if (m !== t[p || "%ObjectPrototype%"])
- throw _(
- `Unexpected [[Prototype]] at ${f}.__proto__ (expected ${p || "%ObjectPrototype%"})`
- );
- }
- }
- function c(f, h, p, m) {
- if (typeof m == "object")
- return d(f, h, m), !0;
- if (m === !1)
- return !1;
- if (typeof m == "string") {
- if (p === "prototype" || p === "constructor") {
- if (Q(t, m)) {
- if (h !== t[m])
- throw _(`Does not match permit for ${f}`);
- return !0;
- }
- } else if (Hr(n, m)) {
- if (typeof h !== m)
- throw _(
- `At ${f} expected ${m} not ${typeof h}`
- );
- return !0;
- }
- }
- throw _(
- `Unexpected property ${p} with permit ${m} at ${f}`
- );
- }
- function l(f, h, p, m) {
- const k = ee(h, p);
- if (!k)
- throw _(`Property ${p} not found at ${f}`);
- if (Q(k, "value")) {
- if (oo(m))
- throw _(`Accessor expected at ${f}`);
- return c(f, k.value, p, m);
- }
- if (!oo(m))
- throw _(`Accessor not expected at ${f}`);
- return c(`${f}`, k.get, p, m.get) && c(`${f}`, k.set, p, m.set);
- }
- function u(f, h, p) {
- const m = p === "__proto__" ? "--proto--" : p;
- if (Q(h, m))
- return h[m];
- if (typeof f == "function" && Q(vn, m))
- return vn[m];
- }
- function d(f, h, p) {
- if (h == null)
- return;
- const m = p["[[Proto]]"];
- i(f, h, m), typeof h == "function" && e(h);
- for (const k of qe(h)) {
- const x = s(f, k), w = `${f}.${x}`, R = u(h, p, x);
- (!R || !l(w, h, k, R)) && ls(h, k, R === !1, w, r);
- }
- }
- d("intrinsics", t, Cr);
-}
-function ui() {
- try {
- Ee.prototype.constructor("return 1");
- } catch {
- return y({});
- }
- const t = {};
- function e(r, n, o) {
- let s;
- try {
- s = (0, eval)(o);
- } catch (l) {
- if (l instanceof ir)
- return;
- throw l;
- }
- const i = G(s), c = function() {
- throw _(
- "Function.prototype.constructor is not a valid constructor."
- );
- };
- B(c, {
- prototype: { value: i },
- name: {
- value: r,
- writable: !1,
- enumerable: !1,
- configurable: !0
- }
- }), B(i, {
- constructor: { value: c }
- }), c !== Ee.prototype.constructor && wr(c, Ee.prototype.constructor), t[n] = c;
- }
- return e("Function", "%InertFunction%", "(function(){})"), e(
- "GeneratorFunction",
- "%InertGeneratorFunction%",
- "(function*(){})"
- ), e(
- "AsyncFunction",
- "%InertAsyncFunction%",
- "(async function(){})"
- ), e(
- "AsyncGeneratorFunction",
- "%InertAsyncGeneratorFunction%",
- "(async function*(){})"
- ), t;
-}
-function di(t = "safe") {
- if (t !== "safe" && t !== "unsafe")
- throw _(`unrecognized dateTaming ${t}`);
- const e = sa, r = e.prototype, n = {
- /**
- * `%SharedDate%.now()` throw a `TypeError` starting with "secure mode".
- * See https://github.com/endojs/endo/issues/910#issuecomment-1581855420
- */
- now() {
- throw _("secure mode Calling %SharedDate%.now() throws");
- }
- }, o = ({ powers: c = "none" } = {}) => {
- let l;
- return c === "original" ? l = function(...d) {
- return new.target === void 0 ? ue(e, void 0, d) : xr(e, d, new.target);
- } : l = function(...d) {
- if (new.target === void 0)
- throw _(
- "secure mode Calling %SharedDate% constructor as a function throws"
- );
- if (d.length === 0)
- throw _(
- "secure mode Calling new %SharedDate%() with no arguments throws"
- );
- return xr(e, d, new.target);
- }, B(l, {
- length: { value: 7 },
- prototype: {
- value: r,
- writable: !1,
- enumerable: !1,
- configurable: !1
- },
- parse: {
- value: e.parse,
- writable: !0,
- enumerable: !1,
- configurable: !0
- },
- UTC: {
- value: e.UTC,
- writable: !0,
- enumerable: !1,
- configurable: !0
- }
- }), l;
- }, s = o({ powers: "original" }), i = o({ powers: "none" });
- return B(s, {
- now: {
- value: e.now,
- writable: !0,
- enumerable: !1,
- configurable: !0
- }
- }), B(i, {
- now: {
- value: n.now,
- writable: !0,
- enumerable: !1,
- configurable: !0
- }
- }), B(r, {
- constructor: { value: i }
- }), {
- "%InitialDate%": s,
- "%SharedDate%": i
- };
-}
-function fi(t = "safe") {
- if (t !== "safe" && t !== "unsafe")
- throw _(`unrecognized mathTaming ${t}`);
- const e = ca, r = e, { random: n, ...o } = Be(e), i = H(zr, {
- ...o,
- random: {
- value: {
- /**
- * `%SharedMath%.random()` throws a TypeError starting with "secure mode".
- * See https://github.com/endojs/endo/issues/910#issuecomment-1581855420
- */
- random() {
- throw _("secure mode %SharedMath%.random() throws");
- }
- }.random,
- writable: !0,
- enumerable: !1,
- configurable: !0
- }
- });
- return {
- "%InitialMath%": r,
- "%SharedMath%": i
- };
-}
-function pi(t = "safe") {
- if (t !== "safe" && t !== "unsafe")
- throw _(`unrecognized regExpTaming ${t}`);
- const e = Xe.prototype, r = (s = {}) => {
- const i = function(...l) {
- return new.target === void 0 ? Xe(...l) : xr(Xe, l, new.target);
- };
- if (B(i, {
- length: { value: 2 },
- prototype: {
- value: e,
- writable: !1,
- enumerable: !1,
- configurable: !1
- }
- }), nn) {
- const c = ee(
- Xe,
- nn
- );
- if (!c)
- throw _("no RegExp[Symbol.species] descriptor");
- B(i, {
- [nn]: c
- });
- }
- return i;
- }, n = r(), o = r();
- return t !== "unsafe" && delete e.compile, B(e, {
- constructor: { value: o }
- }), {
- "%InitialRegExp%": n,
- "%SharedRegExp%": o
- };
-}
-const hi = {
- "%ObjectPrototype%": {
- toString: !0
- },
- "%FunctionPrototype%": {
- toString: !0
- // set by "rollup"
- },
- "%ErrorPrototype%": {
- name: !0
- // set by "precond", "ava", "node-fetch"
- },
- "%IteratorPrototype%": {
- toString: !0,
- // https://github.com/tc39/proposal-iterator-helpers
- constructor: !0,
- // https://github.com/tc39/proposal-iterator-helpers
- [Qe]: !0
- }
-}, gs = {
- "%ObjectPrototype%": {
- toString: !0,
- valueOf: !0
- },
- "%ArrayPrototype%": {
- toString: !0,
- push: !0,
- // set by "Google Analytics"
- concat: !0,
- // set by mobx generated code (old TS compiler?)
- [De]: !0
- // set by mobx generated code (old TS compiler?)
- },
- // Function.prototype has no 'prototype' property to enable.
- // Function instances have their own 'name' and 'length' properties
- // which are configurable and non-writable. Thus, they are already
- // non-assignable anyway.
- "%FunctionPrototype%": {
- constructor: !0,
- // set by "regenerator-runtime"
- bind: !0,
- // set by "underscore", "express"
- toString: !0
- // set by "rollup"
- },
- "%ErrorPrototype%": {
- constructor: !0,
- // set by "fast-json-patch", "node-fetch"
- message: !0,
- name: !0,
- // set by "precond", "ava", "node-fetch", "node 14"
- toString: !0
- // set by "bluebird"
- },
- "%TypeErrorPrototype%": {
- constructor: !0,
- // set by "readable-stream"
- message: !0,
- // set by "tape"
- name: !0
- // set by "readable-stream", "node 14"
- },
- "%SyntaxErrorPrototype%": {
- message: !0,
- // to match TypeErrorPrototype.message
- name: !0
- // set by "node 14"
- },
- "%RangeErrorPrototype%": {
- message: !0,
- // to match TypeErrorPrototype.message
- name: !0
- // set by "node 14"
- },
- "%URIErrorPrototype%": {
- message: !0,
- // to match TypeErrorPrototype.message
- name: !0
- // set by "node 14"
- },
- "%EvalErrorPrototype%": {
- message: !0,
- // to match TypeErrorPrototype.message
- name: !0
- // set by "node 14"
- },
- "%ReferenceErrorPrototype%": {
- message: !0,
- // to match TypeErrorPrototype.message
- name: !0
- // set by "node 14"
- },
- // https://github.com/endojs/endo/issues/550
- "%AggregateErrorPrototype%": {
- message: !0,
- // to match TypeErrorPrototype.message
- name: !0
- // set by "node 14"?
- },
- "%PromisePrototype%": {
- constructor: !0
- // set by "core-js"
- },
- "%TypedArrayPrototype%": "*",
- // set by https://github.com/feross/buffer
- "%Generator%": {
- constructor: !0,
- name: !0,
- toString: !0
- },
- "%IteratorPrototype%": {
- toString: !0,
- // https://github.com/tc39/proposal-iterator-helpers
- constructor: !0,
- // https://github.com/tc39/proposal-iterator-helpers
- [Qe]: !0
- }
-}, mi = {
- ...gs,
- /**
- * Rollup (as used at least by vega) and webpack
- * (as used at least by regenerator) both turn exports into assignments
- * to a big `exports` object that inherits directly from
- * `Object.prototype`. Some of the exported names we've seen include
- * `hasOwnProperty`, `constructor`, and `toString`. But the strategy used
- * by rollup and webpack potentionally turns any exported name
- * into an assignment rejected by the override mistake. That's why
- * the `severe` enablements takes the extreme step of enabling
- * everything on `Object.prototype`.
- *
- * In addition, code doing inheritance manually will often override
- * the `constructor` property on the new prototype by assignment. We've
- * seen this several times.
- *
- * The cost of enabling all these is that they create a miserable debugging
- * experience specifically on Node.
- * https://github.com/Agoric/agoric-sdk/issues/2324
- * explains how it confused the Node console.
- *
- * (TODO Reexamine the vscode situation. I think it may have improved
- * since the following paragraph was written.)
- *
- * The vscode debugger's object inspector shows the own data properties of
- * an object, which is typically what you want, but also shows both getter
- * and setter for every accessor property whether inherited or own.
- * With the `'*'` setting here, all the properties inherited from
- * `Object.prototype` are accessors, creating an unusable display as seen
- * at As explained at
- * https://github.com/endojs/endo/blob/master/packages/ses/docs/lockdown.md#overridetaming-options
- * Open the triangles at the bottom of that section.
- */
- "%ObjectPrototype%": "*",
- /**
- * The widely used Buffer defined at https://github.com/feross/buffer
- * on initialization, manually creates the equivalent of a subclass of
- * `TypedArray`, which it then initializes by assignment. These assignments
- * include enough of the `TypeArray` methods that here, the `severe`
- * enablements just enable them all.
- */
- "%TypedArrayPrototype%": "*",
- /**
- * Needed to work with Immer before https://github.com/immerjs/immer/pull/914
- * is accepted.
- */
- "%MapPrototype%": "*",
- /**
- * Needed to work with Immer before https://github.com/immerjs/immer/pull/914
- * is accepted.
- */
- "%SetPrototype%": "*"
-};
-function gi(t, e, { warn: r }, n = []) {
- const o = new Dt(n);
- function s(d, f, h, p) {
- if ("value" in p && p.configurable) {
- const { value: m } = p, k = Cn(o, h), { get: x, set: w } = ee(
- {
- get [h]() {
- return m;
- },
- set [h](R) {
- if (f === this)
- throw _(
- `Cannot assign to read only property '${_e(
- h
- )}' of '${d}'`
- );
- Q(this, h) ? this[h] = R : (k && r(_(`Override property ${h}`)), D(this, h, {
- value: R,
- writable: !0,
- enumerable: !0,
- configurable: !0
- }));
- }
- },
- h
- );
- D(x, "originalValue", {
- value: m,
- writable: !1,
- enumerable: !1,
- configurable: !1
- }), D(f, h, {
- get: x,
- set: w,
- enumerable: p.enumerable,
- configurable: p.configurable
- });
- }
- }
- function i(d, f, h) {
- const p = ee(f, h);
- p && s(d, f, h, p);
- }
- function c(d, f) {
- const h = Be(f);
- h && ht(qe(h), (p) => s(d, f, p, h[p]));
- }
- function l(d, f, h) {
- for (const p of qe(h)) {
- const m = ee(f, p);
- if (!m || m.get || m.set)
- continue;
- const k = `${d}.${_e(p)}`, x = h[p];
- if (x === !0)
- i(k, f, p);
- else if (x === "*")
- c(k, m.value);
- else if (ke(x))
- l(k, m.value, x);
- else
- throw _(`Unexpected override enablement plan ${k}`);
- }
- }
- let u;
- switch (e) {
- case "min": {
- u = hi;
- break;
- }
- case "moderate": {
- u = gs;
- break;
- }
- case "severe": {
- u = mi;
- break;
- }
- default:
- throw _(`unrecognized overrideTaming ${e}`);
- }
- l("root", t, u);
-}
-const { Fail: bn, quote: Rr } = Y, yi = /^(\w*[a-z])Locale([A-Z]\w*)$/, ys = {
- // See https://tc39.es/ecma262/#sec-string.prototype.localecompare
- localeCompare(t) {
- if (this === null || this === void 0)
- throw _(
- 'Cannot localeCompare with null or undefined "this" value'
- );
- const e = `${this}`, r = `${t}`;
- return e < r ? -1 : e > r ? 1 : (e === r || bn`expected ${Rr(e)} and ${Rr(r)} to compare`, 0);
- },
- toString() {
- return `${this}`;
- }
-}, _i = ys.localeCompare, vi = ys.toString;
-function bi(t, e = "safe") {
- if (e !== "safe" && e !== "unsafe")
- throw _(`unrecognized localeTaming ${e}`);
- if (e !== "unsafe") {
- D(_e.prototype, "localeCompare", {
- value: _i
- });
- for (const r of Nt(t)) {
- const n = t[r];
- if (ke(n))
- for (const o of Nt(n)) {
- const s = $n(yi, o);
- if (s) {
- typeof n[o] == "function" || bn`expected ${Rr(o)} to be a function`;
- const i = `${s[1]}${s[2]}`, c = n[i];
- typeof c == "function" || bn`function ${Rr(i)} not found`, D(n, o, { value: c });
- }
- }
- }
- D(Io.prototype, "toLocaleString", {
- value: vi
- });
- }
-}
-const wi = (t) => ({
- eval(r) {
- return typeof r != "string" ? r : t(r);
- }
-}).eval, { Fail: ao } = Y, xi = (t) => {
- const e = function(n) {
- const o = `${Er(arguments) || ""}`, s = `${Zt(arguments, ",")}`;
- new Ee(s, ""), new Ee(o);
- const i = `(function anonymous(${s}
-) {
-${o}
-})`;
- return t(i);
- };
- return B(e, {
- // Ensure that any function created in any evaluator in a realm is an
- // instance of Function in any evaluator of the same realm.
- prototype: {
- value: Ee.prototype,
- writable: !1,
- enumerable: !1,
- configurable: !1
- }
- }), G(Ee) === Ee.prototype || ao`Function prototype is the same accross compartments`, G(e) === Ee.prototype || ao`Function constructor prototype is the same accross compartments`, e;
-}, Si = (t) => {
- D(
- t,
- pa,
- y(
- jr(H(null), {
- set: y(() => {
- throw _(
- "Cannot set Symbol.unscopables of a Compartment's globalThis"
- );
- }),
- enumerable: !1,
- configurable: !1
- })
- )
- );
-}, _s = (t) => {
- for (const [e, r] of me(us))
- D(t, e, {
- value: r,
- writable: !1,
- enumerable: !1,
- configurable: !1
- });
-}, vs = (t, {
- intrinsics: e,
- newGlobalPropertyNames: r,
- makeCompartmentConstructor: n,
- markVirtualizedNativeFunction: o,
- parentCompartment: s
-}) => {
- for (const [c, l] of me(ds))
- Q(e, l) && D(t, c, {
- value: e[l],
- writable: !0,
- enumerable: !1,
- configurable: !0
- });
- for (const [c, l] of me(r))
- Q(e, l) && D(t, c, {
- value: e[l],
- writable: !0,
- enumerable: !1,
- configurable: !0
- });
- const i = {
- globalThis: t
- };
- i.Compartment = y(
- n(
- n,
- e,
- o,
- s
- )
- );
- for (const [c, l] of me(i))
- D(t, c, {
- value: l,
- writable: !0,
- enumerable: !1,
- configurable: !0
- }), typeof l == "function" && o(l);
-}, wn = (t, e, r) => {
- {
- const n = y(wi(e));
- r(n), D(t, "eval", {
- value: n,
- writable: !0,
- enumerable: !1,
- configurable: !0
- });
- }
- {
- const n = y(xi(e));
- r(n), D(t, "Function", {
- value: n,
- writable: !0,
- enumerable: !1,
- configurable: !0
- });
- }
-}, { Fail: Ei, quote: bs } = Y, ws = new Ur(
- Mn,
- y({
- get(t, e) {
- Ei`Please report unexpected scope handler trap: ${bs(_e(e))}`;
- }
- })
-), ki = {
- get(t, e) {
- },
- set(t, e, r) {
- throw Wt(`${_e(e)} is not defined`);
- },
- has(t, e) {
- return e in S;
- },
- // note: this is likely a bug of safari
- // https://bugs.webkit.org/show_bug.cgi?id=195534
- getPrototypeOf(t) {
- return null;
- },
- // See https://github.com/endojs/endo/issues/1510
- // TODO: report as bug to v8 or Chrome, and record issue link here.
- getOwnPropertyDescriptor(t, e) {
- const r = bs(_e(e));
- console.warn(
- `getOwnPropertyDescriptor trap on scopeTerminatorHandler for ${r}`,
- _().stack
- );
- },
- // See https://github.com/endojs/endo/issues/1490
- // TODO Report bug to JSC or Safari
- ownKeys(t) {
- return [];
- }
-}, xs = y(
- H(
- ws,
- Be(ki)
- )
-), Pi = new Ur(
- Mn,
- xs
-), Ss = (t) => {
- const e = {
- // inherit scopeTerminator behavior
- ...xs,
- // Redirect set properties to the globalObject.
- set(o, s, i) {
- return Mo(t, s, i);
- },
- // Always claim to have a potential property in order to be the recipient of a set
- has(o, s) {
- return !0;
- }
- }, r = y(
- H(
- ws,
- Be(e)
- )
- );
- return new Ur(
- Mn,
- r
- );
-};
-y(Ss);
-const { Fail: Ai } = Y, Ti = () => {
- const t = H(null), e = y({
- eval: {
- get() {
- return delete t.eval, Ko;
- },
- enumerable: !1,
- configurable: !0
- }
- }), r = {
- evalScope: t,
- allowNextEvalToBeUnsafe() {
- const { revoked: n } = r;
- n !== null && Ai`a handler did not reset allowNextEvalToBeUnsafe ${n.err}`, B(t, e);
- },
- /** @type {null | { err: any }} */
- revoked: null
- };
- return r;
-}, io = "\\s*[@#]\\s*([a-zA-Z][a-zA-Z0-9]*)\\s*=\\s*([^\\s\\*]*)", Ii = new Xe(
- `(?:\\s*//${io}|/\\*${io}\\s*\\*/)\\s*$`
-), Fn = (t) => {
- let e = "";
- for (; t.length > 0; ) {
- const r = $n(Ii, t);
- if (r === null)
- break;
- t = Nn(t, 0, t.length - r[0].length), r[3] === "sourceURL" ? e = r[4] : r[1] === "sourceURL" && (e = r[2]);
- }
- return e;
-};
-function Dn(t, e) {
- const r = Na(t, e);
- if (r < 0)
- return -1;
- const n = t[r] === `
-` ? 1 : 0;
- return On(Nn(t, 0, r), `
-`).length + n;
-}
-const Es = new Xe("(?:)", "g"), ks = (t) => {
- const e = Dn(t, Es);
- if (e < 0)
- return t;
- const r = Fn(t);
- throw ir(
- `Possible HTML comment rejected at ${r}:${e}. (SES_HTML_COMMENT_REJECTED)`
- );
-}, Ps = (t) => Pr(t, Es, (r) => r[0] === "<" ? "< ! --" : "-- >"), As = new Xe(
- "(^|[^.]|\\.\\.\\.)\\bimport(\\s*(?:\\(|/[/*]))",
- "g"
-), Ts = (t) => {
- const e = Dn(t, As);
- if (e < 0)
- return t;
- const r = Fn(t);
- throw ir(
- `Possible import expression rejected at ${r}:${e}. (SES_IMPORT_REJECTED)`
- );
-}, Is = (t) => Pr(t, As, (r, n, o) => `${n}__import__${o}`), Ci = new Xe(
- "(^|[^.])\\beval(\\s*\\()",
- "g"
-), Cs = (t) => {
- const e = Dn(t, Ci);
- if (e < 0)
- return t;
- const r = Fn(t);
- throw ir(
- `Possible direct eval expression rejected at ${r}:${e}. (SES_EVAL_REJECTED)`
- );
-}, Rs = (t) => (t = ks(t), t = Ts(t), t), $s = (t, e) => {
- for (const r of e)
- t = r(t);
- return t;
-};
-y({
- rejectHtmlComments: y(ks),
- evadeHtmlCommentTest: y(Ps),
- rejectImportExpressions: y(Ts),
- evadeImportExpressionTest: y(Is),
- rejectSomeDirectEvalExpressions: y(Cs),
- mandatoryTransforms: y(Rs),
- applyTransforms: y($s)
-});
-const Ri = [
- // 11.6.2.1 Keywords
- "await",
- "break",
- "case",
- "catch",
- "class",
- "const",
- "continue",
- "debugger",
- "default",
- "delete",
- "do",
- "else",
- "export",
- "extends",
- "finally",
- "for",
- "function",
- "if",
- "import",
- "in",
- "instanceof",
- "new",
- "return",
- "super",
- "switch",
- "this",
- "throw",
- "try",
- "typeof",
- "var",
- "void",
- "while",
- "with",
- "yield",
- // Also reserved when parsing strict mode code
- "let",
- "static",
- // 11.6.2.2 Future Reserved Words
- "enum",
- // Also reserved when parsing strict mode code
- "implements",
- "package",
- "protected",
- "interface",
- "private",
- "public",
- // Reserved but not mentioned in specs
- "await",
- "null",
- "true",
- "false",
- "this",
- "arguments"
-], $i = /^[a-zA-Z_$][\w$]*$/, co = (t) => t !== "eval" && !Hr(Ri, t) && Rn($i, t);
-function lo(t, e) {
- const r = ee(t, e);
- return r && //
- // The getters will not have .writable, don't let the falsyness of
- // 'undefined' trick us: test with === false, not ! . However descriptors
- // inherit from the (potentially poisoned) global object, so we might see
- // extra properties which weren't really there. Accessor properties have
- // 'get/set/enumerable/configurable', while data properties have
- // 'value/writable/enumerable/configurable'.
- r.configurable === !1 && r.writable === !1 && //
- // Checks for data properties because they're the only ones we can
- // optimize (accessors are most likely non-constant). Descriptors can't
- // can't have accessors and value properties at the same time, therefore
- // this check is sufficient. Using explicit own property deal with the
- // case where Object.prototype has been poisoned.
- Q(r, "value");
-}
-const Ni = (t, e = {}) => {
- const r = Nt(t), n = Nt(e), o = et(
- n,
- (i) => co(i) && lo(e, i)
- );
- return {
- globalObjectConstants: et(
- r,
- (i) => (
- // Can't define a constant: it would prevent a
- // lookup on the endowments.
- !Hr(n, i) && co(i) && lo(t, i)
- )
- ),
- moduleLexicalConstants: o
- };
-};
-function uo(t, e) {
- return t.length === 0 ? "" : `const {${Zt(t, ",")}} = this.${e};`;
-}
-const Oi = (t) => {
- const { globalObjectConstants: e, moduleLexicalConstants: r } = Ni(
- t.globalObject,
- t.moduleLexicals
- ), n = uo(
- e,
- "globalObject"
- ), o = uo(
- r,
- "moduleLexicals"
- ), s = Ee(`
- with (this.scopeTerminator) {
- with (this.globalObject) {
- with (this.moduleLexicals) {
- with (this.evalScope) {
- ${n}
- ${o}
- return function() {
- 'use strict';
- return eval(arguments[0]);
- };
- }
- }
- }
- }
- `);
- return ue(s, t, []);
-}, { Fail: Mi } = Y, Un = ({
- globalObject: t,
- moduleLexicals: e = {},
- globalTransforms: r = [],
- sloppyGlobalsMode: n = !1
-}) => {
- const o = n ? Ss(t) : Pi, s = Ti(), { evalScope: i } = s, c = y({
- evalScope: i,
- moduleLexicals: e,
- globalObject: t,
- scopeTerminator: o
- });
- let l;
- const u = () => {
- l || (l = Oi(c));
- };
- return { safeEvaluate: (f, h) => {
- const { localTransforms: p = [] } = h || {};
- u(), f = $s(f, [
- ...p,
- ...r,
- Rs
- ]);
- let m;
- try {
- return s.allowNextEvalToBeUnsafe(), ue(l, t, [f]);
- } catch (k) {
- throw m = k, k;
- } finally {
- const k = "eval" in i;
- delete i.eval, k && (s.revoked = { err: m }, Mi`handler did not reset allowNextEvalToBeUnsafe ${m}`);
- }
- } };
-}, Li = ") { [native code] }";
-let ln;
-const Ns = () => {
- if (ln === void 0) {
- const t = new Ut();
- D(Vr, "toString", {
- value: {
- toString() {
- const r = La(this);
- return Bo(r, Li) || !lr(t, this) ? r : `function ${this.name}() { [native code] }`;
- }
- }.toString
- }), ln = y(
- (r) => qr(t, r)
- );
- }
- return ln;
-};
-function Fi(t = "safe") {
- if (t !== "safe" && t !== "unsafe")
- throw _(`unrecognized domainTaming ${t}`);
- if (t === "unsafe")
- return;
- const e = S.process || void 0;
- if (typeof e == "object") {
- const r = ee(e, "domain");
- if (r !== void 0 && r.get !== void 0)
- throw _(
- "SES failed to lockdown, Node.js domains have been initialized (SES_NO_DOMAINS)"
- );
- D(e, "domain", {
- value: null,
- configurable: !1,
- writable: !1,
- enumerable: !1
- });
- }
-}
-const Di = () => {
- const t = {}, e = S.ModuleSource;
- if (e !== void 0) {
- let n = function() {
- };
- var r = n;
- t.ModuleSource = e;
- const o = G(e);
- o === Vr ? (wr(e, n), t["%AbstractModuleSource%"] = n, t["%AbstractModuleSourcePrototype%"] = n.prototype) : (t["%AbstractModuleSource%"] = o, t["%AbstractModuleSourcePrototype%"] = o.prototype);
- const s = e.prototype;
- s !== void 0 && (t["%ModuleSourcePrototype%"] = s, G(s) === zr && wr(e.prototype, n.prototype));
- }
- return t;
-}, jn = y([
- ["debug", "debug"],
- // (fmt?, ...args) verbose level on Chrome
- ["log", "log"],
- // (fmt?, ...args) info level on Chrome
- ["info", "info"],
- // (fmt?, ...args)
- ["warn", "warn"],
- // (fmt?, ...args)
- ["error", "error"],
- // (fmt?, ...args)
- ["trace", "log"],
- // (fmt?, ...args)
- ["dirxml", "log"],
- // (fmt?, ...args) but TS typed (...data)
- ["group", "log"],
- // (fmt?, ...args) but TS typed (...label)
- ["groupCollapsed", "log"]
- // (fmt?, ...args) but TS typed (...label)
-]), Zn = y([
- ["assert", "error"],
- // (value, fmt?, ...args)
- ["timeLog", "log"],
- // (label?, ...args) no fmt string
- // Insensitive to whether any argument is an error. All arguments can pass
- // thru to baseConsole as is.
- ["clear", void 0],
- // ()
- ["count", "info"],
- // (label?)
- ["countReset", void 0],
- // (label?)
- ["dir", "log"],
- // (item, options?)
- ["groupEnd", "log"],
- // ()
- // In theory tabular data may be or contain an error. However, we currently
- // do not detect these and may never.
- ["table", "log"],
- // (tabularData, properties?)
- ["time", "info"],
- // (label?)
- ["timeEnd", "info"],
- // (label?)
- // Node Inspector only, MDN, and TypeScript, but not whatwg
- ["profile", void 0],
- // (label?)
- ["profileEnd", void 0],
- // (label?)
- ["timeStamp", void 0]
- // (label?)
-]), Os = y([
- ...jn,
- ...Zn
-]), Ui = (t, { shouldResetForDebugging: e = !1 } = {}) => {
- e && t.resetErrorTagNum();
- let r = [];
- const n = bt(
- de(Os, ([i, c]) => {
- const l = (...u) => {
- ne(r, [i, ...u]);
- };
- return D(l, "name", { value: i }), [i, y(l)];
- })
- );
- y(n);
- const o = () => {
- const i = y(r);
- return r = [], i;
- };
- return y(o), y({ loggingConsole: (
- /** @type {VirtualConsole} */
- n
- ), takeLog: o });
-};
-y(Ui);
-const dt = {
- NOTE: "ERROR_NOTE:",
- MESSAGE: "ERROR_MESSAGE:",
- CAUSE: "cause:",
- ERRORS: "errors:"
-};
-y(dt);
-const zn = (t, e) => {
- if (!t)
- return;
- const { getStackString: r, tagError: n, takeMessageLogArgs: o, takeNoteLogArgsArray: s } = e, i = (x, w) => de(x, (T) => Kr(T) ? (ne(w, T), `(${n(T)})`) : T), c = (x, w, R, T, j) => {
- const I = n(w), L = R === dt.MESSAGE ? `${I}:` : `${I} ${R}`, Z = i(T, j);
- t[x](L, ...Z);
- }, l = (x, w, R = void 0) => {
- if (w.length === 0)
- return;
- if (w.length === 1 && R === void 0) {
- f(x, w[0]);
- return;
- }
- let T;
- w.length === 1 ? T = "Nested error" : T = `Nested ${w.length} errors`, R !== void 0 && (T = `${T} under ${R}`), t.group(T);
- try {
- for (const j of w)
- f(x, j);
- } finally {
- t.groupEnd();
- }
- }, u = new Ut(), d = (x) => (w, R) => {
- const T = [];
- c(x, w, dt.NOTE, R, T), l(x, T, n(w));
- }, f = (x, w) => {
- if (lr(u, w))
- return;
- const R = n(w);
- qr(u, w);
- const T = [], j = o(w), I = s(
- w,
- d(x)
- );
- j === void 0 ? t[x](`${R}:`, w.message) : c(
- x,
- w,
- dt.MESSAGE,
- j,
- T
- );
- let L = r(w);
- typeof L == "string" && L.length >= 1 && !Bo(L, `
-`) && (L += `
-`), t[x](L), w.cause && c(x, w, dt.CAUSE, [w.cause], T), w.errors && c(x, w, dt.ERRORS, w.errors, T);
- for (const Z of I)
- c(x, w, dt.NOTE, Z, T);
- l(x, T, R);
- }, h = de(jn, ([x, w]) => {
- const R = (...T) => {
- const j = [], I = i(T, j);
- t[x] && t[x](...I), l(x, j);
- };
- return D(R, "name", { value: x }), [x, y(R)];
- }), p = et(
- Zn,
- ([x, w]) => x in t
- ), m = de(p, ([x, w]) => {
- const R = (...T) => {
- t[x](...T);
- };
- return D(R, "name", { value: x }), [x, y(R)];
- }), k = bt([...h, ...m]);
- return (
- /** @type {VirtualConsole} */
- y(k)
- );
-};
-y(zn);
-const ji = (t, e, r) => {
- const [n, ...o] = On(t, e), s = jo(o, (i) => [e, ...r, i]);
- return ["", n, ...s];
-}, Ms = (t) => y((r) => {
- const n = [], o = (...l) => (n.length > 0 && (l = jo(
- l,
- (u) => typeof u == "string" && Go(u, `
-`) ? ji(u, `
-`, n) : [u]
- ), l = [...n, ...l]), r(...l)), s = (l, u) => ({ [l]: (...d) => u(...d) })[l], i = bt([
- ...de(jn, ([l]) => [
- l,
- s(l, o)
- ]),
- ...de(Zn, ([l]) => [
- l,
- s(l, (...u) => o(l, ...u))
- ])
- ]);
- for (const l of ["group", "groupCollapsed"])
- i[l] ? i[l] = s(l, (...u) => {
- u.length >= 1 && o(...u), ne(n, " ");
- }) : i[l] = () => {
- };
- return i.groupEnd ? i.groupEnd = s("groupEnd", (...l) => {
- Er(n);
- }) : i.groupEnd = () => {
- }, harden(i), zn(
- /** @type {VirtualConsole} */
- i,
- t
- );
-});
-y(Ms);
-const Zi = (t, e, r = void 0) => {
- const n = et(
- Os,
- ([i, c]) => i in t
- ), o = de(n, ([i, c]) => [i, y((...u) => {
- (c === void 0 || e.canLog(c)) && t[i](...u);
- })]), s = bt(o);
- return (
- /** @type {VirtualConsole} */
- y(s)
- );
-};
-y(Zi);
-const fo = (t) => {
- if ($t === void 0)
- return;
- let e = 0;
- const r = new $e(), n = (d) => {
- Aa(r, d);
- }, o = new ze(), s = (d) => {
- if (Wr(r, d)) {
- const f = Ke(r, d);
- n(d), t(f);
- }
- }, i = new $t(s);
- return {
- rejectionHandledHandler: (d) => {
- const f = z(o, d);
- n(f);
- },
- unhandledRejectionHandler: (d, f) => {
- e += 1;
- const h = e;
- pe(r, h, d), he(o, f, h), Fa(i, f, h, f);
- },
- processTerminationHandler: () => {
- for (const [d, f] of Ta(r))
- n(d), t(f);
- }
- };
-}, un = (t) => {
- throw _(t);
-}, po = (t, e) => y((...r) => ue(t, e, r)), zi = (t = "safe", e = "platform", r = "report", n = void 0) => {
- t === "safe" || t === "unsafe" || un(`unrecognized consoleTaming ${t}`);
- let o;
- n === void 0 ? o = Ir : o = {
- ...Ir,
- getStackString: n
- };
- const s = (
- /** @type {VirtualConsole} */
- // eslint-disable-next-line no-nested-ternary
- typeof S.console < "u" ? S.console : typeof S.print == "function" ? (
- // Make a good-enough console for eshost (including only functions that
- // log at a specific level with no special argument interpretation).
- // https://console.spec.whatwg.org/#logging
- ((u) => y({ debug: u, log: u, info: u, warn: u, error: u }))(
- // eslint-disable-next-line no-undef
- po(S.print)
- )
- ) : void 0
- );
- if (s && s.log)
- for (const u of ["warn", "error"])
- s[u] || D(s, u, {
- value: po(s.log, s)
- });
- const i = (
- /** @type {VirtualConsole} */
- t === "unsafe" ? s : zn(s, o)
- ), c = S.process || void 0;
- if (e !== "none" && typeof c == "object" && typeof c.on == "function") {
- let u;
- if (e === "platform" || e === "exit") {
- const { exit: d } = c;
- typeof d == "function" || un("missing process.exit"), u = () => d(c.exitCode || -1);
- } else e === "abort" && (u = c.abort, typeof u == "function" || un("missing process.abort"));
- c.on("uncaughtException", (d) => {
- i.error("SES_UNCAUGHT_EXCEPTION:", d), u && u();
- });
- }
- if (r !== "none" && typeof c == "object" && typeof c.on == "function") {
- const d = fo((f) => {
- i.error("SES_UNHANDLED_REJECTION:", f);
- });
- d && (c.on("unhandledRejection", d.unhandledRejectionHandler), c.on("rejectionHandled", d.rejectionHandledHandler), c.on("exit", d.processTerminationHandler));
- }
- const l = S.window || void 0;
- if (e !== "none" && typeof l == "object" && typeof l.addEventListener == "function" && l.addEventListener("error", (u) => {
- u.preventDefault(), i.error("SES_UNCAUGHT_EXCEPTION:", u.error), (e === "exit" || e === "abort") && (l.location.href = "about:blank");
- }), r !== "none" && typeof l == "object" && typeof l.addEventListener == "function") {
- const d = fo((f) => {
- i.error("SES_UNHANDLED_REJECTION:", f);
- });
- d && (l.addEventListener("unhandledrejection", (f) => {
- f.preventDefault(), d.unhandledRejectionHandler(f.reason, f.promise);
- }), l.addEventListener("rejectionhandled", (f) => {
- f.preventDefault(), d.rejectionHandledHandler(f.promise);
- }), l.addEventListener("beforeunload", (f) => {
- d.processTerminationHandler();
- }));
- }
- return { console: i };
-}, Bi = [
- // suppress 'getThis' definitely
- "getTypeName",
- // suppress 'getFunction' definitely
- "getFunctionName",
- "getMethodName",
- "getFileName",
- "getLineNumber",
- "getColumnNumber",
- "getEvalOrigin",
- "isToplevel",
- "isEval",
- "isNative",
- "isConstructor",
- "isAsync",
- // suppress 'isPromiseAll' for now
- // suppress 'getPromiseIndex' for now
- // Additional names found by experiment, absent from
- // https://v8.dev/docs/stack-trace-api
- "getPosition",
- "getScriptNameOrSourceURL",
- "toString"
- // TODO replace to use only permitted info
-], Gi = (t) => {
- const r = bt(de(Bi, (n) => {
- const o = t[n];
- return [n, () => ue(o, t, [])];
- }));
- return H(r, {});
-}, Vi = (t) => de(t, Gi), Hi = /\/node_modules\//, Wi = /^(?:node:)?internal\//, qi = /\/packages\/ses\/src\/error\/assert.js$/, Ki = /\/packages\/eventual-send\/src\//, Yi = [
- Hi,
- Wi,
- qi,
- Ki
-], Ji = (t) => {
- if (!t)
- return !0;
- for (const e of Yi)
- if (Rn(e, t))
- return !1;
- return !0;
-}, Xi = /^((?:.*[( ])?)[:/\w_-]*\/\.\.\.\/(.+)$/, Qi = /^((?:.*[( ])?)[:/\w_-]*\/(packages\/.+)$/, ec = [
- Xi,
- Qi
-], tc = (t) => {
- for (const e of ec) {
- const r = $n(e, t);
- if (r)
- return Zt(xa(r, 1), "");
- }
- return t;
-}, rc = (t, e, r, n) => {
- if (r === "unsafe-debug")
- throw _(
- "internal: v8+unsafe-debug special case should already be done"
- );
- const o = t.captureStackTrace, s = (p) => n === "verbose" ? !0 : Ji(p.getFileName()), i = (p) => {
- let m = `${p}`;
- return n === "concise" && (m = tc(m)), `
- at ${m}`;
- }, c = (p, m) => Zt(
- de(et(m, s), i),
- ""
- ), l = new ze(), u = {
- // The optional `optFn` argument is for cutting off the bottom of
- // the stack --- for capturing the stack only above the topmost
- // call to that function. Since this isn't the "real" captureStackTrace
- // but instead calls the real one, if no other cutoff is provided,
- // we cut this one off.
- captureStackTrace(p, m = u.captureStackTrace) {
- if (typeof o == "function") {
- ue(o, t, [p, m]);
- return;
- }
- Mo(p, "stack", "");
- },
- // Shim of proposed special power, to reside by default only
- // in the start compartment, for getting the stack traceback
- // string associated with an error.
- // See https://tc39.es/proposal-error-stacks/
- getStackString(p) {
- let m = z(l, p);
- if (m === void 0 && (p.stack, m = z(l, p), m || (m = { stackString: "" }, he(l, p, m))), m.stackString !== void 0)
- return m.stackString;
- const k = c(p, m.callSites);
- return he(l, p, { stackString: k }), k;
- },
- prepareStackTrace(p, m) {
- if (r === "unsafe") {
- const k = c(p, m);
- return he(l, p, { stackString: k }), `${p}${k}`;
- } else
- return he(l, p, { callSites: m }), "";
- }
- }, d = u.prepareStackTrace;
- t.prepareStackTrace = d;
- const f = new Ut([d]), h = (p) => {
- if (lr(f, p))
- return p;
- const m = {
- prepareStackTrace(k, x) {
- return he(l, k, { callSites: x }), p(k, Vi(x));
- }
- };
- return qr(f, m.prepareStackTrace), m.prepareStackTrace;
- };
- return B(e, {
- captureStackTrace: {
- value: u.captureStackTrace,
- writable: !0,
- enumerable: !1,
- configurable: !0
- },
- prepareStackTrace: {
- get() {
- return t.prepareStackTrace;
- },
- set(p) {
- if (typeof p == "function") {
- const m = h(p);
- t.prepareStackTrace = m;
- } else
- t.prepareStackTrace = d;
- },
- enumerable: !1,
- configurable: !0
- }
- }), u.getStackString;
-}, ho = ee(le.prototype, "stack"), mo = ho && ho.get, nc = {
- getStackString(t) {
- return typeof mo == "function" ? ue(mo, t, []) : "stack" in t ? `${t.stack}` : "";
- }
-};
-let hr = nc.getStackString;
-function oc(t = "safe", e = "concise") {
- if (t !== "safe" && t !== "unsafe" && t !== "unsafe-debug")
- throw _(`unrecognized errorTaming ${t}`);
- if (e !== "concise" && e !== "verbose")
- throw _(`unrecognized stackFiltering ${e}`);
- const r = le.prototype, { captureStackTrace: n } = le, o = typeof n == "function" ? "v8" : "unknown", s = (l = {}) => {
- const u = function(...f) {
- let h;
- return new.target === void 0 ? h = ue(le, this, f) : h = xr(le, f, new.target), o === "v8" && ue(n, le, [h, u]), h;
- };
- return B(u, {
- length: { value: 1 },
- prototype: {
- value: r,
- writable: !1,
- enumerable: !1,
- configurable: !1
- }
- }), u;
- }, i = s({ powers: "original" }), c = s({ powers: "none" });
- B(r, {
- constructor: { value: c }
- });
- for (const l of ps)
- wr(l, c);
- if (B(i, {
- stackTraceLimit: {
- get() {
- if (typeof le.stackTraceLimit == "number")
- return le.stackTraceLimit;
- },
- set(l) {
- if (typeof l == "number" && typeof le.stackTraceLimit == "number") {
- le.stackTraceLimit = l;
- return;
- }
- },
- // WTF on v8 stackTraceLimit is enumerable
- enumerable: !1,
- configurable: !0
- }
- }), t === "unsafe-debug" && o === "v8") {
- B(i, {
- prepareStackTrace: {
- get() {
- return le.prepareStackTrace;
- },
- set(u) {
- le.prepareStackTrace = u;
- },
- enumerable: !1,
- configurable: !0
- },
- captureStackTrace: {
- value: le.captureStackTrace,
- writable: !0,
- enumerable: !1,
- configurable: !0
- }
- });
- const l = Be(i);
- return B(c, {
- stackTraceLimit: l.stackTraceLimit,
- prepareStackTrace: l.prepareStackTrace,
- captureStackTrace: l.captureStackTrace
- }), {
- "%InitialGetStackString%": hr,
- "%InitialError%": i,
- "%SharedError%": c
- };
- }
- return B(c, {
- stackTraceLimit: {
- get() {
- },
- set(l) {
- },
- enumerable: !1,
- configurable: !0
- }
- }), o === "v8" && B(c, {
- prepareStackTrace: {
- get() {
- return () => "";
- },
- set(l) {
- },
- enumerable: !1,
- configurable: !0
- },
- captureStackTrace: {
- value: (l, u) => {
- D(l, "stack", {
- value: ""
- });
- },
- writable: !1,
- enumerable: !1,
- configurable: !0
- }
- }), o === "v8" ? hr = rc(
- le,
- i,
- t,
- e
- ) : t === "unsafe" || t === "unsafe-debug" ? B(r, {
- stack: {
- get() {
- return hr(this);
- },
- set(l) {
- B(this, {
- stack: {
- value: l,
- writable: !0,
- enumerable: !0,
- configurable: !0
- }
- });
- }
- }
- }) : B(r, {
- stack: {
- get() {
- return `${this}`;
- },
- set(l) {
- B(this, {
- stack: {
- value: l,
- writable: !0,
- enumerable: !0,
- configurable: !0
- }
- });
- }
- }
- }), {
- "%InitialGetStackString%": hr,
- "%InitialError%": i,
- "%SharedError%": c
- };
-}
-const sc = () => {
-}, ac = async (t, e, r) => {
- await null;
- const n = t(...e);
- let o = kr(n);
- for (; !o.done; )
- try {
- const s = await o.value;
- o = kr(n, s);
- } catch (s) {
- o = Vo(n, r(s));
- }
- return o.value;
-}, ic = (t, e) => {
- const r = t(...e);
- let n = kr(r);
- for (; !n.done; )
- try {
- n = kr(r, n.value);
- } catch (o) {
- n = Vo(r, o);
- }
- return n.value;
-}, cc = (t, e) => y({ compartment: t, specifier: e }), lc = (t, e, r) => {
- const n = H(null);
- for (const o of t) {
- const s = e(o, r);
- n[o] = s;
- }
- return y(n);
-}, Bt = (t, e, r, n, o, s, i, c, l) => {
- const { resolveHook: u, name: d } = z(
- t,
- r
- ), { imports: f } = o;
- if (!pt(f) || Zo(f, (m) => typeof m != "string"))
- throw Ce(
- re`Invalid module source: 'imports' must be an array of strings, got ${f} for module ${U(n)} of compartment ${U(d)}`
- );
- const h = lc(f, u, n), p = y({
- compartment: r,
- moduleSource: o,
- moduleSpecifier: n,
- resolvedImports: h,
- importMeta: l
- });
- for (const m of Ro(h))
- s(It, [
- t,
- e,
- r,
- m,
- s,
- i,
- c
- ]);
- return p;
-};
-function* uc(t, e, r, n, o, s, i) {
- const {
- importHook: c,
- importNowHook: l,
- moduleMap: u,
- moduleMapHook: d,
- moduleRecords: f,
- parentCompartment: h
- } = z(t, r);
- if (Wr(f, n))
- return Ke(f, n);
- let p = u[n];
- if (p === void 0 && d !== void 0 && (p = d(n)), p === void 0) {
- const m = s(c, l);
- if (m === void 0) {
- const k = s(
- "importHook",
- "importNowHook"
- );
- throw Ce(
- re`${Tr(k)} needed to load module ${U(
- n
- )} in compartment ${U(r.name)}`
- );
- }
- p = m(n), Tt(e, p) || (p = yield p);
- }
- if (typeof p == "string")
- throw Ce(
- re`Cannot map module ${U(n)} to ${U(
- p
- )} in parent compartment, use {source} module descriptor`,
- _
- );
- if (ke(p)) {
- let m = z(e, p);
- if (m !== void 0 && (p = m), p.namespace !== void 0) {
- if (typeof p.namespace == "string") {
- const {
- compartment: w = h,
- namespace: R
- } = p;
- if (!ke(w) || !Tt(t, w))
- throw Ce(
- re`Invalid compartment in module descriptor for specifier ${U(n)} in compartment ${U(r.name)}`
- );
- const T = yield It(
- t,
- e,
- w,
- R,
- o,
- s,
- i
- );
- return pe(f, n, T), T;
- }
- if (ke(p.namespace)) {
- const { namespace: w } = p;
- if (m = z(e, w), m !== void 0)
- p = m;
- else {
- const R = Nt(w), I = Bt(
- t,
- e,
- r,
- n,
- {
- imports: [],
- exports: R,
- execute(L) {
- for (const Z of R)
- L[Z] = w[Z];
- }
- },
- o,
- s,
- i,
- void 0
- );
- return pe(f, n, I), I;
- }
- } else
- throw Ce(
- re`Invalid compartment in module descriptor for specifier ${U(n)} in compartment ${U(r.name)}`
- );
- }
- if (p.source !== void 0)
- if (typeof p.source == "string") {
- const {
- source: w,
- specifier: R = n,
- compartment: T = h,
- importMeta: j = void 0
- } = p, I = yield It(
- t,
- e,
- T,
- w,
- o,
- s,
- i
- ), { moduleSource: L } = I, Z = Bt(
- t,
- e,
- r,
- R,
- L,
- o,
- s,
- i,
- j
- );
- return pe(f, n, Z), Z;
- } else {
- const {
- source: w,
- specifier: R = n,
- importMeta: T
- } = p, j = Bt(
- t,
- e,
- r,
- R,
- w,
- o,
- s,
- i,
- T
- );
- return pe(f, n, j), j;
- }
- if (p.archive !== void 0)
- throw Ce(
- re`Unsupported archive module descriptor for specifier ${U(n)} in compartment ${U(r.name)}`
- );
- if (p.record !== void 0) {
- const {
- compartment: w = r,
- specifier: R = n,
- record: T,
- importMeta: j
- } = p, I = Bt(
- t,
- e,
- w,
- R,
- T,
- o,
- s,
- i,
- j
- );
- return pe(f, n, I), pe(f, R, I), I;
- }
- if (p.compartment !== void 0 && p.specifier !== void 0) {
- if (!ke(p.compartment) || !Tt(t, p.compartment) || typeof p.specifier != "string")
- throw Ce(
- re`Invalid compartment in module descriptor for specifier ${U(n)} in compartment ${U(r.name)}`
- );
- const w = yield It(
- t,
- e,
- p.compartment,
- p.specifier,
- o,
- s,
- i
- );
- return pe(f, n, w), w;
- }
- const x = Bt(
- t,
- e,
- r,
- n,
- p,
- o,
- s,
- i
- );
- return pe(f, n, x), x;
- } else
- throw Ce(
- re`module descriptor must be a string or object for specifier ${U(
- n
- )} in compartment ${U(r.name)}`
- );
-}
-const It = (t, e, r, n, o, s, i) => {
- const { name: c } = z(
- t,
- r
- );
- let l = Ke(i, r);
- l === void 0 && (l = new $e(), pe(i, r, l));
- let u = Ke(l, n);
- return u !== void 0 || (u = s(ac, ic)(
- uc,
- [
- t,
- e,
- r,
- n,
- o,
- s,
- i
- ],
- (d) => {
- throw Jr(
- d,
- re`${d.message}, loading ${U(n)} in compartment ${U(
- c
- )}`
- ), d;
- }
- ), pe(l, n, u)), u;
-}, dc = () => {
- const t = new Dt(), e = [];
- return { enqueueJob: (o, s) => {
- In(
- t,
- qo(o(...s), sc, (i) => {
- ne(e, i);
- })
- );
- }, drainQueue: async () => {
- await null;
- for (const o of t)
- await o;
- return e;
- } };
-}, Ls = ({ errors: t, errorPrefix: e }) => {
- if (t.length > 0) {
- const r = ce("COMPARTMENT_LOAD_ERRORS", "", ["verbose"]) === "verbose";
- throw _(
- `${e} (${t.length} underlying failures: ${Zt(
- de(t, (n) => n.message + (r ? n.stack : "")),
- ", "
- )}`
- );
- }
-}, fc = (t, e) => e, pc = (t, e) => t, go = async (t, e, r, n) => {
- const { name: o } = z(
- t,
- r
- ), s = new $e(), { enqueueJob: i, drainQueue: c } = dc();
- i(It, [
- t,
- e,
- r,
- n,
- i,
- pc,
- s
- ]);
- const l = await c();
- Ls({
- errors: l,
- errorPrefix: `Failed to load module ${U(n)} in package ${U(
- o
- )}`
- });
-}, hc = (t, e, r, n) => {
- const { name: o } = z(
- t,
- r
- ), s = new $e(), i = [], c = (l, u) => {
- try {
- l(...u);
- } catch (d) {
- ne(i, d);
- }
- };
- c(It, [
- t,
- e,
- r,
- n,
- c,
- fc,
- s
- ]), Ls({
- errors: i,
- errorPrefix: `Failed to load module ${U(n)} in package ${U(
- o
- )}`
- });
-}, { quote: St } = Y, mc = () => {
- let t = !1;
- const e = H(null, {
- // Make this appear like an ESM module namespace object.
- [Qe]: {
- value: "Module",
- writable: !1,
- enumerable: !1,
- configurable: !1
- }
- });
- return y({
- activate() {
- t = !0;
- },
- exportsTarget: e,
- exportsProxy: new Ur(e, {
- get(r, n, o) {
- if (!t)
- throw _(
- `Cannot get property ${St(
- n
- )} of module exports namespace, the module has not yet begun to execute`
- );
- return _a(e, n, o);
- },
- set(r, n, o) {
- throw _(
- `Cannot set property ${St(n)} of module exports namespace`
- );
- },
- has(r, n) {
- if (!t)
- throw _(
- `Cannot check property ${St(
- n
- )}, the module has not yet begun to execute`
- );
- return Oo(e, n);
- },
- deleteProperty(r, n) {
- throw _(
- `Cannot delete property ${St(n)}s of module exports namespace`
- );
- },
- ownKeys(r) {
- if (!t)
- throw _(
- "Cannot enumerate keys, the module has not yet begun to execute"
- );
- return qe(e);
- },
- getOwnPropertyDescriptor(r, n) {
- if (!t)
- throw _(
- `Cannot get own property descriptor ${St(
- n
- )}, the module has not yet begun to execute`
- );
- return va(e, n);
- },
- preventExtensions(r) {
- if (!t)
- throw _(
- "Cannot prevent extensions of module exports namespace, the module has not yet begun to execute"
- );
- return wa(e);
- },
- isExtensible() {
- if (!t)
- throw _(
- "Cannot check extensibility of module exports namespace, the module has not yet begun to execute"
- );
- return ba(e);
- },
- getPrototypeOf(r) {
- return null;
- },
- setPrototypeOf(r, n) {
- throw _("Cannot set prototype of module exports namespace");
- },
- defineProperty(r, n, o) {
- throw _(
- `Cannot define property ${St(n)} of module exports namespace`
- );
- },
- apply(r, n, o) {
- throw _(
- "Cannot call module exports namespace, it is not a function"
- );
- },
- construct(r, n) {
- throw _(
- "Cannot construct module exports namespace, it is not a constructor"
- );
- }
- })
- });
-}, Bn = (t, e, r, n) => {
- const { deferredExports: o } = e;
- if (!Wr(o, n)) {
- const s = mc();
- he(
- r,
- s.exportsProxy,
- cc(t, n)
- ), pe(o, n, s);
- }
- return Ke(o, n);
-}, gc = (t, e) => {
- const { sloppyGlobalsMode: r = !1, __moduleShimLexicals__: n = void 0 } = e;
- let o;
- if (n === void 0 && !r)
- ({ safeEvaluate: o } = t);
- else {
- let { globalTransforms: s } = t;
- const { globalObject: i } = t;
- let c;
- n !== void 0 && (s = void 0, c = H(
- null,
- Be(n)
- )), { safeEvaluate: o } = Un({
- globalObject: i,
- moduleLexicals: c,
- globalTransforms: s,
- sloppyGlobalsMode: r
- });
- }
- return { safeEvaluate: o };
-}, Fs = (t, e, r) => {
- if (typeof e != "string")
- throw _("first argument of evaluate() must be a string");
- const {
- transforms: n = [],
- __evadeHtmlCommentTest__: o = !1,
- __evadeImportExpressionTest__: s = !1,
- __rejectSomeDirectEvalExpressions__: i = !0
- // Note default on
- } = r, c = [...n];
- o === !0 && ne(c, Ps), s === !0 && ne(c, Is), i === !0 && ne(c, Cs);
- const { safeEvaluate: l } = gc(
- t,
- r
- );
- return l(e, {
- localTransforms: c
- });
-}, { quote: mr } = Y, yc = (t, e, r, n, o, s) => {
- const { exportsProxy: i, exportsTarget: c, activate: l } = Bn(
- r,
- z(t, r),
- n,
- o
- ), u = H(null);
- if (e.exports) {
- if (!pt(e.exports) || Zo(e.exports, (f) => typeof f != "string"))
- throw _(
- `SES virtual module source "exports" property must be an array of strings for module ${o}`
- );
- ht(e.exports, (f) => {
- let h = c[f];
- const p = [];
- D(c, f, {
- get: () => h,
- set: (x) => {
- h = x;
- for (const w of p)
- w(x);
- },
- enumerable: !0,
- configurable: !1
- }), u[f] = (x) => {
- ne(p, x), x(h);
- };
- }), u["*"] = (f) => {
- f(c);
- };
- }
- const d = {
- activated: !1
- };
- return y({
- notifiers: u,
- exportsProxy: i,
- execute() {
- if (Oo(d, "errorFromExecute"))
- throw d.errorFromExecute;
- if (!d.activated) {
- l(), d.activated = !0;
- try {
- e.execute(c, r, s);
- } catch (f) {
- throw d.errorFromExecute = f, f;
- }
- }
- }
- });
-}, _c = (t, e, r, n) => {
- const {
- compartment: o,
- moduleSpecifier: s,
- moduleSource: i,
- importMeta: c
- } = r, {
- reexports: l = [],
- __syncModuleProgram__: u,
- __fixedExportMap__: d = {},
- __liveExportMap__: f = {},
- __reexportMap__: h = {},
- __needsImportMeta__: p = !1,
- __syncModuleFunctor__: m
- } = i, k = z(t, o), { __shimTransforms__: x, importMetaHook: w } = k, { exportsProxy: R, exportsTarget: T, activate: j } = Bn(
- o,
- k,
- e,
- s
- ), I = H(null), L = H(null), Z = H(null), se = H(null), J = H(null);
- c && jr(J, c), p && w && w(s, J);
- const be = H(null), Me = H(null);
- ht(me(d), ([we, [W]]) => {
- let q = be[W];
- if (!q) {
- let ae, ie = !0, ge = [];
- const te = () => {
- if (ie)
- throw Wt(`binding ${mr(W)} not yet initialized`);
- return ae;
- }, Ae = y((Te) => {
- if (!ie)
- throw _(
- `Internal: binding ${mr(W)} already initialized`
- );
- ae = Te;
- const Wn = ge;
- ge = null, ie = !1;
- for (const Ie of Wn || [])
- Ie(Te);
- return Te;
- });
- q = {
- get: te,
- notify: (Te) => {
- Te !== Ae && (ie ? ne(ge || [], Te) : Te(ae));
- }
- }, be[W] = q, Z[W] = Ae;
- }
- I[we] = {
- get: q.get,
- set: void 0,
- enumerable: !0,
- configurable: !1
- }, Me[we] = q.notify;
- }), ht(
- me(f),
- ([we, [W, q]]) => {
- let ae = be[W];
- if (!ae) {
- let ie, ge = !0;
- const te = [], Ae = () => {
- if (ge)
- throw Wt(
- `binding ${mr(we)} not yet initialized`
- );
- return ie;
- }, xt = y((Ie) => {
- ie = Ie, ge = !1;
- for (const en of te)
- en(Ie);
- }), Te = (Ie) => {
- if (ge)
- throw Wt(`binding ${mr(W)} not yet initialized`);
- ie = Ie;
- for (const en of te)
- en(Ie);
- };
- ae = {
- get: Ae,
- notify: (Ie) => {
- Ie !== xt && (ne(te, Ie), ge || Ie(ie));
- }
- }, be[W] = ae, q && D(L, W, {
- get: Ae,
- set: Te,
- enumerable: !0,
- configurable: !1
- }), se[W] = xt;
- }
- I[we] = {
- get: ae.get,
- set: void 0,
- enumerable: !0,
- configurable: !1
- }, Me[we] = ae.notify;
- }
- );
- const dr = (we) => {
- we(T);
- };
- Me["*"] = dr;
- function zt(we) {
- const W = H(null);
- W.default = !1;
- for (const [q, ae] of we) {
- const ie = Ke(n, q);
- ie.execute();
- const { notifiers: ge } = ie;
- for (const [te, Ae] of ae) {
- const xt = ge[te];
- if (!xt)
- throw ir(
- `The requested module '${q}' does not provide an export named '${te}'`
- );
- for (const Te of Ae)
- xt(Te);
- }
- if (Hr(l, q))
- for (const [te, Ae] of me(
- ge
- ))
- W[te] === void 0 ? W[te] = Ae : W[te] = !1;
- if (h[q])
- for (const [te, Ae] of h[q])
- W[Ae] = ge[te];
- }
- for (const [q, ae] of me(W))
- if (!Me[q] && ae !== !1) {
- Me[q] = ae;
- let ie;
- ae((te) => ie = te), I[q] = {
- get() {
- return ie;
- },
- set: void 0,
- enumerable: !0,
- configurable: !1
- };
- }
- ht(
- zo(Co(I)),
- (q) => D(T, q, I[q])
- ), y(T), j();
- }
- let wt;
- m !== void 0 ? wt = m : wt = Fs(k, u, {
- globalObject: o.globalThis,
- transforms: x,
- __moduleShimLexicals__: L
- });
- let Pe = !1, st;
- function na() {
- if (wt) {
- const we = wt;
- wt = null;
- try {
- we(
- y({
- imports: y(zt),
- onceVar: y(Z),
- liveVar: y(se),
- importMeta: J
- })
- );
- } catch (W) {
- Pe = !0, st = W;
- }
- }
- if (Pe)
- throw st;
- }
- return y({
- notifiers: Me,
- exportsProxy: R,
- execute: na
- });
-}, { Fail: ft, quote: X } = Y, Ds = (t, e, r, n) => {
- const { name: o, moduleRecords: s } = z(
- t,
- r
- ), i = Ke(s, n);
- if (i === void 0)
- throw Wt(
- `Missing link to module ${X(n)} from compartment ${X(
- o
- )}`
- );
- return Ec(t, e, i);
-};
-function vc(t) {
- return typeof t.__syncModuleProgram__ == "string";
-}
-function bc(t, e) {
- const { __fixedExportMap__: r, __liveExportMap__: n } = t;
- ke(r) || ft`Property '__fixedExportMap__' of a precompiled module source must be an object, got ${X(
- r
- )}, for module ${X(e)}`, ke(n) || ft`Property '__liveExportMap__' of a precompiled module source must be an object, got ${X(
- n
- )}, for module ${X(e)}`;
-}
-function wc(t) {
- return typeof t.execute == "function";
-}
-function xc(t, e) {
- const { exports: r } = t;
- pt(r) || ft`Invalid module source: 'exports' of a virtual module source must be an array, got ${X(
- r
- )}, for module ${X(e)}`;
-}
-function Sc(t, e) {
- ke(t) || ft`Invalid module source: must be of type object, got ${X(
- t
- )}, for module ${X(e)}`;
- const { imports: r, exports: n, reexports: o = [] } = t;
- pt(r) || ft`Invalid module source: 'imports' must be an array, got ${X(
- r
- )}, for module ${X(e)}`, pt(n) || ft`Invalid module source: 'exports' must be an array, got ${X(
- n
- )}, for module ${X(e)}`, pt(o) || ft`Invalid module source: 'reexports' must be an array if present, got ${X(
- o
- )}, for module ${X(e)}`;
-}
-const Ec = (t, e, r) => {
- const { compartment: n, moduleSpecifier: o, resolvedImports: s, moduleSource: i } = r, { instances: c } = z(t, n);
- if (Wr(c, o))
- return Ke(c, o);
- Sc(i, o);
- const l = new $e();
- let u;
- if (vc(i))
- bc(i, o), u = _c(
- t,
- e,
- r,
- l
- );
- else if (wc(i))
- xc(i, o), u = yc(
- t,
- i,
- n,
- e,
- o,
- s
- );
- else
- throw _(`Invalid module source, got ${X(i)}`);
- pe(c, o, u);
- for (const [d, f] of me(s)) {
- const h = Ds(
- t,
- e,
- n,
- f
- );
- pe(l, d, h);
- }
- return u;
-}, Gt = new ze(), Fe = new ze(), Gn = function(e = {}, r = {}, n = {}) {
- throw _(
- "Compartment.prototype.constructor is not a valid constructor."
- );
-}, yo = (t, e) => {
- const { execute: r, exportsProxy: n } = Ds(
- Fe,
- Gt,
- t,
- e
- );
- return r(), n;
-}, Vn = {
- constructor: Gn,
- get globalThis() {
- return z(Fe, this).globalObject;
- },
- get name() {
- return z(Fe, this).name;
- },
- /**
- * @param {string} source is a JavaScript program grammar construction.
- * @param {object} [options]
- * @param {Array} [options.transforms]
- * @param {boolean} [options.sloppyGlobalsMode]
- * @param {object} [options.__moduleShimLexicals__]
- * @param {boolean} [options.__evadeHtmlCommentTest__]
- * @param {boolean} [options.__evadeImportExpressionTest__]
- * @param {boolean} [options.__rejectSomeDirectEvalExpressions__]
- */
- evaluate(t, e = {}) {
- const r = z(Fe, this);
- return Fs(r, t, e);
- },
- module(t) {
- if (typeof t != "string")
- throw _("first argument of module() must be a string");
- const { exportsProxy: e } = Bn(
- this,
- z(Fe, this),
- Gt,
- t
- );
- return e;
- },
- async import(t) {
- const { noNamespaceBox: e } = z(Fe, this);
- if (typeof t != "string")
- throw _("first argument of import() must be a string");
- return qo(
- go(Fe, Gt, this, t),
- () => {
- const r = yo(
- /** @type {Compartment} */
- this,
- t
- );
- return e ? r : { namespace: r };
- }
- );
- },
- async load(t) {
- if (typeof t != "string")
- throw _("first argument of load() must be a string");
- return go(Fe, Gt, this, t);
- },
- importNow(t) {
- if (typeof t != "string")
- throw _("first argument of importNow() must be a string");
- return hc(Fe, Gt, this, t), yo(
- /** @type {Compartment} */
- this,
- t
- );
- }
-};
-B(Vn, {
- [Qe]: {
- value: "Compartment",
- writable: !1,
- enumerable: !1,
- configurable: !0
- }
-});
-B(Gn, {
- prototype: { value: Vn }
-});
-const kc = (...t) => {
- if (t.length === 0)
- return {};
- if (t.length === 1 && typeof t[0] == "object" && t[0] !== null && "__options__" in t[0]) {
- const { __options__: e, ...r } = t[0];
- return Y(
- e === !0,
- `Compartment constructor only supports true __options__ sigil, got ${e}`
- ), r;
- } else {
- const [
- e = (
- /** @type {Map} */
- {}
- ),
- r = (
- /** @type {Map} */
- {}
- ),
- n = {}
- ] = t;
- return to(
- n.modules,
- void 0,
- "Compartment constructor must receive either a module map argument or modules option, not both"
- ), to(
- n.globals,
- void 0,
- "Compartment constructor must receive either globals argument or option, not both"
- ), {
- ...n,
- globals: e,
- modules: r
- };
- }
-}, xn = (t, e, r, n = void 0) => {
- function o(...s) {
- if (new.target === void 0)
- throw _(
- "Class constructor Compartment cannot be invoked without 'new'"
- );
- const {
- name: i = "",
- transforms: c = [],
- __shimTransforms__: l = [],
- globals: u = {},
- modules: d = {},
- resolveHook: f,
- importHook: h,
- importNowHook: p,
- moduleMapHook: m,
- importMetaHook: k,
- __noNamespaceBox__: x = !1
- } = kc(...s), w = [...c, ...l], R = { __proto__: null, ...u }, T = { __proto__: null, ...d }, j = new $e(), I = new $e(), L = new $e(), Z = {};
- Si(Z), _s(Z);
- const { safeEvaluate: se } = Un({
- globalObject: Z,
- globalTransforms: w,
- sloppyGlobalsMode: !1
- });
- vs(Z, {
- intrinsics: e,
- newGlobalPropertyNames: fs,
- makeCompartmentConstructor: t,
- parentCompartment: this,
- markVirtualizedNativeFunction: r
- }), wn(
- Z,
- se,
- r
- ), jr(Z, R), he(Fe, this, {
- name: `${i}`,
- globalTransforms: w,
- globalObject: Z,
- safeEvaluate: se,
- resolveHook: f,
- importHook: h,
- importNowHook: p,
- moduleMap: T,
- moduleMapHook: m,
- importMetaHook: k,
- moduleRecords: j,
- __shimTransforms__: l,
- deferredExports: L,
- instances: I,
- parentCompartment: n,
- noNamespaceBox: x
- });
- }
- return o.prototype = Vn, o;
-};
-function dn(t) {
- return G(t).constructor;
-}
-function Pc() {
- return arguments;
-}
-const Ac = () => {
- const t = Ee.prototype.constructor, e = ee(Pc(), "callee"), r = e && e.get, n = Oa(new _e()), o = G(n), s = Br[$o] && Ra(/./), i = s && G(s), c = Sa([]), l = G(c), u = G(aa), d = Ia(new $e()), f = G(d), h = Ca(new Dt()), p = G(h), m = G(l);
- function* k() {
- }
- const x = dn(k), w = x.prototype;
- async function* R() {
- }
- const T = dn(
- R
- ), j = T.prototype, I = j.prototype, L = G(I);
- async function Z() {
- }
- const se = dn(Z), J = {
- "%InertFunction%": t,
- "%ArrayIteratorPrototype%": l,
- "%InertAsyncFunction%": se,
- "%AsyncGenerator%": j,
- "%InertAsyncGeneratorFunction%": T,
- "%AsyncGeneratorPrototype%": I,
- "%AsyncIteratorPrototype%": L,
- "%Generator%": w,
- "%InertGeneratorFunction%": x,
- "%IteratorPrototype%": m,
- "%MapIteratorPrototype%": f,
- "%RegExpStringIteratorPrototype%": i,
- "%SetIteratorPrototype%": p,
- "%StringIteratorPrototype%": o,
- "%ThrowTypeError%": r,
- "%TypedArray%": u,
- "%InertCompartment%": Gn
- };
- return S.Iterator && (J["%IteratorHelperPrototype%"] = G(
- // eslint-disable-next-line @endo/no-polymorphic-call
- S.Iterator.from([]).take(0)
- ), J["%WrapForValidIteratorPrototype%"] = G(
- // eslint-disable-next-line @endo/no-polymorphic-call
- S.Iterator.from({
- next() {
- return { value: void 0 };
- }
- })
- )), S.AsyncIterator && (J["%AsyncIteratorHelperPrototype%"] = G(
- // eslint-disable-next-line @endo/no-polymorphic-call
- S.AsyncIterator.from([]).take(0)
- ), J["%WrapForValidAsyncIteratorPrototype%"] = G(
- // eslint-disable-next-line @endo/no-polymorphic-call
- S.AsyncIterator.from({ next() {
- } })
- )), J;
-}, Us = (t, e) => {
- if (e !== "safe" && e !== "unsafe")
- throw _(`unrecognized fakeHardenOption ${e}`);
- if (e === "safe" || (Object.isExtensible = () => !1, Object.isFrozen = () => !0, Object.isSealed = () => !0, Reflect.isExtensible = () => !1, t.isFake))
- return t;
- const r = (n) => n;
- return r.isFake = !0, y(r);
-};
-y(Us);
-const Tc = () => {
- const t = At, e = t.prototype, r = Wo(At, void 0);
- B(e, {
- constructor: {
- value: r
- // leave other `constructor` attributes as is
- }
- });
- const n = me(
- Be(t)
- ), o = bt(
- de(n, ([s, i]) => [
- s,
- { ...i, configurable: !0 }
- ])
- );
- return B(r, o), { "%SharedSymbol%": r };
-}, Ic = (t) => {
- try {
- return t(), !1;
- } catch {
- return !0;
- }
-}, _o = (t, e, r) => {
- if (t === void 0)
- return !1;
- const n = ee(t, e);
- if (!n || "value" in n)
- return !1;
- const { get: o, set: s } = n;
- if (typeof o != "function" || typeof s != "function" || o() !== r || ue(o, t, []) !== r)
- return !1;
- const i = "Seems to be a setter", c = { __proto__: null };
- if (ue(s, c, [i]), c[e] !== i)
- return !1;
- const l = { __proto__: t };
- return ue(s, l, [i]), l[e] !== i || !Ic(() => ue(s, t, [r])) || "originalValue" in o || n.configurable === !1 ? !1 : (D(t, e, {
- value: r,
- writable: !0,
- enumerable: n.enumerable,
- configurable: !0
- }), !0);
-}, Cc = (t) => {
- _o(
- t["%IteratorPrototype%"],
- "constructor",
- t.Iterator
- ), _o(
- t["%IteratorPrototype%"],
- Qe,
- "Iterator"
- );
-}, Rc = () => {
- const t = on[De];
- D(on, De, {
- configurable: !0,
- get() {
- return t;
- },
- set(e) {
- this !== on && (Q(this, De) && (this[De] = e), D(this, De, {
- value: e,
- writable: !0,
- enumerable: !0,
- configurable: !0
- }));
- }
- });
-}, $c = () => {
- if (typeof Sr.transfer == "function")
- return {};
- const t = S.structuredClone;
- return typeof t != "function" ? {} : (D(Sr, "transfer", {
- // @ts-expect-error
- value: {
- /**
- * @param {number} [newLength]
- */
- transfer(r = void 0) {
- const n = ka(this);
- if (r === void 0 || r === n)
- return t(this, { transfer: [this] });
- if (typeof r != "number")
- throw _("transfer newLength if provided must be a number");
- if (r > n) {
- const o = new To(r), s = new mn(this), i = new mn(o);
- return Pa(i, s), t(this, { transfer: [this] }), o;
- } else {
- const o = Ea(this, 0, r);
- return t(this, { transfer: [this] }), o;
- }
- }
- }.transfer,
- writable: !0,
- enumerable: !1,
- configurable: !0
- }), {});
-}, gr = (t) => {
- let e = !1;
- const r = (...n) => {
- e ? t(" ", ...n) : t(...n);
- };
- return (
- /** @type {GroupReporter} */
- {
- warn(...n) {
- r(...n);
- },
- error(...n) {
- r(...n);
- },
- groupCollapsed(...n) {
- Y(!e), t(...n), e = !0;
- },
- groupEnd() {
- e = !1;
- }
- }
- );
-}, vo = () => {
-}, js = (t) => {
- if (t === "none")
- return gr(vo);
- if (t !== "platform" && t !== "console")
- throw new _(`Invalid lockdown reporting option: ${t}`);
- if (t === "console" || S.window === S || S.importScripts !== void 0)
- return console;
- if (S.console !== void 0) {
- const e = S.console, r = Wo(e.error, e);
- return gr(r);
- }
- return S.print !== void 0 ? gr(S.print) : gr(vo);
-}, bo = (t, e, r) => {
- const { warn: n, error: o, groupCollapsed: s, groupEnd: i } = e;
- let c = !1;
- try {
- return r({
- warn(...l) {
- c || (s(t), c = !0), n(...l);
- },
- error(...l) {
- c || (s(t), c = !0), o(...l);
- }
- });
- } finally {
- c && i();
- }
-}, { Fail: fn, details: wo, quote: pn } = Y;
-let yr, _r;
-const Nc = ni(), Oc = () => {
- let t = !1;
- try {
- t = Ee(
- "eval",
- "SES_changed",
- ` eval("SES_changed = true");
- return SES_changed;
- `
- )(Ko, !1), t || delete S.SES_changed;
- } catch {
- t = !0;
- }
- if (!t)
- throw _(
- "SES cannot initialize unless 'eval' is the original intrinsic 'eval', suitable for direct-eval (dynamically scoped eval) (SES_DIRECT_EVAL)"
- );
-}, Zs = (t = {}) => {
- const {
- errorTaming: e = ce("LOCKDOWN_ERROR_TAMING", "safe"),
- errorTrapping: r = (
- /** @type {"platform" | "none" | "report" | "abort" | "exit"} */
- ce("LOCKDOWN_ERROR_TRAPPING", "platform")
- ),
- reporting: n = (
- /** @type {"platform" | "console" | "none"} */
- ce("LOCKDOWN_REPORTING", "platform")
- ),
- unhandledRejectionTrapping: o = (
- /** @type {"none" | "report"} */
- ce("LOCKDOWN_UNHANDLED_REJECTION_TRAPPING", "report")
- ),
- regExpTaming: s = ce("LOCKDOWN_REGEXP_TAMING", "safe"),
- localeTaming: i = ce("LOCKDOWN_LOCALE_TAMING", "safe"),
- consoleTaming: c = (
- /** @type {'unsafe' | 'safe'} */
- ce("LOCKDOWN_CONSOLE_TAMING", "safe")
- ),
- overrideTaming: l = (
- /** @type {'moderate' | 'min' | 'severe'} */
- ce("LOCKDOWN_OVERRIDE_TAMING", "moderate")
- ),
- stackFiltering: u = ce("LOCKDOWN_STACK_FILTERING", "concise"),
- domainTaming: d = ce("LOCKDOWN_DOMAIN_TAMING", "safe"),
- evalTaming: f = ce("LOCKDOWN_EVAL_TAMING", "safeEval"),
- overrideDebug: h = et(
- On(ce("LOCKDOWN_OVERRIDE_DEBUG", ""), ","),
- /** @param {string} debugName */
- (Pe) => Pe !== ""
- ),
- legacyRegeneratorRuntimeTaming: p = ce(
- "LOCKDOWN_LEGACY_REGENERATOR_RUNTIME_TAMING",
- "safe"
- ),
- __hardenTaming__: m = ce("LOCKDOWN_HARDEN_TAMING", "safe"),
- dateTaming: k = "safe",
- // deprecated
- mathTaming: x = "safe",
- // deprecated
- ...w
- } = t;
- p === "safe" || p === "unsafe-ignore" || fn`lockdown(): non supported option legacyRegeneratorRuntimeTaming: ${pn(p)}`, f === "unsafeEval" || f === "safeEval" || f === "noEval" || fn`lockdown(): non supported option evalTaming: ${pn(f)}`;
- const R = qe(w);
- R.length === 0 || fn`lockdown(): non supported option ${pn(R)}`;
- const T = js(n);
- if (yr === void 0 || // eslint-disable-next-line @endo/no-polymorphic-call
- Y.fail(
- wo`Already locked down at ${yr} (SES_ALREADY_LOCKED_DOWN)`,
- _
- ), yr = _("Prior lockdown (SES_ALREADY_LOCKED_DOWN)"), yr.stack, Oc(), S.Function.prototype.constructor !== S.Function && // @ts-ignore harden is absent on globalThis type def.
- typeof S.harden == "function" && // @ts-ignore lockdown is absent on globalThis type def.
- typeof S.lockdown == "function" && S.Date.prototype.constructor !== S.Date && typeof S.Date.now == "function" && // @ts-ignore does not recognize that Date constructor is a special
- // Function.
- // eslint-disable-next-line @endo/no-polymorphic-call
- Zr(S.Date.prototype.constructor.now(), NaN))
- throw _(
- "Already locked down but not by this SES instance (SES_MULTIPLE_INSTANCES)"
- );
- Fi(d);
- const I = Ns(), { addIntrinsics: L, completePrototypes: Z, finalIntrinsics: se } = ms(T), J = Us(Nc, m);
- L({ harden: J }), L(ui()), L(di(k)), L(oc(e, u)), L(fi(x)), L(pi(s)), L(Tc()), L($c()), L(Di()), L(Ac()), Z();
- const be = se(), Me = { __proto__: null };
- typeof S.Buffer == "function" && (Me.Buffer = S.Buffer);
- let dr;
- e === "safe" && (dr = be["%InitialGetStackString%"]);
- const zt = zi(
- c,
- r,
- o,
- dr
- );
- if (S.console = /** @type {Console} */
- zt.console, typeof /** @type {any} */
- zt.console._times == "object" && (Me.SafeMap = G(
- // eslint-disable-next-line no-underscore-dangle
- /** @type {any} */
- zt.console._times
- )), (e === "unsafe" || e === "unsafe-debug") && S.assert === Y && (S.assert = Xr(void 0, !0)), bi(be, i), Cc(be), bo(
- "SES Removing unpermitted intrinsics",
- T,
- (Pe) => li(
- be,
- I,
- Pe
- )
- ), _s(S), vs(S, {
- intrinsics: be,
- newGlobalPropertyNames: ro,
- makeCompartmentConstructor: xn,
- markVirtualizedNativeFunction: I
- }), f === "noEval")
- wn(
- S,
- Da,
- I
- );
- else if (f === "safeEval") {
- const { safeEvaluate: Pe } = Un({ globalObject: S });
- wn(
- S,
- Pe,
- I
- );
- }
- return () => {
- _r === void 0 || // eslint-disable-next-line @endo/no-polymorphic-call
- Y.fail(
- wo`Already locked down at ${_r} (SES_ALREADY_LOCKED_DOWN)`,
- _
- ), _r = _(
- "Prior lockdown (SES_ALREADY_LOCKED_DOWN)"
- ), _r.stack, bo(
- "SES Enabling property overrides",
- T,
- (st) => gi(
- be,
- l,
- st,
- h
- )
- ), p === "unsafe-ignore" && Rc();
- const Pe = {
- intrinsics: be,
- hostIntrinsics: Me,
- globals: {
- // Harden evaluators
- Function: S.Function,
- eval: S.eval,
- // @ts-ignore Compartment does exist on globalThis
- Compartment: S.Compartment,
- // Harden Symbol
- Symbol: S.Symbol
- }
- };
- for (const st of Nt(ro))
- Pe.globals[st] = S[st];
- return J(Pe), J;
- };
-};
-S.lockdown = (t) => {
- const e = Zs(t);
- S.harden = e();
-};
-S.repairIntrinsics = (t) => {
- const e = Zs(t);
- S.hardenIntrinsics = () => {
- S.harden = e();
- };
-};
-const Mc = Ns(), Lc = js("none");
-S.Compartment = xn(
- xn,
- // Any reporting that would need to be done should have already been done
- // during `lockdown()`.
- // See https://github.com/endojs/endo/pull/2624#discussion_r1840979770
- ci(S, Lc),
- Mc
-);
-S.assert = Y;
-const Fc = Ms(Ir), Dc = ma(
- "MAKE_CAUSAL_CONSOLE_FROM_LOGGER_KEY_FOR_SES_AVA"
-);
-S[Dc] = Fc;
-const zs = (t) => {
- let e = 0, r = 0;
- if (t && window.DOMMatrixReadOnly) {
- const n = window.getComputedStyle(t), o = new DOMMatrixReadOnly(n.transform);
- e = o.m41, r = o.m42;
- }
- return { x: e, y: r };
-}, Uc = (t, e = t, r) => {
- let n = { x: 0, y: 0 }, o = { x: 0, y: 0 };
- const s = (l) => {
- const { clientX: u, clientY: d } = l, f = u - o.x + n.x, h = d - o.y + n.y;
- e.style.transform = `translate(${f}px, ${h}px)`, r == null || r();
- }, i = () => {
- document.removeEventListener("mousemove", s), document.removeEventListener("mouseup", i);
- }, c = (l) => {
- o = { x: l.clientX, y: l.clientY }, n = zs(e), document.addEventListener("mousemove", s), document.addEventListener("mouseup", i);
- };
- return t.addEventListener("mousedown", c), i;
-}, jc = `:host{--spacing-4: .25rem;--spacing-8: calc(var(--spacing-4) * 2);--spacing-12: calc(var(--spacing-4) * 3);--spacing-16: calc(var(--spacing-4) * 4);--spacing-20: calc(var(--spacing-4) * 5);--spacing-24: calc(var(--spacing-4) * 6);--spacing-28: calc(var(--spacing-4) * 7);--spacing-32: calc(var(--spacing-4) * 8);--spacing-36: calc(var(--spacing-4) * 9);--spacing-40: calc(var(--spacing-4) * 10);--font-weight-regular: 400;--font-weight-bold: 500;--font-line-height-s: 1.2;--font-line-height-m: 1.4;--font-line-height-l: 1.5;--font-size-s: 12px;--font-size-m: 14px;--font-size-l: 16px}[data-theme]{background-color:var(--color-background-primary);color:var(--color-foreground-secondary)}::-webkit-resizer{display:none}.wrapper{position:absolute;inset-block-start:var(--modal-block-start);inset-inline-start:var(--modal-inline-start);z-index:1000;padding:10px;border-radius:15px;border:2px solid var(--color-background-quaternary);box-shadow:0 0 10px #0000004d;overflow:hidden;min-inline-size:25px;min-block-size:200px;resize:both}.wrapper:after{content:"";cursor:se-resize;inline-size:1rem;block-size:1rem;background-image:url("data:image/svg+xml,%3csvg%20width='16.022'%20xmlns='http://www.w3.org/2000/svg'%20height='16.022'%20viewBox='-0.011%20-0.011%2016.022%2016.022'%20fill='none'%3e%3cg%20data-testid='Group'%3e%3cg%20data-testid='Path'%3e%3cpath%20d='M.011%2015.917%2015.937-.011'%20class='fills'/%3e%3cg%20class='strokes'%3e%3cpath%20d='M.011%2015.917%2015.937-.011'%20style='fill:%20none;%20stroke-width:%201;%20stroke:%20rgb(111,%20111,%20111);%20stroke-opacity:%201;%20stroke-linecap:%20round;'%20class='stroke-shape'/%3e%3c/g%3e%3c/g%3e%3cg%20data-testid='Path'%3e%3cpath%20d='m11.207%2014.601%203.361-3.401'%20class='fills'/%3e%3cg%20class='strokes'%3e%3cpath%20d='m11.207%2014.601%203.361-3.401'%20style='fill:%20none;%20stroke-width:%201;%20stroke:%20rgb(111,%20111,%20111);%20stroke-opacity:%201;%20stroke-linecap:%20round;'%20class='stroke-shape'/%3e%3c/g%3e%3c/g%3e%3cg%20data-testid='Path'%3e%3cpath%20d='m4.884%2016.004%2011.112-11.17'%20class='fills'/%3e%3cg%20class='strokes'%3e%3cpath%20d='m4.884%2016.004%2011.112-11.17'%20style='fill:%20none;%20stroke-width:%201;%20stroke:%20rgb(111,%20111,%20111);%20stroke-opacity:%201;%20stroke-linecap:%20round;'%20class='stroke-shape'/%3e%3c/g%3e%3c/g%3e%3c/g%3e%3c/svg%3e");background-position:center;right:5px;bottom:5px;pointer-events:none;position:absolute}.inner{padding:10px;cursor:grab;box-sizing:border-box;display:flex;flex-direction:column;overflow:hidden;block-size:100%}.inner>*{flex:1}.inner>.header{flex:0}.header{align-items:center;display:flex;justify-content:space-between;border-block-end:2px solid var(--color-background-quaternary);padding-block-end:var(--spacing-4)}button{background:transparent;border:0;cursor:pointer;padding:0}h1{font-size:var(--font-size-s);font-weight:var(--font-weight-bold);margin:0;margin-inline-end:var(--spacing-4);-webkit-user-select:none;user-select:none}iframe{border:none;inline-size:100%;block-size:100%}`;
-function Zc(t, e, r, n, o) {
- const s = document.createElement("plugin-modal");
- s.setTheme(r);
- const { width: i } = Bs(s, n == null ? void 0 : n.width, n == null ? void 0 : n.height), c = {
- blockStart: 40,
- // To be able to resize the element as expected the position must be absolute from the right.
- // This value is the length of the window minus the width of the element plus the width of the design tab.
- inlineStart: window.innerWidth - i - 290
- };
- return s.style.setProperty(
- "--modal-block-start",
- `${c.blockStart}px`
- ), s.style.setProperty(
- "--modal-inline-start",
- `${c.inlineStart}px`
- ), s.setAttribute("title", t), s.setAttribute("iframe-src", e), o && s.setAttribute("allow-downloads", "true"), document.body.appendChild(s), s;
-}
-function Bs(t, e = 335, r = 590) {
- var m;
- let s = (m = t.shadowRoot) == null ? void 0 : m.querySelector(".wrapper"), i = 0, c = 0;
- if (s) {
- let k = s.getBoundingClientRect();
- i = k.x, c = k.y;
- }
- const l = window.innerWidth - 40, u = window.innerHeight - 40;
- e = Math.min(e, l), r = Math.min(r, u), e = Math.max(e, 200), r = Math.max(r, 200);
- let d = 0;
- i + e > l && (d = l - (i + e));
- let f = 0;
- c + r > u && (f = u - (c + r));
- let { x: h, y: p } = zs(t.wrapper);
- return h = h + d, p = p + f, t.wrapper.style.transform = `translate(${h}px, ${p}px)`, t.wrapper.style.width = `${e}px`, t.wrapper.style.height = `${r}px`, { width: e, height: r };
-}
-const zc = `
- `, Bc = 3;
-var We, Rt;
-class Gc extends HTMLElement {
- constructor() {
- super();
- tn(this, We);
- tn(this, Rt);
- this.wrapper = document.createElement("div"), fr(this, We, document.createElement("div")), fr(this, Rt, null), this.attachShadow({ mode: "open" });
- }
- setTheme(r) {
- this.wrapper && this.wrapper.setAttribute("data-theme", r);
- }
- resize(r, n) {
- this.wrapper && Bs(this, r, n);
- }
- disconnectedCallback() {
- var r;
- (r = at(this, Rt)) == null || r.call(this);
- }
- calculateZIndex() {
- const r = document.querySelectorAll("plugin-modal"), n = Array.from(r).filter((s) => s !== this).map((s) => Number(s.style.zIndex)), o = Math.max(...n, Bc);
- this.style.zIndex = (o + 1).toString();
- }
- connectedCallback() {
- const r = this.getAttribute("title"), n = this.getAttribute("iframe-src"), o = this.getAttribute("allow-downloads") || !1;
- if (!r || !n)
- throw new Error("title and iframe-src attributes are required");
- if (!this.shadowRoot)
- throw new Error("Error creating shadow root");
- at(this, We).classList.add("inner"), this.wrapper.classList.add("wrapper"), this.wrapper.style.maxInlineSize = "90vw", this.wrapper.style.maxBlockSize = "90vh", fr(this, Rt, Uc(at(this, We), this.wrapper, () => {
- this.calculateZIndex();
- }));
- const s = document.createElement("div");
- s.classList.add("header");
- const i = document.createElement("h1");
- i.textContent = r, s.appendChild(i);
- const c = document.createElement("button");
- c.setAttribute("type", "button"), c.innerHTML = `${zc}
`, c.addEventListener("click", () => {
- this.shadowRoot && this.shadowRoot.dispatchEvent(
- new CustomEvent("close", {
- composed: !0,
- bubbles: !0
- })
- );
- }), s.appendChild(c);
- const l = document.createElement("iframe");
- l.src = n, l.allow = "", l.sandbox.add(
- "allow-scripts",
- "allow-forms",
- "allow-modals",
- "allow-popups",
- "allow-popups-to-escape-sandbox",
- "allow-storage-access-by-user-activation",
- "allow-same-origin"
- ), o && l.sandbox.add("allow-downloads"), l.addEventListener("load", () => {
- var d;
- (d = this.shadowRoot) == null || d.dispatchEvent(
- new CustomEvent("load", {
- composed: !0,
- bubbles: !0
- })
- );
- }), this.addEventListener("message", (d) => {
- l.contentWindow && l.contentWindow.postMessage(d.detail, "*");
- }), this.shadowRoot.appendChild(this.wrapper), this.wrapper.appendChild(at(this, We)), at(this, We).appendChild(s), at(this, We).appendChild(l);
- const u = document.createElement("style");
- u.textContent = jc, this.shadowRoot.appendChild(u), this.calculateZIndex();
- }
- size() {
- const r = Number(this.wrapper.style.width.replace("px", "") || "300"), n = Number(this.wrapper.style.height.replace("px", "") || "400");
- return { width: r, height: n };
- }
-}
-We = new WeakMap(), Rt = new WeakMap();
-customElements.define("plugin-modal", Gc);
-var F;
-(function(t) {
- t.assertEqual = (o) => o;
- function e(o) {
- }
- t.assertIs = e;
- function r(o) {
- throw new Error();
- }
- t.assertNever = r, t.arrayToEnum = (o) => {
- const s = {};
- for (const i of o)
- s[i] = i;
- return s;
- }, t.getValidEnumValues = (o) => {
- const s = t.objectKeys(o).filter((c) => typeof o[o[c]] != "number"), i = {};
- for (const c of s)
- i[c] = o[c];
- return t.objectValues(i);
- }, t.objectValues = (o) => t.objectKeys(o).map(function(s) {
- return o[s];
- }), t.objectKeys = typeof Object.keys == "function" ? (o) => Object.keys(o) : (o) => {
- const s = [];
- for (const i in o)
- Object.prototype.hasOwnProperty.call(o, i) && s.push(i);
- return s;
- }, t.find = (o, s) => {
- for (const i of o)
- if (s(i))
- return i;
- }, t.isInteger = typeof Number.isInteger == "function" ? (o) => Number.isInteger(o) : (o) => typeof o == "number" && isFinite(o) && Math.floor(o) === o;
- function n(o, s = " | ") {
- return o.map((i) => typeof i == "string" ? `'${i}'` : i).join(s);
- }
- t.joinValues = n, t.jsonStringifyReplacer = (o, s) => typeof s == "bigint" ? s.toString() : s;
-})(F || (F = {}));
-var Sn;
-(function(t) {
- t.mergeShapes = (e, r) => ({
- ...e,
- ...r
- // second overwrites first
- });
-})(Sn || (Sn = {}));
-const b = F.arrayToEnum([
- "string",
- "nan",
- "number",
- "integer",
- "float",
- "boolean",
- "date",
- "bigint",
- "symbol",
- "function",
- "undefined",
- "null",
- "array",
- "object",
- "unknown",
- "promise",
- "void",
- "never",
- "map",
- "set"
-]), He = (t) => {
- switch (typeof t) {
- case "undefined":
- return b.undefined;
- case "string":
- return b.string;
- case "number":
- return isNaN(t) ? b.nan : b.number;
- case "boolean":
- return b.boolean;
- case "function":
- return b.function;
- case "bigint":
- return b.bigint;
- case "symbol":
- return b.symbol;
- case "object":
- return Array.isArray(t) ? b.array : t === null ? b.null : t.then && typeof t.then == "function" && t.catch && typeof t.catch == "function" ? b.promise : typeof Map < "u" && t instanceof Map ? b.map : typeof Set < "u" && t instanceof Set ? b.set : typeof Date < "u" && t instanceof Date ? b.date : b.object;
- default:
- return b.unknown;
- }
-}, g = F.arrayToEnum([
- "invalid_type",
- "invalid_literal",
- "custom",
- "invalid_union",
- "invalid_union_discriminator",
- "invalid_enum_value",
- "unrecognized_keys",
- "invalid_arguments",
- "invalid_return_type",
- "invalid_date",
- "invalid_string",
- "too_small",
- "too_big",
- "invalid_intersection_types",
- "not_multiple_of",
- "not_finite"
-]), Vc = (t) => JSON.stringify(t, null, 2).replace(/"([^"]+)":/g, "$1:");
-class ye extends Error {
- get errors() {
- return this.issues;
- }
- constructor(e) {
- super(), this.issues = [], this.addIssue = (n) => {
- this.issues = [...this.issues, n];
- }, this.addIssues = (n = []) => {
- this.issues = [...this.issues, ...n];
- };
- const r = new.target.prototype;
- Object.setPrototypeOf ? Object.setPrototypeOf(this, r) : this.__proto__ = r, this.name = "ZodError", this.issues = e;
- }
- format(e) {
- const r = e || function(s) {
- return s.message;
- }, n = { _errors: [] }, o = (s) => {
- for (const i of s.issues)
- if (i.code === "invalid_union")
- i.unionErrors.map(o);
- else if (i.code === "invalid_return_type")
- o(i.returnTypeError);
- else if (i.code === "invalid_arguments")
- o(i.argumentsError);
- else if (i.path.length === 0)
- n._errors.push(r(i));
- else {
- let c = n, l = 0;
- for (; l < i.path.length; ) {
- const u = i.path[l];
- l === i.path.length - 1 ? (c[u] = c[u] || { _errors: [] }, c[u]._errors.push(r(i))) : c[u] = c[u] || { _errors: [] }, c = c[u], l++;
- }
- }
- };
- return o(this), n;
- }
- static assert(e) {
- if (!(e instanceof ye))
- throw new Error(`Not a ZodError: ${e}`);
- }
- toString() {
- return this.message;
- }
- get message() {
- return JSON.stringify(this.issues, F.jsonStringifyReplacer, 2);
- }
- get isEmpty() {
- return this.issues.length === 0;
- }
- flatten(e = (r) => r.message) {
- const r = {}, n = [];
- for (const o of this.issues)
- o.path.length > 0 ? (r[o.path[0]] = r[o.path[0]] || [], r[o.path[0]].push(e(o))) : n.push(e(o));
- return { formErrors: n, fieldErrors: r };
- }
- get formErrors() {
- return this.flatten();
- }
-}
-ye.create = (t) => new ye(t);
-const Mt = (t, e) => {
- let r;
- switch (t.code) {
- case g.invalid_type:
- t.received === b.undefined ? r = "Required" : r = `Expected ${t.expected}, received ${t.received}`;
- break;
- case g.invalid_literal:
- r = `Invalid literal value, expected ${JSON.stringify(t.expected, F.jsonStringifyReplacer)}`;
- break;
- case g.unrecognized_keys:
- r = `Unrecognized key(s) in object: ${F.joinValues(t.keys, ", ")}`;
- break;
- case g.invalid_union:
- r = "Invalid input";
- break;
- case g.invalid_union_discriminator:
- r = `Invalid discriminator value. Expected ${F.joinValues(t.options)}`;
- break;
- case g.invalid_enum_value:
- r = `Invalid enum value. Expected ${F.joinValues(t.options)}, received '${t.received}'`;
- break;
- case g.invalid_arguments:
- r = "Invalid function arguments";
- break;
- case g.invalid_return_type:
- r = "Invalid function return type";
- break;
- case g.invalid_date:
- r = "Invalid date";
- break;
- case g.invalid_string:
- typeof t.validation == "object" ? "includes" in t.validation ? (r = `Invalid input: must include "${t.validation.includes}"`, typeof t.validation.position == "number" && (r = `${r} at one or more positions greater than or equal to ${t.validation.position}`)) : "startsWith" in t.validation ? r = `Invalid input: must start with "${t.validation.startsWith}"` : "endsWith" in t.validation ? r = `Invalid input: must end with "${t.validation.endsWith}"` : F.assertNever(t.validation) : t.validation !== "regex" ? r = `Invalid ${t.validation}` : r = "Invalid";
- break;
- case g.too_small:
- t.type === "array" ? r = `Array must contain ${t.exact ? "exactly" : t.inclusive ? "at least" : "more than"} ${t.minimum} element(s)` : t.type === "string" ? r = `String must contain ${t.exact ? "exactly" : t.inclusive ? "at least" : "over"} ${t.minimum} character(s)` : t.type === "number" ? r = `Number must be ${t.exact ? "exactly equal to " : t.inclusive ? "greater than or equal to " : "greater than "}${t.minimum}` : t.type === "date" ? r = `Date must be ${t.exact ? "exactly equal to " : t.inclusive ? "greater than or equal to " : "greater than "}${new Date(Number(t.minimum))}` : r = "Invalid input";
- break;
- case g.too_big:
- t.type === "array" ? r = `Array must contain ${t.exact ? "exactly" : t.inclusive ? "at most" : "less than"} ${t.maximum} element(s)` : t.type === "string" ? r = `String must contain ${t.exact ? "exactly" : t.inclusive ? "at most" : "under"} ${t.maximum} character(s)` : t.type === "number" ? r = `Number must be ${t.exact ? "exactly" : t.inclusive ? "less than or equal to" : "less than"} ${t.maximum}` : t.type === "bigint" ? r = `BigInt must be ${t.exact ? "exactly" : t.inclusive ? "less than or equal to" : "less than"} ${t.maximum}` : t.type === "date" ? r = `Date must be ${t.exact ? "exactly" : t.inclusive ? "smaller than or equal to" : "smaller than"} ${new Date(Number(t.maximum))}` : r = "Invalid input";
- break;
- case g.custom:
- r = "Invalid input";
- break;
- case g.invalid_intersection_types:
- r = "Intersection results could not be merged";
- break;
- case g.not_multiple_of:
- r = `Number must be a multiple of ${t.multipleOf}`;
- break;
- case g.not_finite:
- r = "Number must be finite";
- break;
- default:
- r = e.defaultError, F.assertNever(t);
- }
- return { message: r };
-};
-let Gs = Mt;
-function Hc(t) {
- Gs = t;
-}
-function $r() {
- return Gs;
-}
-const Nr = (t) => {
- const { data: e, path: r, errorMaps: n, issueData: o } = t, s = [...r, ...o.path || []], i = {
- ...o,
- path: s
- };
- if (o.message !== void 0)
- return {
- ...o,
- path: s,
- message: o.message
- };
- let c = "";
- const l = n.filter((u) => !!u).slice().reverse();
- for (const u of l)
- c = u(i, { data: e, defaultError: c }).message;
- return {
- ...o,
- path: s,
- message: c
- };
-}, Wc = [];
-function v(t, e) {
- const r = $r(), n = Nr({
- issueData: e,
- data: t.data,
- path: t.path,
- errorMaps: [
- t.common.contextualErrorMap,
- // contextual error map is first priority
- t.schemaErrorMap,
- // then schema-bound map if available
- r,
- // then global override map
- r === Mt ? void 0 : Mt
- // then global default map
- ].filter((o) => !!o)
- });
- t.common.issues.push(n);
-}
-class oe {
- constructor() {
- this.value = "valid";
- }
- dirty() {
- this.value === "valid" && (this.value = "dirty");
- }
- abort() {
- this.value !== "aborted" && (this.value = "aborted");
- }
- static mergeArray(e, r) {
- const n = [];
- for (const o of r) {
- if (o.status === "aborted")
- return $;
- o.status === "dirty" && e.dirty(), n.push(o.value);
- }
- return { status: e.value, value: n };
- }
- static async mergeObjectAsync(e, r) {
- const n = [];
- for (const o of r) {
- const s = await o.key, i = await o.value;
- n.push({
- key: s,
- value: i
- });
- }
- return oe.mergeObjectSync(e, n);
- }
- static mergeObjectSync(e, r) {
- const n = {};
- for (const o of r) {
- const { key: s, value: i } = o;
- if (s.status === "aborted" || i.status === "aborted")
- return $;
- s.status === "dirty" && e.dirty(), i.status === "dirty" && e.dirty(), s.value !== "__proto__" && (typeof i.value < "u" || o.alwaysSet) && (n[s.value] = i.value);
- }
- return { status: e.value, value: n };
- }
-}
-const $ = Object.freeze({
- status: "aborted"
-}), Pt = (t) => ({ status: "dirty", value: t }), fe = (t) => ({ status: "valid", value: t }), En = (t) => t.status === "aborted", kn = (t) => t.status === "dirty", yt = (t) => t.status === "valid", qt = (t) => typeof Promise < "u" && t instanceof Promise;
-function Or(t, e, r, n) {
- if (typeof e == "function" ? t !== e || !0 : !e.has(t)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
- return e.get(t);
-}
-function Vs(t, e, r, n, o) {
- if (typeof e == "function" ? t !== e || !0 : !e.has(t)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
- return e.set(t, r), r;
-}
-var E;
-(function(t) {
- t.errToObj = (e) => typeof e == "string" ? { message: e } : e || {}, t.toString = (e) => typeof e == "string" ? e : e == null ? void 0 : e.message;
-})(E || (E = {}));
-var Vt, Ht;
-class je {
- constructor(e, r, n, o) {
- this._cachedPath = [], this.parent = e, this.data = r, this._path = n, this._key = o;
- }
- get path() {
- return this._cachedPath.length || (this._key instanceof Array ? this._cachedPath.push(...this._path, ...this._key) : this._cachedPath.push(...this._path, this._key)), this._cachedPath;
- }
-}
-const xo = (t, e) => {
- if (yt(e))
- return { success: !0, data: e.value };
- if (!t.common.issues.length)
- throw new Error("Validation failed but no issues detected.");
- return {
- success: !1,
- get error() {
- if (this._error)
- return this._error;
- const r = new ye(t.common.issues);
- return this._error = r, this._error;
- }
- };
-};
-function N(t) {
- if (!t)
- return {};
- const { errorMap: e, invalid_type_error: r, required_error: n, description: o } = t;
- if (e && (r || n))
- throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);
- return e ? { errorMap: e, description: o } : { errorMap: (i, c) => {
- var l, u;
- const { message: d } = t;
- return i.code === "invalid_enum_value" ? { message: d ?? c.defaultError } : typeof c.data > "u" ? { message: (l = d ?? n) !== null && l !== void 0 ? l : c.defaultError } : i.code !== "invalid_type" ? { message: c.defaultError } : { message: (u = d ?? r) !== null && u !== void 0 ? u : c.defaultError };
- }, description: o };
-}
-class O {
- get description() {
- return this._def.description;
- }
- _getType(e) {
- return He(e.data);
- }
- _getOrReturnCtx(e, r) {
- return r || {
- common: e.parent.common,
- data: e.data,
- parsedType: He(e.data),
- schemaErrorMap: this._def.errorMap,
- path: e.path,
- parent: e.parent
- };
- }
- _processInputParams(e) {
- return {
- status: new oe(),
- ctx: {
- common: e.parent.common,
- data: e.data,
- parsedType: He(e.data),
- schemaErrorMap: this._def.errorMap,
- path: e.path,
- parent: e.parent
- }
- };
- }
- _parseSync(e) {
- const r = this._parse(e);
- if (qt(r))
- throw new Error("Synchronous parse encountered promise.");
- return r;
- }
- _parseAsync(e) {
- const r = this._parse(e);
- return Promise.resolve(r);
- }
- parse(e, r) {
- const n = this.safeParse(e, r);
- if (n.success)
- return n.data;
- throw n.error;
- }
- safeParse(e, r) {
- var n;
- const o = {
- common: {
- issues: [],
- async: (n = r == null ? void 0 : r.async) !== null && n !== void 0 ? n : !1,
- contextualErrorMap: r == null ? void 0 : r.errorMap
- },
- path: (r == null ? void 0 : r.path) || [],
- schemaErrorMap: this._def.errorMap,
- parent: null,
- data: e,
- parsedType: He(e)
- }, s = this._parseSync({ data: e, path: o.path, parent: o });
- return xo(o, s);
- }
- "~validate"(e) {
- var r, n;
- const o = {
- common: {
- issues: [],
- async: !!this["~standard"].async
- },
- path: [],
- schemaErrorMap: this._def.errorMap,
- parent: null,
- data: e,
- parsedType: He(e)
- };
- if (!this["~standard"].async)
- try {
- const s = this._parseSync({ data: e, path: [], parent: o });
- return yt(s) ? {
- value: s.value
- } : {
- issues: o.common.issues
- };
- } catch (s) {
- !((n = (r = s == null ? void 0 : s.message) === null || r === void 0 ? void 0 : r.toLowerCase()) === null || n === void 0) && n.includes("encountered") && (this["~standard"].async = !0), o.common = {
- issues: [],
- async: !0
- };
- }
- return this._parseAsync({ data: e, path: [], parent: o }).then((s) => yt(s) ? {
- value: s.value
- } : {
- issues: o.common.issues
- });
- }
- async parseAsync(e, r) {
- const n = await this.safeParseAsync(e, r);
- if (n.success)
- return n.data;
- throw n.error;
- }
- async safeParseAsync(e, r) {
- const n = {
- common: {
- issues: [],
- contextualErrorMap: r == null ? void 0 : r.errorMap,
- async: !0
- },
- path: (r == null ? void 0 : r.path) || [],
- schemaErrorMap: this._def.errorMap,
- parent: null,
- data: e,
- parsedType: He(e)
- }, o = this._parse({ data: e, path: n.path, parent: n }), s = await (qt(o) ? o : Promise.resolve(o));
- return xo(n, s);
- }
- refine(e, r) {
- const n = (o) => typeof r == "string" || typeof r > "u" ? { message: r } : typeof r == "function" ? r(o) : r;
- return this._refinement((o, s) => {
- const i = e(o), c = () => s.addIssue({
- code: g.custom,
- ...n(o)
- });
- return typeof Promise < "u" && i instanceof Promise ? i.then((l) => l ? !0 : (c(), !1)) : i ? !0 : (c(), !1);
- });
- }
- refinement(e, r) {
- return this._refinement((n, o) => e(n) ? !0 : (o.addIssue(typeof r == "function" ? r(n, o) : r), !1));
- }
- _refinement(e) {
- return new Oe({
- schema: this,
- typeName: C.ZodEffects,
- effect: { type: "refinement", refinement: e }
- });
- }
- superRefine(e) {
- return this._refinement(e);
- }
- constructor(e) {
- this.spa = this.safeParseAsync, this._def = e, this.parse = this.parse.bind(this), this.safeParse = this.safeParse.bind(this), this.parseAsync = this.parseAsync.bind(this), this.safeParseAsync = this.safeParseAsync.bind(this), this.spa = this.spa.bind(this), this.refine = this.refine.bind(this), this.refinement = this.refinement.bind(this), this.superRefine = this.superRefine.bind(this), this.optional = this.optional.bind(this), this.nullable = this.nullable.bind(this), this.nullish = this.nullish.bind(this), this.array = this.array.bind(this), this.promise = this.promise.bind(this), this.or = this.or.bind(this), this.and = this.and.bind(this), this.transform = this.transform.bind(this), this.brand = this.brand.bind(this), this.default = this.default.bind(this), this.catch = this.catch.bind(this), this.describe = this.describe.bind(this), this.pipe = this.pipe.bind(this), this.readonly = this.readonly.bind(this), this.isNullable = this.isNullable.bind(this), this.isOptional = this.isOptional.bind(this), this["~standard"] = {
- version: 1,
- vendor: "zod",
- validate: (r) => this["~validate"](r)
- };
- }
- optional() {
- return Ue.create(this, this._def);
- }
- nullable() {
- return ot.create(this, this._def);
- }
- nullish() {
- return this.nullable().optional();
- }
- array() {
- return Ne.create(this);
- }
- promise() {
- return Ft.create(this, this._def);
- }
- or(e) {
- return Xt.create([this, e], this._def);
- }
- and(e) {
- return Qt.create(this, e, this._def);
- }
- transform(e) {
- return new Oe({
- ...N(this._def),
- schema: this,
- typeName: C.ZodEffects,
- effect: { type: "transform", transform: e }
- });
- }
- default(e) {
- const r = typeof e == "function" ? e : () => e;
- return new or({
- ...N(this._def),
- innerType: this,
- defaultValue: r,
- typeName: C.ZodDefault
- });
- }
- brand() {
- return new Hn({
- typeName: C.ZodBranded,
- type: this,
- ...N(this._def)
- });
- }
- catch(e) {
- const r = typeof e == "function" ? e : () => e;
- return new sr({
- ...N(this._def),
- innerType: this,
- catchValue: r,
- typeName: C.ZodCatch
- });
- }
- describe(e) {
- const r = this.constructor;
- return new r({
- ...this._def,
- description: e
- });
- }
- pipe(e) {
- return ur.create(this, e);
- }
- readonly() {
- return ar.create(this);
- }
- isOptional() {
- return this.safeParse(void 0).success;
- }
- isNullable() {
- return this.safeParse(null).success;
- }
-}
-const qc = /^c[^\s-]{8,}$/i, Kc = /^[0-9a-z]+$/, Yc = /^[0-9A-HJKMNP-TV-Z]{26}$/i, Jc = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i, Xc = /^[a-z0-9_-]{21}$/i, Qc = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/, el = /^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/, tl = /^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i, rl = "^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$";
-let hn;
-const nl = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/, ol = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/(3[0-2]|[12]?[0-9])$/, sl = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/, al = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/, il = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/, cl = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/, Hs = "((\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\d|30)|(02)-(0[1-9]|1\\d|2[0-8])))", ll = new RegExp(`^${Hs}$`);
-function Ws(t) {
- let e = "([01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d";
- return t.precision ? e = `${e}\\.\\d{${t.precision}}` : t.precision == null && (e = `${e}(\\.\\d+)?`), e;
-}
-function ul(t) {
- return new RegExp(`^${Ws(t)}$`);
-}
-function qs(t) {
- let e = `${Hs}T${Ws(t)}`;
- const r = [];
- return r.push(t.local ? "Z?" : "Z"), t.offset && r.push("([+-]\\d{2}:?\\d{2})"), e = `${e}(${r.join("|")})`, new RegExp(`^${e}$`);
-}
-function dl(t, e) {
- return !!((e === "v4" || !e) && nl.test(t) || (e === "v6" || !e) && sl.test(t));
-}
-function fl(t, e) {
- if (!Qc.test(t))
- return !1;
- try {
- const [r] = t.split("."), n = r.replace(/-/g, "+").replace(/_/g, "/").padEnd(r.length + (4 - r.length % 4) % 4, "="), o = JSON.parse(atob(n));
- return !(typeof o != "object" || o === null || !o.typ || !o.alg || e && o.alg !== e);
- } catch {
- return !1;
- }
-}
-function pl(t, e) {
- return !!((e === "v4" || !e) && ol.test(t) || (e === "v6" || !e) && al.test(t));
-}
-class Re extends O {
- _parse(e) {
- if (this._def.coerce && (e.data = String(e.data)), this._getType(e) !== b.string) {
- const s = this._getOrReturnCtx(e);
- return v(s, {
- code: g.invalid_type,
- expected: b.string,
- received: s.parsedType
- }), $;
- }
- const n = new oe();
- let o;
- for (const s of this._def.checks)
- if (s.kind === "min")
- e.data.length < s.value && (o = this._getOrReturnCtx(e, o), v(o, {
- code: g.too_small,
- minimum: s.value,
- type: "string",
- inclusive: !0,
- exact: !1,
- message: s.message
- }), n.dirty());
- else if (s.kind === "max")
- e.data.length > s.value && (o = this._getOrReturnCtx(e, o), v(o, {
- code: g.too_big,
- maximum: s.value,
- type: "string",
- inclusive: !0,
- exact: !1,
- message: s.message
- }), n.dirty());
- else if (s.kind === "length") {
- const i = e.data.length > s.value, c = e.data.length < s.value;
- (i || c) && (o = this._getOrReturnCtx(e, o), i ? v(o, {
- code: g.too_big,
- maximum: s.value,
- type: "string",
- inclusive: !0,
- exact: !0,
- message: s.message
- }) : c && v(o, {
- code: g.too_small,
- minimum: s.value,
- type: "string",
- inclusive: !0,
- exact: !0,
- message: s.message
- }), n.dirty());
- } else if (s.kind === "email")
- tl.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "email",
- code: g.invalid_string,
- message: s.message
- }), n.dirty());
- else if (s.kind === "emoji")
- hn || (hn = new RegExp(rl, "u")), hn.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "emoji",
- code: g.invalid_string,
- message: s.message
- }), n.dirty());
- else if (s.kind === "uuid")
- Jc.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "uuid",
- code: g.invalid_string,
- message: s.message
- }), n.dirty());
- else if (s.kind === "nanoid")
- Xc.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "nanoid",
- code: g.invalid_string,
- message: s.message
- }), n.dirty());
- else if (s.kind === "cuid")
- qc.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "cuid",
- code: g.invalid_string,
- message: s.message
- }), n.dirty());
- else if (s.kind === "cuid2")
- Kc.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "cuid2",
- code: g.invalid_string,
- message: s.message
- }), n.dirty());
- else if (s.kind === "ulid")
- Yc.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "ulid",
- code: g.invalid_string,
- message: s.message
- }), n.dirty());
- else if (s.kind === "url")
- try {
- new URL(e.data);
- } catch {
- o = this._getOrReturnCtx(e, o), v(o, {
- validation: "url",
- code: g.invalid_string,
- message: s.message
- }), n.dirty();
- }
- else s.kind === "regex" ? (s.regex.lastIndex = 0, s.regex.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "regex",
- code: g.invalid_string,
- message: s.message
- }), n.dirty())) : s.kind === "trim" ? e.data = e.data.trim() : s.kind === "includes" ? e.data.includes(s.value, s.position) || (o = this._getOrReturnCtx(e, o), v(o, {
- code: g.invalid_string,
- validation: { includes: s.value, position: s.position },
- message: s.message
- }), n.dirty()) : s.kind === "toLowerCase" ? e.data = e.data.toLowerCase() : s.kind === "toUpperCase" ? e.data = e.data.toUpperCase() : s.kind === "startsWith" ? e.data.startsWith(s.value) || (o = this._getOrReturnCtx(e, o), v(o, {
- code: g.invalid_string,
- validation: { startsWith: s.value },
- message: s.message
- }), n.dirty()) : s.kind === "endsWith" ? e.data.endsWith(s.value) || (o = this._getOrReturnCtx(e, o), v(o, {
- code: g.invalid_string,
- validation: { endsWith: s.value },
- message: s.message
- }), n.dirty()) : s.kind === "datetime" ? qs(s).test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- code: g.invalid_string,
- validation: "datetime",
- message: s.message
- }), n.dirty()) : s.kind === "date" ? ll.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- code: g.invalid_string,
- validation: "date",
- message: s.message
- }), n.dirty()) : s.kind === "time" ? ul(s).test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- code: g.invalid_string,
- validation: "time",
- message: s.message
- }), n.dirty()) : s.kind === "duration" ? el.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "duration",
- code: g.invalid_string,
- message: s.message
- }), n.dirty()) : s.kind === "ip" ? dl(e.data, s.version) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "ip",
- code: g.invalid_string,
- message: s.message
- }), n.dirty()) : s.kind === "jwt" ? fl(e.data, s.alg) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "jwt",
- code: g.invalid_string,
- message: s.message
- }), n.dirty()) : s.kind === "cidr" ? pl(e.data, s.version) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "cidr",
- code: g.invalid_string,
- message: s.message
- }), n.dirty()) : s.kind === "base64" ? il.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "base64",
- code: g.invalid_string,
- message: s.message
- }), n.dirty()) : s.kind === "base64url" ? cl.test(e.data) || (o = this._getOrReturnCtx(e, o), v(o, {
- validation: "base64url",
- code: g.invalid_string,
- message: s.message
- }), n.dirty()) : F.assertNever(s);
- return { status: n.value, value: e.data };
- }
- _regex(e, r, n) {
- return this.refinement((o) => e.test(o), {
- validation: r,
- code: g.invalid_string,
- ...E.errToObj(n)
- });
- }
- _addCheck(e) {
- return new Re({
- ...this._def,
- checks: [...this._def.checks, e]
- });
- }
- email(e) {
- return this._addCheck({ kind: "email", ...E.errToObj(e) });
- }
- url(e) {
- return this._addCheck({ kind: "url", ...E.errToObj(e) });
- }
- emoji(e) {
- return this._addCheck({ kind: "emoji", ...E.errToObj(e) });
- }
- uuid(e) {
- return this._addCheck({ kind: "uuid", ...E.errToObj(e) });
- }
- nanoid(e) {
- return this._addCheck({ kind: "nanoid", ...E.errToObj(e) });
- }
- cuid(e) {
- return this._addCheck({ kind: "cuid", ...E.errToObj(e) });
- }
- cuid2(e) {
- return this._addCheck({ kind: "cuid2", ...E.errToObj(e) });
- }
- ulid(e) {
- return this._addCheck({ kind: "ulid", ...E.errToObj(e) });
- }
- base64(e) {
- return this._addCheck({ kind: "base64", ...E.errToObj(e) });
- }
- base64url(e) {
- return this._addCheck({
- kind: "base64url",
- ...E.errToObj(e)
- });
- }
- jwt(e) {
- return this._addCheck({ kind: "jwt", ...E.errToObj(e) });
- }
- ip(e) {
- return this._addCheck({ kind: "ip", ...E.errToObj(e) });
- }
- cidr(e) {
- return this._addCheck({ kind: "cidr", ...E.errToObj(e) });
- }
- datetime(e) {
- var r, n;
- return typeof e == "string" ? this._addCheck({
- kind: "datetime",
- precision: null,
- offset: !1,
- local: !1,
- message: e
- }) : this._addCheck({
- kind: "datetime",
- precision: typeof (e == null ? void 0 : e.precision) > "u" ? null : e == null ? void 0 : e.precision,
- offset: (r = e == null ? void 0 : e.offset) !== null && r !== void 0 ? r : !1,
- local: (n = e == null ? void 0 : e.local) !== null && n !== void 0 ? n : !1,
- ...E.errToObj(e == null ? void 0 : e.message)
- });
- }
- date(e) {
- return this._addCheck({ kind: "date", message: e });
- }
- time(e) {
- return typeof e == "string" ? this._addCheck({
- kind: "time",
- precision: null,
- message: e
- }) : this._addCheck({
- kind: "time",
- precision: typeof (e == null ? void 0 : e.precision) > "u" ? null : e == null ? void 0 : e.precision,
- ...E.errToObj(e == null ? void 0 : e.message)
- });
- }
- duration(e) {
- return this._addCheck({ kind: "duration", ...E.errToObj(e) });
- }
- regex(e, r) {
- return this._addCheck({
- kind: "regex",
- regex: e,
- ...E.errToObj(r)
- });
- }
- includes(e, r) {
- return this._addCheck({
- kind: "includes",
- value: e,
- position: r == null ? void 0 : r.position,
- ...E.errToObj(r == null ? void 0 : r.message)
- });
- }
- startsWith(e, r) {
- return this._addCheck({
- kind: "startsWith",
- value: e,
- ...E.errToObj(r)
- });
- }
- endsWith(e, r) {
- return this._addCheck({
- kind: "endsWith",
- value: e,
- ...E.errToObj(r)
- });
- }
- min(e, r) {
- return this._addCheck({
- kind: "min",
- value: e,
- ...E.errToObj(r)
- });
- }
- max(e, r) {
- return this._addCheck({
- kind: "max",
- value: e,
- ...E.errToObj(r)
- });
- }
- length(e, r) {
- return this._addCheck({
- kind: "length",
- value: e,
- ...E.errToObj(r)
- });
- }
- /**
- * Equivalent to `.min(1)`
- */
- nonempty(e) {
- return this.min(1, E.errToObj(e));
- }
- trim() {
- return new Re({
- ...this._def,
- checks: [...this._def.checks, { kind: "trim" }]
- });
- }
- toLowerCase() {
- return new Re({
- ...this._def,
- checks: [...this._def.checks, { kind: "toLowerCase" }]
- });
- }
- toUpperCase() {
- return new Re({
- ...this._def,
- checks: [...this._def.checks, { kind: "toUpperCase" }]
- });
- }
- get isDatetime() {
- return !!this._def.checks.find((e) => e.kind === "datetime");
- }
- get isDate() {
- return !!this._def.checks.find((e) => e.kind === "date");
- }
- get isTime() {
- return !!this._def.checks.find((e) => e.kind === "time");
- }
- get isDuration() {
- return !!this._def.checks.find((e) => e.kind === "duration");
- }
- get isEmail() {
- return !!this._def.checks.find((e) => e.kind === "email");
- }
- get isURL() {
- return !!this._def.checks.find((e) => e.kind === "url");
- }
- get isEmoji() {
- return !!this._def.checks.find((e) => e.kind === "emoji");
- }
- get isUUID() {
- return !!this._def.checks.find((e) => e.kind === "uuid");
- }
- get isNANOID() {
- return !!this._def.checks.find((e) => e.kind === "nanoid");
- }
- get isCUID() {
- return !!this._def.checks.find((e) => e.kind === "cuid");
- }
- get isCUID2() {
- return !!this._def.checks.find((e) => e.kind === "cuid2");
- }
- get isULID() {
- return !!this._def.checks.find((e) => e.kind === "ulid");
- }
- get isIP() {
- return !!this._def.checks.find((e) => e.kind === "ip");
- }
- get isCIDR() {
- return !!this._def.checks.find((e) => e.kind === "cidr");
- }
- get isBase64() {
- return !!this._def.checks.find((e) => e.kind === "base64");
- }
- get isBase64url() {
- return !!this._def.checks.find((e) => e.kind === "base64url");
- }
- get minLength() {
- let e = null;
- for (const r of this._def.checks)
- r.kind === "min" && (e === null || r.value > e) && (e = r.value);
- return e;
- }
- get maxLength() {
- let e = null;
- for (const r of this._def.checks)
- r.kind === "max" && (e === null || r.value < e) && (e = r.value);
- return e;
- }
-}
-Re.create = (t) => {
- var e;
- return new Re({
- checks: [],
- typeName: C.ZodString,
- coerce: (e = t == null ? void 0 : t.coerce) !== null && e !== void 0 ? e : !1,
- ...N(t)
- });
-};
-function hl(t, e) {
- const r = (t.toString().split(".")[1] || "").length, n = (e.toString().split(".")[1] || "").length, o = r > n ? r : n, s = parseInt(t.toFixed(o).replace(".", "")), i = parseInt(e.toFixed(o).replace(".", ""));
- return s % i / Math.pow(10, o);
-}
-class tt extends O {
- constructor() {
- super(...arguments), this.min = this.gte, this.max = this.lte, this.step = this.multipleOf;
- }
- _parse(e) {
- if (this._def.coerce && (e.data = Number(e.data)), this._getType(e) !== b.number) {
- const s = this._getOrReturnCtx(e);
- return v(s, {
- code: g.invalid_type,
- expected: b.number,
- received: s.parsedType
- }), $;
- }
- let n;
- const o = new oe();
- for (const s of this._def.checks)
- s.kind === "int" ? F.isInteger(e.data) || (n = this._getOrReturnCtx(e, n), v(n, {
- code: g.invalid_type,
- expected: "integer",
- received: "float",
- message: s.message
- }), o.dirty()) : s.kind === "min" ? (s.inclusive ? e.data < s.value : e.data <= s.value) && (n = this._getOrReturnCtx(e, n), v(n, {
- code: g.too_small,
- minimum: s.value,
- type: "number",
- inclusive: s.inclusive,
- exact: !1,
- message: s.message
- }), o.dirty()) : s.kind === "max" ? (s.inclusive ? e.data > s.value : e.data >= s.value) && (n = this._getOrReturnCtx(e, n), v(n, {
- code: g.too_big,
- maximum: s.value,
- type: "number",
- inclusive: s.inclusive,
- exact: !1,
- message: s.message
- }), o.dirty()) : s.kind === "multipleOf" ? hl(e.data, s.value) !== 0 && (n = this._getOrReturnCtx(e, n), v(n, {
- code: g.not_multiple_of,
- multipleOf: s.value,
- message: s.message
- }), o.dirty()) : s.kind === "finite" ? Number.isFinite(e.data) || (n = this._getOrReturnCtx(e, n), v(n, {
- code: g.not_finite,
- message: s.message
- }), o.dirty()) : F.assertNever(s);
- return { status: o.value, value: e.data };
- }
- gte(e, r) {
- return this.setLimit("min", e, !0, E.toString(r));
- }
- gt(e, r) {
- return this.setLimit("min", e, !1, E.toString(r));
- }
- lte(e, r) {
- return this.setLimit("max", e, !0, E.toString(r));
- }
- lt(e, r) {
- return this.setLimit("max", e, !1, E.toString(r));
- }
- setLimit(e, r, n, o) {
- return new tt({
- ...this._def,
- checks: [
- ...this._def.checks,
- {
- kind: e,
- value: r,
- inclusive: n,
- message: E.toString(o)
- }
- ]
- });
- }
- _addCheck(e) {
- return new tt({
- ...this._def,
- checks: [...this._def.checks, e]
- });
- }
- int(e) {
- return this._addCheck({
- kind: "int",
- message: E.toString(e)
- });
- }
- positive(e) {
- return this._addCheck({
- kind: "min",
- value: 0,
- inclusive: !1,
- message: E.toString(e)
- });
- }
- negative(e) {
- return this._addCheck({
- kind: "max",
- value: 0,
- inclusive: !1,
- message: E.toString(e)
- });
- }
- nonpositive(e) {
- return this._addCheck({
- kind: "max",
- value: 0,
- inclusive: !0,
- message: E.toString(e)
- });
- }
- nonnegative(e) {
- return this._addCheck({
- kind: "min",
- value: 0,
- inclusive: !0,
- message: E.toString(e)
- });
- }
- multipleOf(e, r) {
- return this._addCheck({
- kind: "multipleOf",
- value: e,
- message: E.toString(r)
- });
- }
- finite(e) {
- return this._addCheck({
- kind: "finite",
- message: E.toString(e)
- });
- }
- safe(e) {
- return this._addCheck({
- kind: "min",
- inclusive: !0,
- value: Number.MIN_SAFE_INTEGER,
- message: E.toString(e)
- })._addCheck({
- kind: "max",
- inclusive: !0,
- value: Number.MAX_SAFE_INTEGER,
- message: E.toString(e)
- });
- }
- get minValue() {
- let e = null;
- for (const r of this._def.checks)
- r.kind === "min" && (e === null || r.value > e) && (e = r.value);
- return e;
- }
- get maxValue() {
- let e = null;
- for (const r of this._def.checks)
- r.kind === "max" && (e === null || r.value < e) && (e = r.value);
- return e;
- }
- get isInt() {
- return !!this._def.checks.find((e) => e.kind === "int" || e.kind === "multipleOf" && F.isInteger(e.value));
- }
- get isFinite() {
- let e = null, r = null;
- for (const n of this._def.checks) {
- if (n.kind === "finite" || n.kind === "int" || n.kind === "multipleOf")
- return !0;
- n.kind === "min" ? (r === null || n.value > r) && (r = n.value) : n.kind === "max" && (e === null || n.value < e) && (e = n.value);
- }
- return Number.isFinite(r) && Number.isFinite(e);
- }
-}
-tt.create = (t) => new tt({
- checks: [],
- typeName: C.ZodNumber,
- coerce: (t == null ? void 0 : t.coerce) || !1,
- ...N(t)
-});
-class rt extends O {
- constructor() {
- super(...arguments), this.min = this.gte, this.max = this.lte;
- }
- _parse(e) {
- if (this._def.coerce)
- try {
- e.data = BigInt(e.data);
- } catch {
- return this._getInvalidInput(e);
- }
- if (this._getType(e) !== b.bigint)
- return this._getInvalidInput(e);
- let n;
- const o = new oe();
- for (const s of this._def.checks)
- s.kind === "min" ? (s.inclusive ? e.data < s.value : e.data <= s.value) && (n = this._getOrReturnCtx(e, n), v(n, {
- code: g.too_small,
- type: "bigint",
- minimum: s.value,
- inclusive: s.inclusive,
- message: s.message
- }), o.dirty()) : s.kind === "max" ? (s.inclusive ? e.data > s.value : e.data >= s.value) && (n = this._getOrReturnCtx(e, n), v(n, {
- code: g.too_big,
- type: "bigint",
- maximum: s.value,
- inclusive: s.inclusive,
- message: s.message
- }), o.dirty()) : s.kind === "multipleOf" ? e.data % s.value !== BigInt(0) && (n = this._getOrReturnCtx(e, n), v(n, {
- code: g.not_multiple_of,
- multipleOf: s.value,
- message: s.message
- }), o.dirty()) : F.assertNever(s);
- return { status: o.value, value: e.data };
- }
- _getInvalidInput(e) {
- const r = this._getOrReturnCtx(e);
- return v(r, {
- code: g.invalid_type,
- expected: b.bigint,
- received: r.parsedType
- }), $;
- }
- gte(e, r) {
- return this.setLimit("min", e, !0, E.toString(r));
- }
- gt(e, r) {
- return this.setLimit("min", e, !1, E.toString(r));
- }
- lte(e, r) {
- return this.setLimit("max", e, !0, E.toString(r));
- }
- lt(e, r) {
- return this.setLimit("max", e, !1, E.toString(r));
- }
- setLimit(e, r, n, o) {
- return new rt({
- ...this._def,
- checks: [
- ...this._def.checks,
- {
- kind: e,
- value: r,
- inclusive: n,
- message: E.toString(o)
- }
- ]
- });
- }
- _addCheck(e) {
- return new rt({
- ...this._def,
- checks: [...this._def.checks, e]
- });
- }
- positive(e) {
- return this._addCheck({
- kind: "min",
- value: BigInt(0),
- inclusive: !1,
- message: E.toString(e)
- });
- }
- negative(e) {
- return this._addCheck({
- kind: "max",
- value: BigInt(0),
- inclusive: !1,
- message: E.toString(e)
- });
- }
- nonpositive(e) {
- return this._addCheck({
- kind: "max",
- value: BigInt(0),
- inclusive: !0,
- message: E.toString(e)
- });
- }
- nonnegative(e) {
- return this._addCheck({
- kind: "min",
- value: BigInt(0),
- inclusive: !0,
- message: E.toString(e)
- });
- }
- multipleOf(e, r) {
- return this._addCheck({
- kind: "multipleOf",
- value: e,
- message: E.toString(r)
- });
- }
- get minValue() {
- let e = null;
- for (const r of this._def.checks)
- r.kind === "min" && (e === null || r.value > e) && (e = r.value);
- return e;
- }
- get maxValue() {
- let e = null;
- for (const r of this._def.checks)
- r.kind === "max" && (e === null || r.value < e) && (e = r.value);
- return e;
- }
-}
-rt.create = (t) => {
- var e;
- return new rt({
- checks: [],
- typeName: C.ZodBigInt,
- coerce: (e = t == null ? void 0 : t.coerce) !== null && e !== void 0 ? e : !1,
- ...N(t)
- });
-};
-class Kt extends O {
- _parse(e) {
- if (this._def.coerce && (e.data = !!e.data), this._getType(e) !== b.boolean) {
- const n = this._getOrReturnCtx(e);
- return v(n, {
- code: g.invalid_type,
- expected: b.boolean,
- received: n.parsedType
- }), $;
- }
- return fe(e.data);
- }
-}
-Kt.create = (t) => new Kt({
- typeName: C.ZodBoolean,
- coerce: (t == null ? void 0 : t.coerce) || !1,
- ...N(t)
-});
-class _t extends O {
- _parse(e) {
- if (this._def.coerce && (e.data = new Date(e.data)), this._getType(e) !== b.date) {
- const s = this._getOrReturnCtx(e);
- return v(s, {
- code: g.invalid_type,
- expected: b.date,
- received: s.parsedType
- }), $;
- }
- if (isNaN(e.data.getTime())) {
- const s = this._getOrReturnCtx(e);
- return v(s, {
- code: g.invalid_date
- }), $;
- }
- const n = new oe();
- let o;
- for (const s of this._def.checks)
- s.kind === "min" ? e.data.getTime() < s.value && (o = this._getOrReturnCtx(e, o), v(o, {
- code: g.too_small,
- message: s.message,
- inclusive: !0,
- exact: !1,
- minimum: s.value,
- type: "date"
- }), n.dirty()) : s.kind === "max" ? e.data.getTime() > s.value && (o = this._getOrReturnCtx(e, o), v(o, {
- code: g.too_big,
- message: s.message,
- inclusive: !0,
- exact: !1,
- maximum: s.value,
- type: "date"
- }), n.dirty()) : F.assertNever(s);
- return {
- status: n.value,
- value: new Date(e.data.getTime())
- };
- }
- _addCheck(e) {
- return new _t({
- ...this._def,
- checks: [...this._def.checks, e]
- });
- }
- min(e, r) {
- return this._addCheck({
- kind: "min",
- value: e.getTime(),
- message: E.toString(r)
- });
- }
- max(e, r) {
- return this._addCheck({
- kind: "max",
- value: e.getTime(),
- message: E.toString(r)
- });
- }
- get minDate() {
- let e = null;
- for (const r of this._def.checks)
- r.kind === "min" && (e === null || r.value > e) && (e = r.value);
- return e != null ? new Date(e) : null;
- }
- get maxDate() {
- let e = null;
- for (const r of this._def.checks)
- r.kind === "max" && (e === null || r.value < e) && (e = r.value);
- return e != null ? new Date(e) : null;
- }
-}
-_t.create = (t) => new _t({
- checks: [],
- coerce: (t == null ? void 0 : t.coerce) || !1,
- typeName: C.ZodDate,
- ...N(t)
-});
-class Mr extends O {
- _parse(e) {
- if (this._getType(e) !== b.symbol) {
- const n = this._getOrReturnCtx(e);
- return v(n, {
- code: g.invalid_type,
- expected: b.symbol,
- received: n.parsedType
- }), $;
- }
- return fe(e.data);
- }
-}
-Mr.create = (t) => new Mr({
- typeName: C.ZodSymbol,
- ...N(t)
-});
-class Yt extends O {
- _parse(e) {
- if (this._getType(e) !== b.undefined) {
- const n = this._getOrReturnCtx(e);
- return v(n, {
- code: g.invalid_type,
- expected: b.undefined,
- received: n.parsedType
- }), $;
- }
- return fe(e.data);
- }
-}
-Yt.create = (t) => new Yt({
- typeName: C.ZodUndefined,
- ...N(t)
-});
-class Jt extends O {
- _parse(e) {
- if (this._getType(e) !== b.null) {
- const n = this._getOrReturnCtx(e);
- return v(n, {
- code: g.invalid_type,
- expected: b.null,
- received: n.parsedType
- }), $;
- }
- return fe(e.data);
- }
-}
-Jt.create = (t) => new Jt({
- typeName: C.ZodNull,
- ...N(t)
-});
-class Lt extends O {
- constructor() {
- super(...arguments), this._any = !0;
- }
- _parse(e) {
- return fe(e.data);
- }
-}
-Lt.create = (t) => new Lt({
- typeName: C.ZodAny,
- ...N(t)
-});
-class mt extends O {
- constructor() {
- super(...arguments), this._unknown = !0;
- }
- _parse(e) {
- return fe(e.data);
- }
-}
-mt.create = (t) => new mt({
- typeName: C.ZodUnknown,
- ...N(t)
-});
-class Ye extends O {
- _parse(e) {
- const r = this._getOrReturnCtx(e);
- return v(r, {
- code: g.invalid_type,
- expected: b.never,
- received: r.parsedType
- }), $;
- }
-}
-Ye.create = (t) => new Ye({
- typeName: C.ZodNever,
- ...N(t)
-});
-class Lr extends O {
- _parse(e) {
- if (this._getType(e) !== b.undefined) {
- const n = this._getOrReturnCtx(e);
- return v(n, {
- code: g.invalid_type,
- expected: b.void,
- received: n.parsedType
- }), $;
- }
- return fe(e.data);
- }
-}
-Lr.create = (t) => new Lr({
- typeName: C.ZodVoid,
- ...N(t)
-});
-class Ne extends O {
- _parse(e) {
- const { ctx: r, status: n } = this._processInputParams(e), o = this._def;
- if (r.parsedType !== b.array)
- return v(r, {
- code: g.invalid_type,
- expected: b.array,
- received: r.parsedType
- }), $;
- if (o.exactLength !== null) {
- const i = r.data.length > o.exactLength.value, c = r.data.length < o.exactLength.value;
- (i || c) && (v(r, {
- code: i ? g.too_big : g.too_small,
- minimum: c ? o.exactLength.value : void 0,
- maximum: i ? o.exactLength.value : void 0,
- type: "array",
- inclusive: !0,
- exact: !0,
- message: o.exactLength.message
- }), n.dirty());
- }
- if (o.minLength !== null && r.data.length < o.minLength.value && (v(r, {
- code: g.too_small,
- minimum: o.minLength.value,
- type: "array",
- inclusive: !0,
- exact: !1,
- message: o.minLength.message
- }), n.dirty()), o.maxLength !== null && r.data.length > o.maxLength.value && (v(r, {
- code: g.too_big,
- maximum: o.maxLength.value,
- type: "array",
- inclusive: !0,
- exact: !1,
- message: o.maxLength.message
- }), n.dirty()), r.common.async)
- return Promise.all([...r.data].map((i, c) => o.type._parseAsync(new je(r, i, r.path, c)))).then((i) => oe.mergeArray(n, i));
- const s = [...r.data].map((i, c) => o.type._parseSync(new je(r, i, r.path, c)));
- return oe.mergeArray(n, s);
- }
- get element() {
- return this._def.type;
- }
- min(e, r) {
- return new Ne({
- ...this._def,
- minLength: { value: e, message: E.toString(r) }
- });
- }
- max(e, r) {
- return new Ne({
- ...this._def,
- maxLength: { value: e, message: E.toString(r) }
- });
- }
- length(e, r) {
- return new Ne({
- ...this._def,
- exactLength: { value: e, message: E.toString(r) }
- });
- }
- nonempty(e) {
- return this.min(1, e);
- }
-}
-Ne.create = (t, e) => new Ne({
- type: t,
- minLength: null,
- maxLength: null,
- exactLength: null,
- typeName: C.ZodArray,
- ...N(e)
-});
-function kt(t) {
- if (t instanceof V) {
- const e = {};
- for (const r in t.shape) {
- const n = t.shape[r];
- e[r] = Ue.create(kt(n));
- }
- return new V({
- ...t._def,
- shape: () => e
- });
- } else return t instanceof Ne ? new Ne({
- ...t._def,
- type: kt(t.element)
- }) : t instanceof Ue ? Ue.create(kt(t.unwrap())) : t instanceof ot ? ot.create(kt(t.unwrap())) : t instanceof Ze ? Ze.create(t.items.map((e) => kt(e))) : t;
-}
-class V extends O {
- constructor() {
- super(...arguments), this._cached = null, this.nonstrict = this.passthrough, this.augment = this.extend;
- }
- _getCached() {
- if (this._cached !== null)
- return this._cached;
- const e = this._def.shape(), r = F.objectKeys(e);
- return this._cached = { shape: e, keys: r };
- }
- _parse(e) {
- if (this._getType(e) !== b.object) {
- const u = this._getOrReturnCtx(e);
- return v(u, {
- code: g.invalid_type,
- expected: b.object,
- received: u.parsedType
- }), $;
- }
- const { status: n, ctx: o } = this._processInputParams(e), { shape: s, keys: i } = this._getCached(), c = [];
- if (!(this._def.catchall instanceof Ye && this._def.unknownKeys === "strip"))
- for (const u in o.data)
- i.includes(u) || c.push(u);
- const l = [];
- for (const u of i) {
- const d = s[u], f = o.data[u];
- l.push({
- key: { status: "valid", value: u },
- value: d._parse(new je(o, f, o.path, u)),
- alwaysSet: u in o.data
- });
- }
- if (this._def.catchall instanceof Ye) {
- const u = this._def.unknownKeys;
- if (u === "passthrough")
- for (const d of c)
- l.push({
- key: { status: "valid", value: d },
- value: { status: "valid", value: o.data[d] }
- });
- else if (u === "strict")
- c.length > 0 && (v(o, {
- code: g.unrecognized_keys,
- keys: c
- }), n.dirty());
- else if (u !== "strip") throw new Error("Internal ZodObject error: invalid unknownKeys value.");
- } else {
- const u = this._def.catchall;
- for (const d of c) {
- const f = o.data[d];
- l.push({
- key: { status: "valid", value: d },
- value: u._parse(
- new je(o, f, o.path, d)
- //, ctx.child(key), value, getParsedType(value)
- ),
- alwaysSet: d in o.data
- });
- }
- }
- return o.common.async ? Promise.resolve().then(async () => {
- const u = [];
- for (const d of l) {
- const f = await d.key, h = await d.value;
- u.push({
- key: f,
- value: h,
- alwaysSet: d.alwaysSet
- });
- }
- return u;
- }).then((u) => oe.mergeObjectSync(n, u)) : oe.mergeObjectSync(n, l);
- }
- get shape() {
- return this._def.shape();
- }
- strict(e) {
- return E.errToObj, new V({
- ...this._def,
- unknownKeys: "strict",
- ...e !== void 0 ? {
- errorMap: (r, n) => {
- var o, s, i, c;
- const l = (i = (s = (o = this._def).errorMap) === null || s === void 0 ? void 0 : s.call(o, r, n).message) !== null && i !== void 0 ? i : n.defaultError;
- return r.code === "unrecognized_keys" ? {
- message: (c = E.errToObj(e).message) !== null && c !== void 0 ? c : l
- } : {
- message: l
- };
- }
- } : {}
- });
- }
- strip() {
- return new V({
- ...this._def,
- unknownKeys: "strip"
- });
- }
- passthrough() {
- return new V({
- ...this._def,
- unknownKeys: "passthrough"
- });
- }
- // const AugmentFactory =
- // (def: Def) =>
- // (
- // augmentation: Augmentation
- // ): ZodObject<
- // extendShape, Augmentation>,
- // Def["unknownKeys"],
- // Def["catchall"]
- // > => {
- // return new ZodObject({
- // ...def,
- // shape: () => ({
- // ...def.shape(),
- // ...augmentation,
- // }),
- // }) as any;
- // };
- extend(e) {
- return new V({
- ...this._def,
- shape: () => ({
- ...this._def.shape(),
- ...e
- })
- });
- }
- /**
- * Prior to zod@1.0.12 there was a bug in the
- * inferred type of merged objects. Please
- * upgrade if you are experiencing issues.
- */
- merge(e) {
- return new V({
- unknownKeys: e._def.unknownKeys,
- catchall: e._def.catchall,
- shape: () => ({
- ...this._def.shape(),
- ...e._def.shape()
- }),
- typeName: C.ZodObject
- });
- }
- // merge<
- // Incoming extends AnyZodObject,
- // Augmentation extends Incoming["shape"],
- // NewOutput extends {
- // [k in keyof Augmentation | keyof Output]: k extends keyof Augmentation
- // ? Augmentation[k]["_output"]
- // : k extends keyof Output
- // ? Output[k]
- // : never;
- // },
- // NewInput extends {
- // [k in keyof Augmentation | keyof Input]: k extends keyof Augmentation
- // ? Augmentation[k]["_input"]
- // : k extends keyof Input
- // ? Input[k]
- // : never;
- // }
- // >(
- // merging: Incoming
- // ): ZodObject<
- // extendShape>,
- // Incoming["_def"]["unknownKeys"],
- // Incoming["_def"]["catchall"],
- // NewOutput,
- // NewInput
- // > {
- // const merged: any = new ZodObject({
- // unknownKeys: merging._def.unknownKeys,
- // catchall: merging._def.catchall,
- // shape: () =>
- // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()),
- // typeName: ZodFirstPartyTypeKind.ZodObject,
- // }) as any;
- // return merged;
- // }
- setKey(e, r) {
- return this.augment({ [e]: r });
- }
- // merge(
- // merging: Incoming
- // ): //ZodObject = (merging) => {
- // ZodObject<
- // extendShape>,
- // Incoming["_def"]["unknownKeys"],
- // Incoming["_def"]["catchall"]
- // > {
- // // const mergedShape = objectUtil.mergeShapes(
- // // this._def.shape(),
- // // merging._def.shape()
- // // );
- // const merged: any = new ZodObject({
- // unknownKeys: merging._def.unknownKeys,
- // catchall: merging._def.catchall,
- // shape: () =>
- // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()),
- // typeName: ZodFirstPartyTypeKind.ZodObject,
- // }) as any;
- // return merged;
- // }
- catchall(e) {
- return new V({
- ...this._def,
- catchall: e
- });
- }
- pick(e) {
- const r = {};
- return F.objectKeys(e).forEach((n) => {
- e[n] && this.shape[n] && (r[n] = this.shape[n]);
- }), new V({
- ...this._def,
- shape: () => r
- });
- }
- omit(e) {
- const r = {};
- return F.objectKeys(this.shape).forEach((n) => {
- e[n] || (r[n] = this.shape[n]);
- }), new V({
- ...this._def,
- shape: () => r
- });
- }
- /**
- * @deprecated
- */
- deepPartial() {
- return kt(this);
- }
- partial(e) {
- const r = {};
- return F.objectKeys(this.shape).forEach((n) => {
- const o = this.shape[n];
- e && !e[n] ? r[n] = o : r[n] = o.optional();
- }), new V({
- ...this._def,
- shape: () => r
- });
- }
- required(e) {
- const r = {};
- return F.objectKeys(this.shape).forEach((n) => {
- if (e && !e[n])
- r[n] = this.shape[n];
- else {
- let s = this.shape[n];
- for (; s instanceof Ue; )
- s = s._def.innerType;
- r[n] = s;
- }
- }), new V({
- ...this._def,
- shape: () => r
- });
- }
- keyof() {
- return Ks(F.objectKeys(this.shape));
- }
-}
-V.create = (t, e) => new V({
- shape: () => t,
- unknownKeys: "strip",
- catchall: Ye.create(),
- typeName: C.ZodObject,
- ...N(e)
-});
-V.strictCreate = (t, e) => new V({
- shape: () => t,
- unknownKeys: "strict",
- catchall: Ye.create(),
- typeName: C.ZodObject,
- ...N(e)
-});
-V.lazycreate = (t, e) => new V({
- shape: t,
- unknownKeys: "strip",
- catchall: Ye.create(),
- typeName: C.ZodObject,
- ...N(e)
-});
-class Xt extends O {
- _parse(e) {
- const { ctx: r } = this._processInputParams(e), n = this._def.options;
- function o(s) {
- for (const c of s)
- if (c.result.status === "valid")
- return c.result;
- for (const c of s)
- if (c.result.status === "dirty")
- return r.common.issues.push(...c.ctx.common.issues), c.result;
- const i = s.map((c) => new ye(c.ctx.common.issues));
- return v(r, {
- code: g.invalid_union,
- unionErrors: i
- }), $;
- }
- if (r.common.async)
- return Promise.all(n.map(async (s) => {
- const i = {
- ...r,
- common: {
- ...r.common,
- issues: []
- },
- parent: null
- };
- return {
- result: await s._parseAsync({
- data: r.data,
- path: r.path,
- parent: i
- }),
- ctx: i
- };
- })).then(o);
- {
- let s;
- const i = [];
- for (const l of n) {
- const u = {
- ...r,
- common: {
- ...r.common,
- issues: []
- },
- parent: null
- }, d = l._parseSync({
- data: r.data,
- path: r.path,
- parent: u
- });
- if (d.status === "valid")
- return d;
- d.status === "dirty" && !s && (s = { result: d, ctx: u }), u.common.issues.length && i.push(u.common.issues);
- }
- if (s)
- return r.common.issues.push(...s.ctx.common.issues), s.result;
- const c = i.map((l) => new ye(l));
- return v(r, {
- code: g.invalid_union,
- unionErrors: c
- }), $;
- }
- }
- get options() {
- return this._def.options;
- }
-}
-Xt.create = (t, e) => new Xt({
- options: t,
- typeName: C.ZodUnion,
- ...N(e)
-});
-const Ve = (t) => t instanceof tr ? Ve(t.schema) : t instanceof Oe ? Ve(t.innerType()) : t instanceof rr ? [t.value] : t instanceof nt ? t.options : t instanceof nr ? F.objectValues(t.enum) : t instanceof or ? Ve(t._def.innerType) : t instanceof Yt ? [void 0] : t instanceof Jt ? [null] : t instanceof Ue ? [void 0, ...Ve(t.unwrap())] : t instanceof ot ? [null, ...Ve(t.unwrap())] : t instanceof Hn || t instanceof ar ? Ve(t.unwrap()) : t instanceof sr ? Ve(t._def.innerType) : [];
-class Qr extends O {
- _parse(e) {
- const { ctx: r } = this._processInputParams(e);
- if (r.parsedType !== b.object)
- return v(r, {
- code: g.invalid_type,
- expected: b.object,
- received: r.parsedType
- }), $;
- const n = this.discriminator, o = r.data[n], s = this.optionsMap.get(o);
- return s ? r.common.async ? s._parseAsync({
- data: r.data,
- path: r.path,
- parent: r
- }) : s._parseSync({
- data: r.data,
- path: r.path,
- parent: r
- }) : (v(r, {
- code: g.invalid_union_discriminator,
- options: Array.from(this.optionsMap.keys()),
- path: [n]
- }), $);
- }
- get discriminator() {
- return this._def.discriminator;
- }
- get options() {
- return this._def.options;
- }
- get optionsMap() {
- return this._def.optionsMap;
- }
- /**
- * The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor.
- * However, it only allows a union of objects, all of which need to share a discriminator property. This property must
- * have a different value for each object in the union.
- * @param discriminator the name of the discriminator property
- * @param types an array of object schemas
- * @param params
- */
- static create(e, r, n) {
- const o = /* @__PURE__ */ new Map();
- for (const s of r) {
- const i = Ve(s.shape[e]);
- if (!i.length)
- throw new Error(`A discriminator value for key \`${e}\` could not be extracted from all schema options`);
- for (const c of i) {
- if (o.has(c))
- throw new Error(`Discriminator property ${String(e)} has duplicate value ${String(c)}`);
- o.set(c, s);
- }
- }
- return new Qr({
- typeName: C.ZodDiscriminatedUnion,
- discriminator: e,
- options: r,
- optionsMap: o,
- ...N(n)
- });
- }
-}
-function Pn(t, e) {
- const r = He(t), n = He(e);
- if (t === e)
- return { valid: !0, data: t };
- if (r === b.object && n === b.object) {
- const o = F.objectKeys(e), s = F.objectKeys(t).filter((c) => o.indexOf(c) !== -1), i = { ...t, ...e };
- for (const c of s) {
- const l = Pn(t[c], e[c]);
- if (!l.valid)
- return { valid: !1 };
- i[c] = l.data;
- }
- return { valid: !0, data: i };
- } else if (r === b.array && n === b.array) {
- if (t.length !== e.length)
- return { valid: !1 };
- const o = [];
- for (let s = 0; s < t.length; s++) {
- const i = t[s], c = e[s], l = Pn(i, c);
- if (!l.valid)
- return { valid: !1 };
- o.push(l.data);
- }
- return { valid: !0, data: o };
- } else return r === b.date && n === b.date && +t == +e ? { valid: !0, data: t } : { valid: !1 };
-}
-class Qt extends O {
- _parse(e) {
- const { status: r, ctx: n } = this._processInputParams(e), o = (s, i) => {
- if (En(s) || En(i))
- return $;
- const c = Pn(s.value, i.value);
- return c.valid ? ((kn(s) || kn(i)) && r.dirty(), { status: r.value, value: c.data }) : (v(n, {
- code: g.invalid_intersection_types
- }), $);
- };
- return n.common.async ? Promise.all([
- this._def.left._parseAsync({
- data: n.data,
- path: n.path,
- parent: n
- }),
- this._def.right._parseAsync({
- data: n.data,
- path: n.path,
- parent: n
- })
- ]).then(([s, i]) => o(s, i)) : o(this._def.left._parseSync({
- data: n.data,
- path: n.path,
- parent: n
- }), this._def.right._parseSync({
- data: n.data,
- path: n.path,
- parent: n
- }));
- }
-}
-Qt.create = (t, e, r) => new Qt({
- left: t,
- right: e,
- typeName: C.ZodIntersection,
- ...N(r)
-});
-class Ze extends O {
- _parse(e) {
- const { status: r, ctx: n } = this._processInputParams(e);
- if (n.parsedType !== b.array)
- return v(n, {
- code: g.invalid_type,
- expected: b.array,
- received: n.parsedType
- }), $;
- if (n.data.length < this._def.items.length)
- return v(n, {
- code: g.too_small,
- minimum: this._def.items.length,
- inclusive: !0,
- exact: !1,
- type: "array"
- }), $;
- !this._def.rest && n.data.length > this._def.items.length && (v(n, {
- code: g.too_big,
- maximum: this._def.items.length,
- inclusive: !0,
- exact: !1,
- type: "array"
- }), r.dirty());
- const s = [...n.data].map((i, c) => {
- const l = this._def.items[c] || this._def.rest;
- return l ? l._parse(new je(n, i, n.path, c)) : null;
- }).filter((i) => !!i);
- return n.common.async ? Promise.all(s).then((i) => oe.mergeArray(r, i)) : oe.mergeArray(r, s);
- }
- get items() {
- return this._def.items;
- }
- rest(e) {
- return new Ze({
- ...this._def,
- rest: e
- });
- }
-}
-Ze.create = (t, e) => {
- if (!Array.isArray(t))
- throw new Error("You must pass an array of schemas to z.tuple([ ... ])");
- return new Ze({
- items: t,
- typeName: C.ZodTuple,
- rest: null,
- ...N(e)
- });
-};
-class er extends O {
- get keySchema() {
- return this._def.keyType;
- }
- get valueSchema() {
- return this._def.valueType;
- }
- _parse(e) {
- const { status: r, ctx: n } = this._processInputParams(e);
- if (n.parsedType !== b.object)
- return v(n, {
- code: g.invalid_type,
- expected: b.object,
- received: n.parsedType
- }), $;
- const o = [], s = this._def.keyType, i = this._def.valueType;
- for (const c in n.data)
- o.push({
- key: s._parse(new je(n, c, n.path, c)),
- value: i._parse(new je(n, n.data[c], n.path, c)),
- alwaysSet: c in n.data
- });
- return n.common.async ? oe.mergeObjectAsync(r, o) : oe.mergeObjectSync(r, o);
- }
- get element() {
- return this._def.valueType;
- }
- static create(e, r, n) {
- return r instanceof O ? new er({
- keyType: e,
- valueType: r,
- typeName: C.ZodRecord,
- ...N(n)
- }) : new er({
- keyType: Re.create(),
- valueType: e,
- typeName: C.ZodRecord,
- ...N(r)
- });
- }
-}
-class Fr extends O {
- get keySchema() {
- return this._def.keyType;
- }
- get valueSchema() {
- return this._def.valueType;
- }
- _parse(e) {
- const { status: r, ctx: n } = this._processInputParams(e);
- if (n.parsedType !== b.map)
- return v(n, {
- code: g.invalid_type,
- expected: b.map,
- received: n.parsedType
- }), $;
- const o = this._def.keyType, s = this._def.valueType, i = [...n.data.entries()].map(([c, l], u) => ({
- key: o._parse(new je(n, c, n.path, [u, "key"])),
- value: s._parse(new je(n, l, n.path, [u, "value"]))
- }));
- if (n.common.async) {
- const c = /* @__PURE__ */ new Map();
- return Promise.resolve().then(async () => {
- for (const l of i) {
- const u = await l.key, d = await l.value;
- if (u.status === "aborted" || d.status === "aborted")
- return $;
- (u.status === "dirty" || d.status === "dirty") && r.dirty(), c.set(u.value, d.value);
- }
- return { status: r.value, value: c };
- });
- } else {
- const c = /* @__PURE__ */ new Map();
- for (const l of i) {
- const u = l.key, d = l.value;
- if (u.status === "aborted" || d.status === "aborted")
- return $;
- (u.status === "dirty" || d.status === "dirty") && r.dirty(), c.set(u.value, d.value);
- }
- return { status: r.value, value: c };
- }
- }
-}
-Fr.create = (t, e, r) => new Fr({
- valueType: e,
- keyType: t,
- typeName: C.ZodMap,
- ...N(r)
-});
-class vt extends O {
- _parse(e) {
- const { status: r, ctx: n } = this._processInputParams(e);
- if (n.parsedType !== b.set)
- return v(n, {
- code: g.invalid_type,
- expected: b.set,
- received: n.parsedType
- }), $;
- const o = this._def;
- o.minSize !== null && n.data.size < o.minSize.value && (v(n, {
- code: g.too_small,
- minimum: o.minSize.value,
- type: "set",
- inclusive: !0,
- exact: !1,
- message: o.minSize.message
- }), r.dirty()), o.maxSize !== null && n.data.size > o.maxSize.value && (v(n, {
- code: g.too_big,
- maximum: o.maxSize.value,
- type: "set",
- inclusive: !0,
- exact: !1,
- message: o.maxSize.message
- }), r.dirty());
- const s = this._def.valueType;
- function i(l) {
- const u = /* @__PURE__ */ new Set();
- for (const d of l) {
- if (d.status === "aborted")
- return $;
- d.status === "dirty" && r.dirty(), u.add(d.value);
- }
- return { status: r.value, value: u };
- }
- const c = [...n.data.values()].map((l, u) => s._parse(new je(n, l, n.path, u)));
- return n.common.async ? Promise.all(c).then((l) => i(l)) : i(c);
- }
- min(e, r) {
- return new vt({
- ...this._def,
- minSize: { value: e, message: E.toString(r) }
- });
- }
- max(e, r) {
- return new vt({
- ...this._def,
- maxSize: { value: e, message: E.toString(r) }
- });
- }
- size(e, r) {
- return this.min(e, r).max(e, r);
- }
- nonempty(e) {
- return this.min(1, e);
- }
-}
-vt.create = (t, e) => new vt({
- valueType: t,
- minSize: null,
- maxSize: null,
- typeName: C.ZodSet,
- ...N(e)
-});
-class Ct extends O {
- constructor() {
- super(...arguments), this.validate = this.implement;
- }
- _parse(e) {
- const { ctx: r } = this._processInputParams(e);
- if (r.parsedType !== b.function)
- return v(r, {
- code: g.invalid_type,
- expected: b.function,
- received: r.parsedType
- }), $;
- function n(c, l) {
- return Nr({
- data: c,
- path: r.path,
- errorMaps: [
- r.common.contextualErrorMap,
- r.schemaErrorMap,
- $r(),
- Mt
- ].filter((u) => !!u),
- issueData: {
- code: g.invalid_arguments,
- argumentsError: l
- }
- });
- }
- function o(c, l) {
- return Nr({
- data: c,
- path: r.path,
- errorMaps: [
- r.common.contextualErrorMap,
- r.schemaErrorMap,
- $r(),
- Mt
- ].filter((u) => !!u),
- issueData: {
- code: g.invalid_return_type,
- returnTypeError: l
- }
- });
- }
- const s = { errorMap: r.common.contextualErrorMap }, i = r.data;
- if (this._def.returns instanceof Ft) {
- const c = this;
- return fe(async function(...l) {
- const u = new ye([]), d = await c._def.args.parseAsync(l, s).catch((p) => {
- throw u.addIssue(n(l, p)), u;
- }), f = await Reflect.apply(i, this, d);
- return await c._def.returns._def.type.parseAsync(f, s).catch((p) => {
- throw u.addIssue(o(f, p)), u;
- });
- });
- } else {
- const c = this;
- return fe(function(...l) {
- const u = c._def.args.safeParse(l, s);
- if (!u.success)
- throw new ye([n(l, u.error)]);
- const d = Reflect.apply(i, this, u.data), f = c._def.returns.safeParse(d, s);
- if (!f.success)
- throw new ye([o(d, f.error)]);
- return f.data;
- });
- }
- }
- parameters() {
- return this._def.args;
- }
- returnType() {
- return this._def.returns;
- }
- args(...e) {
- return new Ct({
- ...this._def,
- args: Ze.create(e).rest(mt.create())
- });
- }
- returns(e) {
- return new Ct({
- ...this._def,
- returns: e
- });
- }
- implement(e) {
- return this.parse(e);
- }
- strictImplement(e) {
- return this.parse(e);
- }
- static create(e, r, n) {
- return new Ct({
- args: e || Ze.create([]).rest(mt.create()),
- returns: r || mt.create(),
- typeName: C.ZodFunction,
- ...N(n)
- });
- }
-}
-class tr extends O {
- get schema() {
- return this._def.getter();
- }
- _parse(e) {
- const { ctx: r } = this._processInputParams(e);
- return this._def.getter()._parse({ data: r.data, path: r.path, parent: r });
- }
-}
-tr.create = (t, e) => new tr({
- getter: t,
- typeName: C.ZodLazy,
- ...N(e)
-});
-class rr extends O {
- _parse(e) {
- if (e.data !== this._def.value) {
- const r = this._getOrReturnCtx(e);
- return v(r, {
- received: r.data,
- code: g.invalid_literal,
- expected: this._def.value
- }), $;
- }
- return { status: "valid", value: e.data };
- }
- get value() {
- return this._def.value;
- }
-}
-rr.create = (t, e) => new rr({
- value: t,
- typeName: C.ZodLiteral,
- ...N(e)
-});
-function Ks(t, e) {
- return new nt({
- values: t,
- typeName: C.ZodEnum,
- ...N(e)
- });
-}
-class nt extends O {
- constructor() {
- super(...arguments), Vt.set(this, void 0);
- }
- _parse(e) {
- if (typeof e.data != "string") {
- const r = this._getOrReturnCtx(e), n = this._def.values;
- return v(r, {
- expected: F.joinValues(n),
- received: r.parsedType,
- code: g.invalid_type
- }), $;
- }
- if (Or(this, Vt) || Vs(this, Vt, new Set(this._def.values)), !Or(this, Vt).has(e.data)) {
- const r = this._getOrReturnCtx(e), n = this._def.values;
- return v(r, {
- received: r.data,
- code: g.invalid_enum_value,
- options: n
- }), $;
- }
- return fe(e.data);
- }
- get options() {
- return this._def.values;
- }
- get enum() {
- const e = {};
- for (const r of this._def.values)
- e[r] = r;
- return e;
- }
- get Values() {
- const e = {};
- for (const r of this._def.values)
- e[r] = r;
- return e;
- }
- get Enum() {
- const e = {};
- for (const r of this._def.values)
- e[r] = r;
- return e;
- }
- extract(e, r = this._def) {
- return nt.create(e, {
- ...this._def,
- ...r
- });
- }
- exclude(e, r = this._def) {
- return nt.create(this.options.filter((n) => !e.includes(n)), {
- ...this._def,
- ...r
- });
- }
-}
-Vt = /* @__PURE__ */ new WeakMap();
-nt.create = Ks;
-class nr extends O {
- constructor() {
- super(...arguments), Ht.set(this, void 0);
- }
- _parse(e) {
- const r = F.getValidEnumValues(this._def.values), n = this._getOrReturnCtx(e);
- if (n.parsedType !== b.string && n.parsedType !== b.number) {
- const o = F.objectValues(r);
- return v(n, {
- expected: F.joinValues(o),
- received: n.parsedType,
- code: g.invalid_type
- }), $;
- }
- if (Or(this, Ht) || Vs(this, Ht, new Set(F.getValidEnumValues(this._def.values))), !Or(this, Ht).has(e.data)) {
- const o = F.objectValues(r);
- return v(n, {
- received: n.data,
- code: g.invalid_enum_value,
- options: o
- }), $;
- }
- return fe(e.data);
- }
- get enum() {
- return this._def.values;
- }
-}
-Ht = /* @__PURE__ */ new WeakMap();
-nr.create = (t, e) => new nr({
- values: t,
- typeName: C.ZodNativeEnum,
- ...N(e)
-});
-class Ft extends O {
- unwrap() {
- return this._def.type;
- }
- _parse(e) {
- const { ctx: r } = this._processInputParams(e);
- if (r.parsedType !== b.promise && r.common.async === !1)
- return v(r, {
- code: g.invalid_type,
- expected: b.promise,
- received: r.parsedType
- }), $;
- const n = r.parsedType === b.promise ? r.data : Promise.resolve(r.data);
- return fe(n.then((o) => this._def.type.parseAsync(o, {
- path: r.path,
- errorMap: r.common.contextualErrorMap
- })));
- }
-}
-Ft.create = (t, e) => new Ft({
- type: t,
- typeName: C.ZodPromise,
- ...N(e)
-});
-class Oe extends O {
- innerType() {
- return this._def.schema;
- }
- sourceType() {
- return this._def.schema._def.typeName === C.ZodEffects ? this._def.schema.sourceType() : this._def.schema;
- }
- _parse(e) {
- const { status: r, ctx: n } = this._processInputParams(e), o = this._def.effect || null, s = {
- addIssue: (i) => {
- v(n, i), i.fatal ? r.abort() : r.dirty();
- },
- get path() {
- return n.path;
- }
- };
- if (s.addIssue = s.addIssue.bind(s), o.type === "preprocess") {
- const i = o.transform(n.data, s);
- if (n.common.async)
- return Promise.resolve(i).then(async (c) => {
- if (r.value === "aborted")
- return $;
- const l = await this._def.schema._parseAsync({
- data: c,
- path: n.path,
- parent: n
- });
- return l.status === "aborted" ? $ : l.status === "dirty" || r.value === "dirty" ? Pt(l.value) : l;
- });
- {
- if (r.value === "aborted")
- return $;
- const c = this._def.schema._parseSync({
- data: i,
- path: n.path,
- parent: n
- });
- return c.status === "aborted" ? $ : c.status === "dirty" || r.value === "dirty" ? Pt(c.value) : c;
- }
- }
- if (o.type === "refinement") {
- const i = (c) => {
- const l = o.refinement(c, s);
- if (n.common.async)
- return Promise.resolve(l);
- if (l instanceof Promise)
- throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead.");
- return c;
- };
- if (n.common.async === !1) {
- const c = this._def.schema._parseSync({
- data: n.data,
- path: n.path,
- parent: n
- });
- return c.status === "aborted" ? $ : (c.status === "dirty" && r.dirty(), i(c.value), { status: r.value, value: c.value });
- } else
- return this._def.schema._parseAsync({ data: n.data, path: n.path, parent: n }).then((c) => c.status === "aborted" ? $ : (c.status === "dirty" && r.dirty(), i(c.value).then(() => ({ status: r.value, value: c.value }))));
- }
- if (o.type === "transform")
- if (n.common.async === !1) {
- const i = this._def.schema._parseSync({
- data: n.data,
- path: n.path,
- parent: n
- });
- if (!yt(i))
- return i;
- const c = o.transform(i.value, s);
- if (c instanceof Promise)
- throw new Error("Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.");
- return { status: r.value, value: c };
- } else
- return this._def.schema._parseAsync({ data: n.data, path: n.path, parent: n }).then((i) => yt(i) ? Promise.resolve(o.transform(i.value, s)).then((c) => ({ status: r.value, value: c })) : i);
- F.assertNever(o);
- }
-}
-Oe.create = (t, e, r) => new Oe({
- schema: t,
- typeName: C.ZodEffects,
- effect: e,
- ...N(r)
-});
-Oe.createWithPreprocess = (t, e, r) => new Oe({
- schema: e,
- effect: { type: "preprocess", transform: t },
- typeName: C.ZodEffects,
- ...N(r)
-});
-class Ue extends O {
- _parse(e) {
- return this._getType(e) === b.undefined ? fe(void 0) : this._def.innerType._parse(e);
- }
- unwrap() {
- return this._def.innerType;
- }
-}
-Ue.create = (t, e) => new Ue({
- innerType: t,
- typeName: C.ZodOptional,
- ...N(e)
-});
-class ot extends O {
- _parse(e) {
- return this._getType(e) === b.null ? fe(null) : this._def.innerType._parse(e);
- }
- unwrap() {
- return this._def.innerType;
- }
-}
-ot.create = (t, e) => new ot({
- innerType: t,
- typeName: C.ZodNullable,
- ...N(e)
-});
-class or extends O {
- _parse(e) {
- const { ctx: r } = this._processInputParams(e);
- let n = r.data;
- return r.parsedType === b.undefined && (n = this._def.defaultValue()), this._def.innerType._parse({
- data: n,
- path: r.path,
- parent: r
- });
- }
- removeDefault() {
- return this._def.innerType;
- }
-}
-or.create = (t, e) => new or({
- innerType: t,
- typeName: C.ZodDefault,
- defaultValue: typeof e.default == "function" ? e.default : () => e.default,
- ...N(e)
-});
-class sr extends O {
- _parse(e) {
- const { ctx: r } = this._processInputParams(e), n = {
- ...r,
- common: {
- ...r.common,
- issues: []
- }
- }, o = this._def.innerType._parse({
- data: n.data,
- path: n.path,
- parent: {
- ...n
- }
- });
- return qt(o) ? o.then((s) => ({
- status: "valid",
- value: s.status === "valid" ? s.value : this._def.catchValue({
- get error() {
- return new ye(n.common.issues);
- },
- input: n.data
- })
- })) : {
- status: "valid",
- value: o.status === "valid" ? o.value : this._def.catchValue({
- get error() {
- return new ye(n.common.issues);
- },
- input: n.data
- })
- };
- }
- removeCatch() {
- return this._def.innerType;
- }
-}
-sr.create = (t, e) => new sr({
- innerType: t,
- typeName: C.ZodCatch,
- catchValue: typeof e.catch == "function" ? e.catch : () => e.catch,
- ...N(e)
-});
-class Dr extends O {
- _parse(e) {
- if (this._getType(e) !== b.nan) {
- const n = this._getOrReturnCtx(e);
- return v(n, {
- code: g.invalid_type,
- expected: b.nan,
- received: n.parsedType
- }), $;
- }
- return { status: "valid", value: e.data };
- }
-}
-Dr.create = (t) => new Dr({
- typeName: C.ZodNaN,
- ...N(t)
-});
-const ml = Symbol("zod_brand");
-class Hn extends O {
- _parse(e) {
- const { ctx: r } = this._processInputParams(e), n = r.data;
- return this._def.type._parse({
- data: n,
- path: r.path,
- parent: r
- });
- }
- unwrap() {
- return this._def.type;
- }
-}
-class ur extends O {
- _parse(e) {
- const { status: r, ctx: n } = this._processInputParams(e);
- if (n.common.async)
- return (async () => {
- const s = await this._def.in._parseAsync({
- data: n.data,
- path: n.path,
- parent: n
- });
- return s.status === "aborted" ? $ : s.status === "dirty" ? (r.dirty(), Pt(s.value)) : this._def.out._parseAsync({
- data: s.value,
- path: n.path,
- parent: n
- });
- })();
- {
- const o = this._def.in._parseSync({
- data: n.data,
- path: n.path,
- parent: n
- });
- return o.status === "aborted" ? $ : o.status === "dirty" ? (r.dirty(), {
- status: "dirty",
- value: o.value
- }) : this._def.out._parseSync({
- data: o.value,
- path: n.path,
- parent: n
- });
- }
- }
- static create(e, r) {
- return new ur({
- in: e,
- out: r,
- typeName: C.ZodPipeline
- });
- }
-}
-class ar extends O {
- _parse(e) {
- const r = this._def.innerType._parse(e), n = (o) => (yt(o) && (o.value = Object.freeze(o.value)), o);
- return qt(r) ? r.then((o) => n(o)) : n(r);
- }
- unwrap() {
- return this._def.innerType;
- }
-}
-ar.create = (t, e) => new ar({
- innerType: t,
- typeName: C.ZodReadonly,
- ...N(e)
-});
-function Ys(t, e = {}, r) {
- return t ? Lt.create().superRefine((n, o) => {
- var s, i;
- if (!t(n)) {
- const c = typeof e == "function" ? e(n) : typeof e == "string" ? { message: e } : e, l = (i = (s = c.fatal) !== null && s !== void 0 ? s : r) !== null && i !== void 0 ? i : !0, u = typeof c == "string" ? { message: c } : c;
- o.addIssue({ code: "custom", ...u, fatal: l });
- }
- }) : Lt.create();
-}
-const gl = {
- object: V.lazycreate
-};
-var C;
-(function(t) {
- t.ZodString = "ZodString", t.ZodNumber = "ZodNumber", t.ZodNaN = "ZodNaN", t.ZodBigInt = "ZodBigInt", t.ZodBoolean = "ZodBoolean", t.ZodDate = "ZodDate", t.ZodSymbol = "ZodSymbol", t.ZodUndefined = "ZodUndefined", t.ZodNull = "ZodNull", t.ZodAny = "ZodAny", t.ZodUnknown = "ZodUnknown", t.ZodNever = "ZodNever", t.ZodVoid = "ZodVoid", t.ZodArray = "ZodArray", t.ZodObject = "ZodObject", t.ZodUnion = "ZodUnion", t.ZodDiscriminatedUnion = "ZodDiscriminatedUnion", t.ZodIntersection = "ZodIntersection", t.ZodTuple = "ZodTuple", t.ZodRecord = "ZodRecord", t.ZodMap = "ZodMap", t.ZodSet = "ZodSet", t.ZodFunction = "ZodFunction", t.ZodLazy = "ZodLazy", t.ZodLiteral = "ZodLiteral", t.ZodEnum = "ZodEnum", t.ZodEffects = "ZodEffects", t.ZodNativeEnum = "ZodNativeEnum", t.ZodOptional = "ZodOptional", t.ZodNullable = "ZodNullable", t.ZodDefault = "ZodDefault", t.ZodCatch = "ZodCatch", t.ZodPromise = "ZodPromise", t.ZodBranded = "ZodBranded", t.ZodPipeline = "ZodPipeline", t.ZodReadonly = "ZodReadonly";
-})(C || (C = {}));
-const yl = (t, e = {
- message: `Input not instance of ${t.name}`
-}) => Ys((r) => r instanceof t, e), Js = Re.create, Xs = tt.create, _l = Dr.create, vl = rt.create, Qs = Kt.create, bl = _t.create, wl = Mr.create, xl = Yt.create, Sl = Jt.create, El = Lt.create, kl = mt.create, Pl = Ye.create, Al = Lr.create, Tl = Ne.create, Il = V.create, Cl = V.strictCreate, Rl = Xt.create, $l = Qr.create, Nl = Qt.create, Ol = Ze.create, Ml = er.create, Ll = Fr.create, Fl = vt.create, Dl = Ct.create, Ul = tr.create, jl = rr.create, Zl = nt.create, zl = nr.create, Bl = Ft.create, So = Oe.create, Gl = Ue.create, Vl = ot.create, Hl = Oe.createWithPreprocess, Wl = ur.create, ql = () => Js().optional(), Kl = () => Xs().optional(), Yl = () => Qs().optional(), Jl = {
- string: (t) => Re.create({ ...t, coerce: !0 }),
- number: (t) => tt.create({ ...t, coerce: !0 }),
- boolean: (t) => Kt.create({
- ...t,
- coerce: !0
- }),
- bigint: (t) => rt.create({ ...t, coerce: !0 }),
- date: (t) => _t.create({ ...t, coerce: !0 })
-}, Xl = $;
-var K = /* @__PURE__ */ Object.freeze({
- __proto__: null,
- defaultErrorMap: Mt,
- setErrorMap: Hc,
- getErrorMap: $r,
- makeIssue: Nr,
- EMPTY_PATH: Wc,
- addIssueToContext: v,
- ParseStatus: oe,
- INVALID: $,
- DIRTY: Pt,
- OK: fe,
- isAborted: En,
- isDirty: kn,
- isValid: yt,
- isAsync: qt,
- get util() {
- return F;
- },
- get objectUtil() {
- return Sn;
- },
- ZodParsedType: b,
- getParsedType: He,
- ZodType: O,
- datetimeRegex: qs,
- ZodString: Re,
- ZodNumber: tt,
- ZodBigInt: rt,
- ZodBoolean: Kt,
- ZodDate: _t,
- ZodSymbol: Mr,
- ZodUndefined: Yt,
- ZodNull: Jt,
- ZodAny: Lt,
- ZodUnknown: mt,
- ZodNever: Ye,
- ZodVoid: Lr,
- ZodArray: Ne,
- ZodObject: V,
- ZodUnion: Xt,
- ZodDiscriminatedUnion: Qr,
- ZodIntersection: Qt,
- ZodTuple: Ze,
- ZodRecord: er,
- ZodMap: Fr,
- ZodSet: vt,
- ZodFunction: Ct,
- ZodLazy: tr,
- ZodLiteral: rr,
- ZodEnum: nt,
- ZodNativeEnum: nr,
- ZodPromise: Ft,
- ZodEffects: Oe,
- ZodTransformer: Oe,
- ZodOptional: Ue,
- ZodNullable: ot,
- ZodDefault: or,
- ZodCatch: sr,
- ZodNaN: Dr,
- BRAND: ml,
- ZodBranded: Hn,
- ZodPipeline: ur,
- ZodReadonly: ar,
- custom: Ys,
- Schema: O,
- ZodSchema: O,
- late: gl,
- get ZodFirstPartyTypeKind() {
- return C;
- },
- coerce: Jl,
- any: El,
- array: Tl,
- bigint: vl,
- boolean: Qs,
- date: bl,
- discriminatedUnion: $l,
- effect: So,
- enum: Zl,
- function: Dl,
- instanceof: yl,
- intersection: Nl,
- lazy: Ul,
- literal: jl,
- map: Ll,
- nan: _l,
- nativeEnum: zl,
- never: Pl,
- null: Sl,
- nullable: Vl,
- number: Xs,
- object: Il,
- oboolean: Yl,
- onumber: Kl,
- optional: Gl,
- ostring: ql,
- pipeline: Wl,
- preprocess: Hl,
- promise: Bl,
- record: Ml,
- set: Fl,
- strictObject: Cl,
- string: Js,
- symbol: wl,
- transformer: So,
- tuple: Ol,
- undefined: xl,
- union: Rl,
- unknown: kl,
- void: Al,
- NEVER: Xl,
- ZodIssueCode: g,
- quotelessJson: Vc,
- ZodError: ye
-});
-const Ql = K.object({
- pluginId: K.string(),
- name: K.string(),
- host: K.string().url(),
- code: K.string(),
- icon: K.string().optional(),
- description: K.string().max(200).optional(),
- permissions: K.array(
- K.enum([
- "content:read",
- "content:write",
- "library:read",
- "library:write",
- "user:read",
- "comment:read",
- "comment:write",
- "allow:downloads",
- "allow:localstorage"
- ])
- )
-});
-function ea(t, e) {
- return new URL(e, t).toString();
-}
-function eu(t) {
- return fetch(t).then((e) => e.json()).then((e) => {
- if (!Ql.safeParse(e).success)
- throw new Error("Invalid plugin manifest");
- return e;
- }).catch((e) => {
- throw console.error(e), e;
- });
-}
-function Eo(t) {
- return !t.host && !t.code.startsWith("http") ? Promise.resolve(t.code) : fetch(ea(t.host, t.code)).then((e) => {
- if (e.ok)
- return e.text();
- throw new Error("Failed to load plugin code");
- });
-}
-const ta = K.object({
- width: K.number().positive(),
- height: K.number().positive()
-}), tu = K.function().args(
- K.string(),
- K.string(),
- K.enum(["dark", "light"]),
- ta.optional(),
- K.boolean().optional()
-).implement((t, e, r, n, o) => Zc(t, e, r, n, o));
-async function ru(t, e, r, n) {
- let o = await Eo(e), s = !1, i = !1, c = null, l = [];
- const u = /* @__PURE__ */ new Set(), d = !!e.permissions.find(
- (I) => I === "allow:downloads"
- ), f = t.addListener("themechange", (I) => {
- c == null || c.setTheme(I);
- }), h = t.addListener("finish", () => {
- k(), t == null || t.removeListener(h);
- });
- let p = [];
- const m = () => {
- j(f), p.forEach((I) => {
- j(I);
- }), l = [], p = [];
- }, k = () => {
- m(), u.forEach(clearTimeout), u.clear(), c && (c.removeEventListener("close", k), c.remove(), c = null), i = !0, r();
- }, x = async () => {
- if (!s) {
- s = !0;
- return;
- }
- m(), o = await Eo(e), n(o);
- }, w = (I, L, Z) => {
- const se = t.theme, J = ea(e.host, L);
- (c == null ? void 0 : c.getAttribute("iframe-src")) !== J && (c = tu(I, J, se, Z, d), c.setTheme(se), c.addEventListener("close", k, {
- once: !0
- }), c.addEventListener("load", x));
- }, R = (I) => {
- l.push(I);
- }, T = (I, L, Z) => {
- const se = t.addListener(
- I,
- (...J) => {
- i || L(...J);
- },
- Z
- );
- return p.push(se), se;
- }, j = (I) => {
- t.removeListener(I);
- };
- return {
- close: k,
- destroyListener: j,
- openModal: w,
- resizeModal: (I, L) => {
- ta.parse({ width: I, height: L }), c && c.resize(I, L);
- },
- getModal: () => c,
- registerListener: T,
- registerMessageCallback: R,
- sendMessage: (I) => {
- l.forEach((L) => L(I));
- },
- get manifest() {
- return e;
- },
- get context() {
- return t;
- },
- get timeouts() {
- return u;
- },
- get code() {
- return o;
- }
- };
-}
-const nu = [
- "finish",
- "pagechange",
- "filechange",
- "selectionchange",
- "themechange",
- "shapechange",
- "contentsave"
-];
-function ou(t) {
- const e = (n) => {
- if (!t.manifest.permissions.includes(n))
- throw new Error(`Permission ${n} is not granted`);
- };
- return {
- penpot: {
- ui: {
- open: (n, o, s) => {
- t.openModal(n, o, s);
- },
- get size() {
- var n;
- return ((n = t.getModal()) == null ? void 0 : n.size()) || null;
- },
- resize: (n, o) => t.resizeModal(n, o),
- sendMessage(n) {
- var s;
- const o = new CustomEvent("message", {
- detail: n
- });
- (s = t.getModal()) == null || s.dispatchEvent(o);
- },
- onMessage: (n) => {
- K.function().parse(n), t.registerMessageCallback(n);
- }
- },
- utils: {
- geometry: {
- center(n) {
- return window.app.plugins.public_utils.centerShapes(n);
- }
- },
- types: {
- isBoard(n) {
- return n.type === "board";
- },
- isGroup(n) {
- return n.type === "group";
- },
- isMask(n) {
- return n.type === "group" && n.isMask();
- },
- isBool(n) {
- return n.type === "boolean";
- },
- isRectangle(n) {
- return n.type === "rectangle";
- },
- isPath(n) {
- return n.type === "path";
- },
- isText(n) {
- return n.type === "text";
- },
- isEllipse(n) {
- return n.type === "ellipse";
- },
- isSVG(n) {
- return n.type === "svg-raw";
- }
- }
- },
- closePlugin: () => {
- t.close();
- },
- on(n, o, s) {
- return K.enum(nu).parse(n), K.function().parse(o), e("content:read"), t.registerListener(n, o, s);
- },
- off(n) {
- t.destroyListener(n);
- },
- // Penpot State API
- get root() {
- return e("content:read"), t.context.root;
- },
- get currentFile() {
- return e("content:read"), t.context.currentFile;
- },
- get currentPage() {
- return e("content:read"), t.context.currentPage;
- },
- get selection() {
- return e("content:read"), t.context.selection;
- },
- set selection(n) {
- e("content:read"), t.context.selection = n;
- },
- get viewport() {
- return t.context.viewport;
- },
- get history() {
- return t.context.history;
- },
- get library() {
- return e("library:read"), t.context.library;
- },
- get fonts() {
- return e("content:read"), t.context.fonts;
- },
- get currentUser() {
- return e("user:read"), t.context.currentUser;
- },
- get activeUsers() {
- return e("user:read"), t.context.activeUsers;
- },
- shapesColors(n) {
- return e("content:read"), t.context.shapesColors(n);
- },
- replaceColor(n, o, s) {
- return e("content:write"), t.context.replaceColor(n, o, s);
- },
- get theme() {
- return t.context.theme;
- },
- get localStorage() {
- return e("allow:localstorage"), t.context.localStorage;
- },
- createBoard() {
- return e("content:write"), t.context.createBoard();
- },
- createRectangle() {
- return e("content:write"), t.context.createRectangle();
- },
- createEllipse() {
- return e("content:write"), t.context.createEllipse();
- },
- createText(n) {
- return e("content:write"), t.context.createText(n);
- },
- createPath() {
- return e("content:write"), t.context.createPath();
- },
- createBoolean(n, o) {
- return e("content:write"), t.context.createBoolean(n, o);
- },
- createShapeFromSvg(n) {
- return e("content:write"), t.context.createShapeFromSvg(n);
- },
- createShapeFromSvgWithImages(n) {
- return e("content:write"), t.context.createShapeFromSvgWithImages(n);
- },
- group(n) {
- return e("content:write"), t.context.group(n);
- },
- ungroup(n, ...o) {
- e("content:write"), t.context.ungroup(n, ...o);
- },
- uploadMediaUrl(n, o) {
- return e("content:write"), t.context.uploadMediaUrl(n, o);
- },
- uploadMediaData(n, o, s) {
- return e("content:write"), t.context.uploadMediaData(n, o, s);
- },
- generateMarkup(n, o) {
- return e("content:read"), t.context.generateMarkup(n, o);
- },
- generateStyle(n, o) {
- return e("content:read"), t.context.generateStyle(n, o);
- },
- generateFontFaces(n) {
- return e("content:read"), t.context.generateFontFaces(n);
- },
- openViewer() {
- e("content:read"), t.context.openViewer();
- },
- createPage() {
- return e("content:write"), t.context.createPage();
- },
- openPage(n) {
- e("content:read"), t.context.openPage(n);
- },
- alignHorizontal(n, o) {
- e("content:write"), t.context.alignHorizontal(n, o);
- },
- alignVertical(n, o) {
- e("content:write"), t.context.alignVertical(n, o);
- },
- distributeHorizontal(n) {
- e("content:write"), t.context.distributeHorizontal(n);
- },
- distributeVertical(n) {
- e("content:write"), t.context.distributeVertical(n);
- },
- flatten(n) {
- return e("content:write"), t.context.flatten(n);
- }
- }
- };
-}
-let ko = !1;
-const P = {
- hardenIntrinsics: () => {
- ko || (ko = !0, hardenIntrinsics());
- },
- createCompartment: (t) => new Compartment(t),
- harden: (t) => harden(t),
- safeReturn(t) {
- return t == null ? t : harden(t);
- }
-};
-function su(t) {
- P.hardenIntrinsics();
- const e = ou(t), r = {
- get(c, l, u) {
- const d = Reflect.get(c, l, u);
- return typeof d == "function" ? function(...f) {
- const h = d.apply(c, f);
- return P.safeReturn(h);
- } : P.safeReturn(d);
- }
- }, n = new Proxy(e.penpot, r), o = (c, l) => {
- const u = {
- ...l,
- credentials: "omit",
- headers: {
- ...l == null ? void 0 : l.headers,
- Authorization: ""
- }
- };
- return fetch(c, u).then((d) => {
- const f = {
- ok: d.ok,
- status: d.status,
- statusText: d.statusText,
- url: d.url,
- text: d.text.bind(d),
- json: d.json.bind(d)
- };
- return P.safeReturn(f);
- });
- }, s = {
- penpot: n,
- fetch: P.harden(o),
- setTimeout: P.harden(
- (...[c, l]) => {
- const u = setTimeout(() => {
- c();
- }, l);
- return t.timeouts.add(u), P.safeReturn(u);
- }
- ),
- clearTimeout: P.harden((c) => {
- clearTimeout(c), t.timeouts.delete(c);
- }),
- /**
- * GLOBAL FUNCTIONS ACCESIBLE TO PLUGINS
- **/
- isFinite: P.harden(isFinite),
- isNaN: P.harden(isNaN),
- parseFloat: P.harden(parseFloat),
- parseInt: P.harden(parseInt),
- decodeURI: P.harden(decodeURI),
- decodeURIComponent: P.harden(decodeURIComponent),
- encodeURI: P.harden(encodeURI),
- encodeURIComponent: P.harden(encodeURIComponent),
- Object: P.harden(Object),
- Boolean: P.harden(Boolean),
- Symbol: P.harden(Symbol),
- Number: P.harden(Number),
- BigInt: P.harden(BigInt),
- Math: P.harden(Math),
- Date: P.harden(Date),
- String: P.harden(String),
- RegExp: P.harden(RegExp),
- Array: P.harden(Array),
- Int8Array: P.harden(Int8Array),
- Uint8Array: P.harden(Uint8Array),
- Uint8ClampedArray: P.harden(Uint8ClampedArray),
- Int16Array: P.harden(Int16Array),
- Uint16Array: P.harden(Uint16Array),
- Int32Array: P.harden(Int32Array),
- Uint32Array: P.harden(Uint32Array),
- BigInt64Array: P.harden(BigInt64Array),
- BigUint64Array: P.harden(BigUint64Array),
- Float32Array: P.harden(Float32Array),
- Float64Array: P.harden(Float64Array),
- Map: P.harden(Map),
- Set: P.harden(Set),
- WeakMap: P.harden(WeakMap),
- WeakSet: P.harden(WeakSet),
- ArrayBuffer: P.harden(ArrayBuffer),
- DataView: P.harden(DataView),
- Atomics: P.harden(Atomics),
- JSON: P.harden(JSON),
- Promise: P.harden(Promise),
- Proxy: P.harden(Proxy),
- Intl: P.harden(Intl),
- // Window properties
- console: P.harden(window.console),
- devicePixelRatio: P.harden(window.devicePixelRatio),
- atob: P.harden(window.atob),
- btoa: P.harden(window.btoa),
- structuredClone: P.harden(window.structuredClone)
- }, i = P.createCompartment(s);
- return {
- evaluate: () => {
- i.evaluate(t.code);
- },
- cleanGlobalThis: () => {
- Object.keys(s).forEach((c) => {
- delete i.globalThis[c];
- });
- },
- compartment: i
- };
-}
-async function au(t, e, r) {
- const n = async () => {
- try {
- s.evaluate();
- } catch (i) {
- console.error(i), o.close();
- }
- }, o = await ru(
- t,
- e,
- function() {
- s.cleanGlobalThis(), r();
- },
- function() {
- n();
- }
- ), s = su(o);
- return n(), {
- plugin: o,
- manifest: e,
- compartment: s
- };
-}
-let gt = [], An = null;
-function iu(t) {
- An = t;
-}
-const Po = () => {
- gt.forEach((t) => {
- t.plugin.close();
- }), gt = [];
-};
-window.addEventListener("message", (t) => {
- try {
- for (const e of gt)
- e.plugin.sendMessage(t.data);
- } catch (e) {
- console.error(e);
- }
-});
-const cu = async function(t, e) {
- try {
- const r = An && An(t.pluginId);
- if (!r)
- return;
- Po();
- const n = await au(
- P.harden(r),
- t,
- () => {
- gt = gt.filter((o) => o !== n), e && e();
- }
- );
- gt.push(n);
- } catch (r) {
- Po(), console.error(r);
- }
-}, ra = async function(t, e) {
- cu(t, e);
-}, lu = async function(t) {
- const e = await eu(t);
- ra(e);
-}, uu = function(t) {
- const e = gt.find((r) => r.manifest.pluginId === t);
- e && e.plugin.close();
-};
-console.log("%c[PLUGINS] Loading plugin system", "color: #008d7c");
-repairIntrinsics({
- evalTaming: "unsafeEval",
- stackFiltering: "verbose",
- errorTaming: "unsafe",
- consoleTaming: "unsafe",
- errorTrapping: "none",
- unhandledRejectionTrapping: "none"
-});
-const Ao = globalThis;
-Ao.initPluginsRuntime = (t) => {
- try {
- console.log("%c[PLUGINS] Initialize runtime", "color: #008d7c"), iu(t), Ao.ɵcontext = t("TEST"), globalThis.ɵloadPlugin = ra, globalThis.ɵloadPluginByUrl = lu, globalThis.ɵunloadPlugin = uu;
- } catch (e) {
- console.error(e);
- }
-};
-//# sourceMappingURL=index.js.map
diff --git a/frontend/resources/templates/index.mustache b/frontend/resources/templates/index.mustache
index b310d23630..deeba32f81 100644
--- a/frontend/resources/templates/index.mustache
+++ b/frontend/resources/templates/index.mustache
@@ -29,8 +29,6 @@
{{/manifest}}
-
-
+