mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
Merge remote-tracking branch 'origin/develop' into staging
This commit is contained in:
101
CHANGES.md
101
CHANGES.md
@@ -1,12 +1,54 @@
|
|||||||
# CHANGELOG
|
# 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)
|
## 2.8.1 (Unreleased)
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
- Fix unexpected exception on processing old texts [Github #6889](https://github.com/penpot/penpot/pull/6889)
|
- Fix unexpected exception on processing old texts [Github #6889](https://github.com/penpot/penpot/pull/6889)
|
||||||
|
|
||||||
|
|
||||||
## 2.8.0
|
## 2.8.0
|
||||||
|
|
||||||
### :rocket: Epics and highlights
|
### :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.
|
on-premises instances** that want to keep up to date.
|
||||||
|
|
||||||
### :heart: Community contributions (Thank you!)
|
### :heart: Community contributions (Thank you!)
|
||||||
|
|
||||||
- Add Serbian language [GitHub #5002](https://github.com/penpot/penpot/issues/5002) by [crnobog69](https://github.com/crnobog69)
|
- Add Serbian language [GitHub #5002](https://github.com/penpot/penpot/issues/5002) by [crnobog69](https://github.com/crnobog69)
|
||||||
|
|
||||||
### :sparkles: New features & Enhancements
|
### :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 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)
|
- Fix url on invitation link [Taiga #11284](https://tree.taiga.io/project/penpot/issue/11284)
|
||||||
|
|
||||||
|
|
||||||
## 2.7.1
|
## 2.7.1
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :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 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)
|
- Fix tokens disappearing after manual additions [Taiga #11063](https://tree.taiga.io/project/penpot/issue/11063)
|
||||||
|
|
||||||
|
|
||||||
## 2.7.0
|
## 2.7.0
|
||||||
|
|
||||||
### :rocket: Epics and highlights
|
### :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)
|
- 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)
|
- Fix Storybook link 'list of all available icons' wrong path [Taiga #10705](https://tree.taiga.io/project/penpot/issue/10705)
|
||||||
|
|
||||||
|
|
||||||
## 2.5.4
|
## 2.5.4
|
||||||
|
|
||||||
### :heart: Community contributions (Thank you!)
|
### :heart: Community contributions (Thank you!)
|
||||||
@@ -267,7 +307,7 @@ on-premises instances** that want to keep up to date.
|
|||||||
|
|
||||||
### :boom: Breaking changes & Deprecations
|
### :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:
|
section:
|
||||||
|
|
||||||
This release includes a fix for an internal bug in Penpot that caused incorrect handling
|
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
|
no new incorrect references will be generated. However, existing files may still contain
|
||||||
incorrect references.
|
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
|
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
|
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
|
(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 missing methods reference on API Docs
|
||||||
- Fix memory usage issue on file-gc asynchronous task (related to snapshots feature)
|
- Fix memory usage issue on file-gc asynchronous task (related to snapshots feature)
|
||||||
|
|
||||||
|
|
||||||
## 2.4.1
|
## 2.4.1
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :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 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)
|
- Fix problem when changing color libraries [Plugins #184](https://github.com/penpot/penpot-plugins/issues/184)
|
||||||
|
|
||||||
|
|
||||||
## 2.4.0
|
## 2.4.0
|
||||||
|
|
||||||
### :rocket: Epics and highlights
|
### :rocket: Epics and highlights
|
||||||
@@ -414,7 +452,6 @@ is a number of cores)
|
|||||||
|
|
||||||
- Add initial documentation for Kubernetes
|
- Add initial documentation for Kubernetes
|
||||||
|
|
||||||
|
|
||||||
## 2.3.1
|
## 2.3.1
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
@@ -422,7 +459,6 @@ is a number of cores)
|
|||||||
- Fix unexpected issue on interaction between plugins sandbox and
|
- Fix unexpected issue on interaction between plugins sandbox and
|
||||||
internal impl of promise
|
internal impl of promise
|
||||||
|
|
||||||
|
|
||||||
## 2.3.0
|
## 2.3.0
|
||||||
|
|
||||||
### :rocket: Epics and highlights
|
### :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.
|
You can enable it with the `enable-feature-text-editor-v2` configuration flag.
|
||||||
|
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
- Fix problem with constraints buttons [Taiga #8465](https://tree.taiga.io/project/penpot/issue/8465)
|
- 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
|
### :boom: Breaking changes & Deprecations
|
||||||
|
|
||||||
- Removed "merge assets" option when exporting ".svg + .json" files. After the components changes the option wasn't
|
- 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
|
working properly and we're planning to change the format soon. We think it's better to deprecate the option for the
|
||||||
time being.
|
time being.
|
||||||
|
|
||||||
### :heart: Community contributions (Thank you!)
|
### :heart: Community contributions (Thank you!)
|
||||||
|
|
||||||
@@ -505,7 +540,7 @@ time being.
|
|||||||
freeing up space in the database. It can be enabled with the
|
freeing up space in the database. It can be enabled with the
|
||||||
`enable-enable-tiered-file-data-storage` flag.
|
`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)
|
- **JSON Interoperability for HTTP API** [Taiga #8372](https://tree.taiga.io/project/penpot/us/8372)
|
||||||
|
|
||||||
@@ -548,7 +583,7 @@ time being.
|
|||||||
|
|
||||||
- **Design System**
|
- **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)
|
- **Storybook** [Taiga #6329](https://tree.taiga.io/project/penpot/us/6329)
|
||||||
|
|
||||||
@@ -603,11 +638,11 @@ time being.
|
|||||||
|
|
||||||
### :sparkles: New features
|
### :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
|
### :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 pencil loader [Taiga #8348](https://tree.taiga.io/project/penpot/issue/8348)
|
||||||
- Fix several issues on the OIDC.
|
- Fix several issues on the OIDC.
|
||||||
- Fix regression on the `email-verification` flag [Taiga #8398](https://tree.taiga.io/project/penpot/issue/8398)
|
- 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 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)
|
- Fix style scoping problem with imported SVG [Taiga #7671](https://tree.taiga.io/project/penpot/issue/7671)
|
||||||
|
|
||||||
|
|
||||||
## 2.0.1
|
## 2.0.1
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
- Fix different issues related to components v2 migrations including [Github #4443](https://github.com/penpot/penpot/issues/4443)
|
- 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
|
## 2.0.0 - I Just Can't Get Enough
|
||||||
|
|
||||||
### :rocket: Epics and highlights
|
### :rocket: Epics and highlights
|
||||||
|
|
||||||
- Grid CSS layout [Taiga #4915](https://tree.taiga.io/project/penpot/epic/4915)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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.
|
- 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!)
|
### :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)
|
- 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)
|
- 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)
|
- 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)
|
- Typo (by StephanEggermont) [#157](https://github.com/penpot/penpot-docs/pull/157)
|
||||||
|
|
||||||
### :sparkles: New features
|
### :sparkles: New features
|
||||||
|
|
||||||
- Send comments with Ctrl+Enter / Cmd + Enter [Taiga #6085](https://tree.taiga.io/project/penpot/issue/6085)
|
- 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)
|
- 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)
|
- 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)
|
- [REDESIGN] Onboarding slides [Taiga #6678](https://tree.taiga.io/project/penpot/us/6678)
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :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 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 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- 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)
|
- [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)
|
- [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
|
### :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
|
### :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 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)
|
- Fix tooltip on toggle visibility and toggle lock buttons [Taiga #5141](https://tree.taiga.io/project/penpot/issue/5141)
|
||||||
|
|
||||||
|
|
||||||
## 1.19.1
|
## 1.19.1
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :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)
|
- Update google fonts catalog (at 2023/07/06) [Taiga #5592](https://tree.taiga.io/project/penpot/issue/5592)
|
||||||
|
|
||||||
|
|
||||||
### :heart: Community contributions by (Thank you!)
|
### :heart: Community contributions by (Thank you!)
|
||||||
|
|
||||||
- Update Typography palette order (by @akshay-gupta7) [Github #3156](https://github.com/penpot/penpot/pull/3156)
|
- 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)
|
- Fix problem with opacity in imported SVG's [Taiga #4923](https://tree.taiga.io/project/penpot/issue/4923)
|
||||||
|
|
||||||
### :heart: Community contributions by (Thank you!)
|
### :heart: Community contributions by (Thank you!)
|
||||||
|
|
||||||
- To @ondrejkonec: for contributing to the code with:
|
- To @ondrejkonec: for contributing to the code with:
|
||||||
- Refactor CSS variables [Github #2948](https://github.com/penpot/penpot/pull/2948)
|
- Refactor CSS variables [Github #2948](https://github.com/penpot/penpot/pull/2948)
|
||||||
|
|
||||||
## 1.17.3
|
## 1.17.3
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
- Fix copy and paste very nested inside itself [Taiga #4848](https://tree.taiga.io/project/penpot/issue/4848)
|
- 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 custom fonts not rendered correctly [Taiga #4874](https://tree.taiga.io/project/penpot/issue/4874)
|
||||||
- Fix problem with shadows and blur on multiple selection
|
- Fix problem with shadows and blur on multiple selection
|
||||||
@@ -1182,6 +1218,7 @@ time being.
|
|||||||
## 1.17.1
|
## 1.17.1
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :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 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 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)
|
- 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
|
### :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
|
version has never been documented nor set as default value so
|
||||||
technically this is not a breaking change because we are removing
|
technically this is not a breaking change because we are removing
|
||||||
a "private API".
|
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)
|
- 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)
|
- Change shortcut for "Clear undo" [#2219](https://github.com/penpot/penpot/issues/2219)
|
||||||
|
|
||||||
|
|
||||||
## 1.15.2-beta
|
## 1.15.2-beta
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :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)
|
- Fix bringing complete file data when launching the export dialog [Taiga #4006](https://tree.taiga.io/project/penpot/issue/4006)
|
||||||
|
|
||||||
### :arrow_up: Deps updates
|
### :arrow_up: Deps updates
|
||||||
|
|
||||||
### :heart: Community contributions by (Thank you!)
|
### :heart: Community contributions by (Thank you!)
|
||||||
|
|
||||||
## 1.14.2-beta
|
## 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)
|
- 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)
|
- Fix problem with empty text boxes events [Taiga #3627](https://tree.taiga.io/project/penpot/issue/3627)
|
||||||
|
|
||||||
|
|
||||||
## 1.13.5-beta
|
## 1.13.5-beta
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
- Fix orientation artboard preset not working with differently sized artboards [Taiga #3548](https://tree.taiga.io/project/penpot/issue/3548)
|
- 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)
|
- 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)
|
- 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
|
### :arrow_up: Deps updates
|
||||||
|
|
||||||
### :heart: Community contributions by (Thank you!)
|
### :heart: Community contributions by (Thank you!)
|
||||||
|
|
||||||
## 1.12.4-beta
|
## 1.12.4-beta
|
||||||
@@ -1689,7 +1727,7 @@ time being.
|
|||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
- Fix issue with shift+select to deselect shapes [Taiga #3154](https://tree.taiga.io/project/penpot/issue/3154)
|
- 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
|
- Fix issue on password persistence after registration process on private instances
|
||||||
|
|
||||||
## 1.12.2-beta
|
## 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 length of names in sidebar [Taiga #2962](https://tree.taiga.io/project/penpot/issue/2962)
|
||||||
- Fix issues on loki integration
|
- Fix issues on loki integration
|
||||||
|
|
||||||
|
|
||||||
## 1.12.0-beta
|
## 1.12.0-beta
|
||||||
|
|
||||||
### :boom: Breaking changes
|
### :boom: Breaking changes
|
||||||
|
|||||||
@@ -193,7 +193,7 @@
|
|||||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
<div
|
<div
|
||||||
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
||||||
Click to the link below to confirm the change:</div>
|
Click the link below to confirm the change.</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -217,8 +217,7 @@
|
|||||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||||
<div
|
<div
|
||||||
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">
|
||||||
If you received this email by mistake, please consider changing your password for security
|
If you did not request this change, consider changing your password for security reasons.</div>
|
||||||
reasons.</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ Hello {{name|abbreviate:25}}!
|
|||||||
|
|
||||||
We received a request to change your current email to {{ pending-email }}.
|
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}}
|
{{ public-uri }}/#/auth/verify-token?token={{token}}
|
||||||
|
|
||||||
If you received this email by mistake, please consider changing your password
|
If you did not request this change, consider changing your password for security reasons.
|
||||||
for security reasons.
|
|
||||||
|
|
||||||
Enjoy!
|
Enjoy!
|
||||||
The Penpot team.
|
The Penpot team.
|
||||||
|
|||||||
@@ -71,19 +71,27 @@ def run_cmd(params):
|
|||||||
print("EXC:", str(cause))
|
print("EXC:", str(cause))
|
||||||
sys.exit(-2)
|
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 = {
|
params = {
|
||||||
"cmd": "create-profile",
|
"cmd": "create-profile",
|
||||||
"params": {
|
"params": {
|
||||||
"fullname": fullname,
|
"fullname": fullname,
|
||||||
"email": email,
|
"email": email,
|
||||||
"password": password
|
"password": password,
|
||||||
|
**props
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res = run_cmd(params)
|
res = run_cmd(params)
|
||||||
print(f"Created: {res['email']} / {res['id']}")
|
print(f"Created: {res['email']} / {res['id']}")
|
||||||
|
|
||||||
|
|
||||||
def update_profile(email, fullname, password, is_active):
|
def update_profile(email, fullname, password, is_active):
|
||||||
params = {
|
params = {
|
||||||
"cmd": "update-profile",
|
"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("-e", "--email", help="email", action="store")
|
||||||
parser.add_argument("-p", "--password", help="password", 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("-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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
|
[app.common.logging :as l]
|
||||||
[app.common.media :as cm]
|
[app.common.media :as cm]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.schema.openapi :as-alias oapi]
|
[app.common.schema.openapi :as-alias oapi]
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
[buddy.core.bytes :as bb]
|
[buddy.core.bytes :as bb]
|
||||||
[buddy.core.codecs :as bc]
|
[buddy.core.codecs :as bc]
|
||||||
[clojure.java.shell :as sh]
|
[clojure.java.shell :as sh]
|
||||||
|
[clojure.string]
|
||||||
[clojure.xml :as xml]
|
[clojure.xml :as xml]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[datoteka.fs :as fs]
|
[datoteka.fs :as fs]
|
||||||
@@ -215,6 +217,23 @@
|
|||||||
{:width (int width)
|
{:width (int width)
|
||||||
:height (int height)})))]))
|
: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
|
(defmethod process :info
|
||||||
[{:keys [input] :as params}]
|
[{:keys [input] :as params}]
|
||||||
(let [{:keys [path mtype] :as input} (check-input input)]
|
(let [{:keys [path mtype] :as input} (check-input input)]
|
||||||
@@ -234,13 +253,17 @@
|
|||||||
:code :media-type-mismatch
|
:code :media-type-mismatch
|
||||||
:hint (str "Seems like you are uploading a file whose content does not match the extension."
|
:hint (str "Seems like you are uploading a file whose content does not match the extension."
|
||||||
"Expected: " mtype ". Got: " mtype')))
|
"Expected: " mtype ". Got: " mtype')))
|
||||||
;; For an animated GIF, getImageWidth/Height returns the delta size of one frame (if no frame given
|
(let [{:keys [width height]}
|
||||||
;; it returns size of the last one), whereas getPageWidth/Height always return the full size of
|
(or (get-dimensions-with-orientation (str path))
|
||||||
;; any frame.
|
(do
|
||||||
(assoc input
|
(l/warn "Failed to read image dimensions with orientation; falling back to im4java"
|
||||||
:width (.getPageWidth instance)
|
{:path path})
|
||||||
:height (.getPageHeight instance)
|
{:width (.getPageWidth instance)
|
||||||
:ts (dt/now))))))
|
:height (.getPageHeight instance)}))]
|
||||||
|
(assoc input
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:ts (dt/now)))))))
|
||||||
|
|
||||||
(defmethod process-error org.im4java.core.InfoException
|
(defmethod process-error org.im4java.core.InfoException
|
||||||
[error]
|
[error]
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
WHEN 'professional' THEN 'active'
|
WHEN 'professional' THEN 'active'
|
||||||
ELSE COALESCE(p.props->'~:subscription'->>'~:status', 'incomplete')
|
ELSE COALESCE(p.props->'~:subscription'->>'~:status', 'incomplete')
|
||||||
END,
|
END,
|
||||||
'~:seats', p.props->'~:quantity'
|
'~:seats', p.props->'~:subscription'->'~:quantity'
|
||||||
) AS subscription
|
) AS subscription
|
||||||
FROM team_profile_rel AS tp
|
FROM team_profile_rel AS tp
|
||||||
JOIN team AS t ON (t.id = tp.team_id)
|
JOIN team AS t ON (t.id = tp.team_id)
|
||||||
@@ -193,7 +193,8 @@
|
|||||||
|
|
||||||
(def ^:private sql:get-owned-teams
|
(def ^:private sql:get-owned-teams
|
||||||
"SELECT t.id, t.name,
|
"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
|
FROM team AS t
|
||||||
JOIN team_profile_rel AS tpr ON (tpr.team_id = t.id)
|
JOIN team_profile_rel AS tpr ON (tpr.team_id = t.id)
|
||||||
WHERE t.is_default IS false
|
WHERE t.is_default IS false
|
||||||
|
|||||||
@@ -460,11 +460,14 @@
|
|||||||
::rpc/profile-id (:id profile1)}
|
::rpc/profile-id (:id profile1)}
|
||||||
out (th/command! params)]
|
out (th/command! params)]
|
||||||
|
|
||||||
|
;; (th/print-result! out)
|
||||||
(t/is (th/success? out))
|
(t/is (th/success? out))
|
||||||
(let [result (:result out)]
|
(let [[item1 :as result] (:result out)]
|
||||||
(t/is (= 1 (count result)))
|
(t/is (= 1 (count result)))
|
||||||
(t/is (= (:id team1) (-> result first :id)))
|
(t/is (= (:id team1) (:id item1)))
|
||||||
(t/is (not= (:default-team-id profile1) (-> result first :id))))))
|
(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
|
(t/deftest team-deletion-1
|
||||||
|
|||||||
@@ -349,7 +349,7 @@
|
|||||||
rounded-s (d/format-number (* 100 s) precision)
|
rounded-s (d/format-number (* 100 s) precision)
|
||||||
rounded-l (d/format-number (* 100 l) precision)
|
rounded-l (d/format-number (* 100 l) precision)
|
||||||
rounded-a (d/format-number a 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
|
(defn format-rgba
|
||||||
[[r g b a]]
|
[[r g b a]]
|
||||||
|
|||||||
@@ -241,7 +241,7 @@
|
|||||||
[:shapes ::sm/any]
|
[:shapes ::sm/any]
|
||||||
[:index {:optional true} [:maybe :int]]
|
[:index {:optional true} [:maybe :int]]
|
||||||
[:after-shape {:optional true} ::sm/any]
|
[:after-shape {:optional true} ::sm/any]
|
||||||
[:component-swap {:optional true} :boolean]]]
|
[:allow-altering-copies {:optional true} :boolean]]]
|
||||||
|
|
||||||
[:reorder-children
|
[:reorder-children
|
||||||
[:map {:title "ReorderChildrenChange"}
|
[:map {:title "ReorderChildrenChange"}
|
||||||
@@ -418,7 +418,7 @@
|
|||||||
[:type [:= :set-token-set]]
|
[:type [:= :set-token-set]]
|
||||||
[:set-name :string]
|
[:set-name :string]
|
||||||
[:group? :boolean]
|
[:group? :boolean]
|
||||||
[:token-set [:maybe ctob/schema:token-set-attrs]]]]
|
[:token-set [:maybe [:fn ctob/token-set?]]]]]
|
||||||
|
|
||||||
[:set-token
|
[:set-token
|
||||||
[:map {:title "SetTokenChange"}
|
[:map {:title "SetTokenChange"}
|
||||||
@@ -761,7 +761,7 @@
|
|||||||
(d/update-in-when data [:components component-id :objects] reg-objects))))
|
(d/update-in-when data [:components component-id :objects] reg-objects))))
|
||||||
|
|
||||||
(defmethod process-change :mov-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]
|
(letfn [(calculate-invalid-targets [objects shape-id]
|
||||||
(let [reduce-fn #(into %1 (calculate-invalid-targets objects %2))]
|
(let [reduce-fn #(into %1 (calculate-invalid-targets objects %2))]
|
||||||
(->> (get-in objects [shape-id :shapes])
|
(->> (get-in objects [shape-id :shapes])
|
||||||
@@ -776,7 +776,7 @@
|
|||||||
(and shape
|
(and shape
|
||||||
(not (invalid-targets parent-id))
|
(not (invalid-targets parent-id))
|
||||||
(not (cfh/components-nesting-loop? objects shape-id 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
|
syncing ;; If we are syncing the changes of a main component, it's allowed to change the structure of a copy
|
||||||
(and
|
(and
|
||||||
(not (ctk/in-component-copy? (get objects (:parent-id shape)))) ;; We don't want to change the structure of component copies
|
(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))
|
(ctob/delete-set lib' set-name))
|
||||||
|
|
||||||
(not (ctob/get-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
|
:else
|
||||||
(ctob/update-set lib' set-name (fn [prev-token-set]
|
(ctob/update-set lib' set-name (fn [_] token-set)))))))
|
||||||
(ctob/make-token-set (merge prev-token-set token-set)))))))))
|
|
||||||
|
|
||||||
(defmethod process-change :set-token-theme
|
(defmethod process-change :set-token-theme
|
||||||
[data {:keys [group theme-name theme]}]
|
[data {:keys [group theme-name theme]}]
|
||||||
|
|||||||
@@ -464,8 +464,8 @@
|
|||||||
|
|
||||||
(some? index)
|
(some? index)
|
||||||
(assoc :index index)
|
(assoc :index index)
|
||||||
(:component-swap options)
|
(:allow-altering-copies options)
|
||||||
(assoc :component-swap true)
|
(assoc :allow-altering-copies true)
|
||||||
(:ignore-touched options)
|
(:ignore-touched options)
|
||||||
(assoc :ignore-touched true))
|
(assoc :ignore-touched true))
|
||||||
|
|
||||||
@@ -473,12 +473,14 @@
|
|||||||
(fn [undo-changes shape]
|
(fn [undo-changes shape]
|
||||||
(let [prev-sibling (cfh/get-prev-sibling objects (:id shape))]
|
(let [prev-sibling (cfh/get-prev-sibling objects (:id shape))]
|
||||||
(conj undo-changes
|
(conj undo-changes
|
||||||
{:type :mov-objects
|
(cond-> {:type :mov-objects
|
||||||
:page-id (::page-id (meta changes))
|
:page-id (::page-id (meta changes))
|
||||||
:parent-id (:parent-id shape)
|
:parent-id (:parent-id shape)
|
||||||
:shapes [(:id shape)]
|
:shapes [(:id shape)]
|
||||||
:after-shape prev-sibling
|
:after-shape prev-sibling
|
||||||
:index 0}))) ; index is used in case there is no after-shape (moving bottom shapes)
|
: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
|
restore-touched-change
|
||||||
{:type :mod-obj
|
{:type :mod-obj
|
||||||
@@ -916,7 +918,7 @@
|
|||||||
(-> changes
|
(-> changes
|
||||||
(update :redo-changes conj {:type :set-token-set
|
(update :redo-changes conj {:type :set-token-set
|
||||||
:set-name name
|
:set-name name
|
||||||
:token-set (assoc prev-token-set :name new-name)
|
:token-set (ctob/rename prev-token-set new-name)
|
||||||
:group? false})
|
:group? false})
|
||||||
(update :undo-changes conj {:type :set-token-set
|
(update :undo-changes conj {:type :set-token-set
|
||||||
:set-name new-name
|
:set-name new-name
|
||||||
@@ -937,11 +939,11 @@
|
|||||||
:group? group?})
|
:group? group?})
|
||||||
(update :undo-changes conj (if prev-token-set
|
(update :undo-changes conj (if prev-token-set
|
||||||
{:type :set-token-set
|
{:type :set-token-set
|
||||||
:set-name (or
|
:set-name (if token-set
|
||||||
;; Undo of edit
|
;; Undo of edit
|
||||||
(:name token-set)
|
(ctob/get-name token-set)
|
||||||
;; Undo of delete
|
;; Undo of delete
|
||||||
set-name)
|
set-name)
|
||||||
:token-set prev-token-set
|
:token-set prev-token-set
|
||||||
:group? group?}
|
:group? group?}
|
||||||
;; Undo of create
|
;; Undo of create
|
||||||
|
|||||||
@@ -152,12 +152,22 @@
|
|||||||
(dm/get-prop shape :type))))
|
(dm/get-prop shape :type))))
|
||||||
|
|
||||||
(defn get-children-ids
|
(defn get-children-ids
|
||||||
[objects id]
|
"Returns the ids of all the descendants of the shape identified
|
||||||
(letfn [(get-children-ids-rec [id processed]
|
by the id. Optionally, you can pass an ignore function to indicate
|
||||||
(when (not (contains? processed id))
|
when to ignore a descendant (and all its descendants)"
|
||||||
(when-let [shapes (-> (get objects id) :shapes (some-> vec))]
|
([objects id]
|
||||||
(into shapes (mapcat #(get-children-ids-rec % (conj processed id))) shapes))))]
|
(get-children-ids objects id {}))
|
||||||
(get-children-ids-rec 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
|
(defn get-children-ids-with-self
|
||||||
[objects id]
|
[objects id]
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
[app.common.types.shape :as cts]
|
[app.common.types.shape :as cts]
|
||||||
[app.common.types.shape.interactions :as ctsi]
|
[app.common.types.shape.interactions :as ctsi]
|
||||||
[app.common.types.shape.shadow :as ctss]
|
[app.common.types.shape.shadow :as ctss]
|
||||||
|
[app.common.types.text :as cttx]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
@@ -1527,6 +1528,31 @@
|
|||||||
colors
|
colors
|
||||||
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
|
(def available-migrations
|
||||||
(into (d/ordered-set)
|
(into (d/ordered-set)
|
||||||
["legacy-2"
|
["legacy-2"
|
||||||
@@ -1591,4 +1617,5 @@
|
|||||||
"0006-fix-old-texts-fills"
|
"0006-fix-old-texts-fills"
|
||||||
"0007-clear-invalid-strokes-and-fills-v2"
|
"0007-clear-invalid-strokes-and-fills-v2"
|
||||||
"0008-fix-library-colors-v4"
|
"0008-fix-library-colors-v4"
|
||||||
"0009-clean-library-colors"]))
|
"0009-clean-library-colors"
|
||||||
|
"0009-add-partial-text-touched-flags"]))
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
(log/dbg :hint "repairing shape :invalid-parent" :id (:id shape) :name (:name shape) :page-id page-id)
|
(log/dbg :hint "repairing shape :invalid-parent" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||||
(-> (pcb/empty-changes nil page-id)
|
(-> (pcb/empty-changes nil page-id)
|
||||||
(pcb/with-file-data file-data)
|
(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
|
(defmethod repair-error :frame-not-found
|
||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
@@ -387,7 +387,7 @@
|
|||||||
(-> (pcb/empty-changes nil page-id)
|
(-> (pcb/empty-changes nil page-id)
|
||||||
(pcb/with-file-data file-data)
|
(pcb/with-file-data file-data)
|
||||||
(pcb/update-shapes [(:id shape)] repair-shape)
|
(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
|
(defmethod repair-error :root-copy-not-allowed
|
||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {: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))
|
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||||
file)
|
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
|
(defmethod repair-error :variant-bad-variant-name
|
||||||
[_ error file _]
|
[_ error file _]
|
||||||
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
(log/error :hint "Variant error code, we don't want to auto repair it for now" :code (:code error))
|
||||||
|
|||||||
@@ -68,7 +68,6 @@
|
|||||||
:variant-bad-name
|
:variant-bad-name
|
||||||
:variant-bad-variant-name
|
:variant-bad-variant-name
|
||||||
:variant-component-bad-name
|
:variant-component-bad-name
|
||||||
:variant-no-properties
|
|
||||||
:variant-component-bad-id})
|
:variant-component-bad-id})
|
||||||
|
|
||||||
(def ^:private schema:error
|
(def ^:private schema:error
|
||||||
@@ -589,11 +588,7 @@
|
|||||||
(when-not (ctk/is-variant? main-component)
|
(when-not (ctk/is-variant? main-component)
|
||||||
(report-error :not-a-variant
|
(report-error :not-a-variant
|
||||||
(str/ffmt "Shape % should be a variant" (:id main-component))
|
(str/ffmt "Shape % should be a variant" (:id main-component))
|
||||||
main-component file component-page))
|
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))))
|
|
||||||
|
|
||||||
(defn- check-component
|
(defn- check-component
|
||||||
"Validate semantic coherence of a component. Report all errors found."
|
"Validate semantic coherence of a component. Report all errors found."
|
||||||
|
|||||||
@@ -117,6 +117,7 @@
|
|||||||
;; Only for developtment.
|
;; Only for developtment.
|
||||||
:tiered-file-data-storage
|
:tiered-file-data-storage
|
||||||
:token-units
|
:token-units
|
||||||
|
:token-typography-types
|
||||||
:transit-readable-response
|
:transit-readable-response
|
||||||
:user-feedback
|
:user-feedback
|
||||||
;; TODO: remove this flag.
|
;; TODO: remove this flag.
|
||||||
@@ -149,7 +150,8 @@
|
|||||||
:enable-onboarding
|
:enable-onboarding
|
||||||
:enable-dashboard-templates-section
|
:enable-dashboard-templates-section
|
||||||
:enable-google-fonts-provider
|
:enable-google-fonts-provider
|
||||||
:enable-component-thumbnails])
|
:enable-component-thumbnails
|
||||||
|
:enable-render-wasm-dpr])
|
||||||
|
|
||||||
(defn parse
|
(defn parse
|
||||||
[& flags]
|
[& flags]
|
||||||
|
|||||||
@@ -467,15 +467,15 @@
|
|||||||
row-tracks (set-flex-multi-span parent row-tracks children-map shape-cells bounds objects :row)
|
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
|
;; 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)))
|
fr-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-row-space (max 0 (- bound-height (+ row-total-size-nofr row-total-gap)))
|
||||||
|
|
||||||
;; Get the minimum values for fr's
|
;; Get the minimum values for fr's
|
||||||
min-column-fr (min-fr-value column-tracks)
|
min-column-fr (min-fr-value column-tracks)
|
||||||
min-row-fr (min-fr-value row-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))
|
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 (/ free-row-space row-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?)
|
column-tracks (set-fr-value column-tracks column-fr auto-width?)
|
||||||
row-tracks (set-fr-value row-tracks row-fr auto-height?)
|
row-tracks (set-fr-value row-tracks row-fr auto-height?)
|
||||||
@@ -484,13 +484,13 @@
|
|||||||
column-total-size (tracks-total-size column-tracks)
|
column-total-size (tracks-total-size column-tracks)
|
||||||
row-total-size (tracks-total-size row-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))))
|
auto-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-row-space (max 0 (if auto-height? 0 (- bound-height (+ row-total-size row-total-gap))))
|
||||||
column-autos (tracks-total-autos column-tracks)
|
column-autos (tracks-total-autos column-tracks)
|
||||||
row-autos (tracks-total-autos row-tracks)
|
row-autos (tracks-total-autos row-tracks)
|
||||||
|
|
||||||
column-add-auto (/ free-column-space column-autos)
|
column-add-auto (/ auto-column-space column-autos)
|
||||||
row-add-auto (/ free-row-space row-autos)
|
row-add-auto (/ auto-row-space row-autos)
|
||||||
|
|
||||||
column-tracks (cond-> column-tracks
|
column-tracks (cond-> column-tracks
|
||||||
(= :stretch (:layout-justify-content parent))
|
(= :stretch (:layout-justify-content parent))
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
[app.common.types.shape-tree :as ctst]
|
[app.common.types.shape-tree :as ctst]
|
||||||
[app.common.types.shape.interactions :as ctsi]
|
[app.common.types.shape.interactions :as ctsi]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
|
[app.common.types.text :as cttx]
|
||||||
[app.common.types.token :as cto]
|
[app.common.types.token :as cto]
|
||||||
[app.common.types.typography :as cty]
|
[app.common.types.typography :as cty]
|
||||||
[app.common.types.variant :as ctv]
|
[app.common.types.variant :as ctv]
|
||||||
@@ -1664,28 +1665,33 @@
|
|||||||
:shapes all-parents})]))))
|
: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
|
(defn- add-update-attr-operations
|
||||||
[attr dest-shape origin-shape roperations uoperations touched]
|
[attr dest-shape roperations uoperations attr-val]
|
||||||
(let [orig-value (get origin-shape attr)
|
(let [roperation {:type :set
|
||||||
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 attr
|
:attr attr
|
||||||
:val val
|
:val attr-val
|
||||||
:ignore-touched true}
|
:ignore-touched true}
|
||||||
uoperation {:type :set
|
uoperation {:type :set
|
||||||
:attr attr
|
:attr attr
|
||||||
@@ -1694,6 +1700,34 @@
|
|||||||
[(conj roperations roperation)
|
[(conj roperations roperation)
|
||||||
(conj uoperations uoperation)]))
|
(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
|
(defn- update-attrs
|
||||||
"The main function that implements the attribute sync algorithm. Copy
|
"The main function that implements the attribute sync algorithm. Copy
|
||||||
attributes that have changed in the origin shape to the dest shape.
|
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?))
|
(generate-update-tokens container dest-shape origin-shape touched omit-touched?))
|
||||||
|
|
||||||
(let [attr-group (get ctk/sync-attrs attr)
|
(let [attr-group (get ctk/sync-attrs attr)
|
||||||
skip-operations? (or (= (get origin-shape attr) (get dest-shape attr))
|
;; position-data is a special case because can be affected by
|
||||||
(and (touched attr-group)
|
;; :geometry-group and :content-group so, if the position-data
|
||||||
omit-touched?))
|
;; 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']
|
[roperations' uoperations']
|
||||||
(if skip-operations?
|
(if skip-operations?
|
||||||
[roperations uoperations]
|
[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)
|
(recur (next attrs)
|
||||||
roperations'
|
roperations'
|
||||||
uoperations')))))))
|
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
|
(defn update-attrs-on-switch
|
||||||
"Copy attributes that have changed in the origin shape to the dest shape. Used on variants switch"
|
"Copy attributes that have changed in the shape previous to the switch
|
||||||
[changes dest-shape origin-shape dest-root origin-root origin-ref-shape container]
|
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.
|
(let [;; We need to sync only the position relative to the origin of the component.
|
||||||
;; (see update-attrs for a full explanation)
|
;; (see update-attrs for a full explanation)
|
||||||
origin-shape (reposition-shape origin-shape origin-root dest-root)
|
previous-shape (reposition-shape previous-shape prev-root current-root)
|
||||||
touched (get dest-shape :touched #{})
|
touched (get previous-shape :touched #{})]
|
||||||
touched-origin (get origin-shape :touched #{})]
|
|
||||||
|
|
||||||
(loop [attrs updatable-attrs
|
(loop [attrs updatable-attrs
|
||||||
roperations [{:type :set-touched :touched (:touched origin-shape)}]
|
roperations [{:type :set-touched :touched (:touched previous-shape)}]
|
||||||
uoperations (list {:type :set-touched :touched (:touched dest-shape)})]
|
uoperations (list {:type :set-touched :touched (:touched current-shape)})]
|
||||||
(if-let [attr (first attrs)]
|
(if-let [attr (first attrs)]
|
||||||
(let [attr-group (get ctk/sync-attrs attr)
|
(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']
|
[roperations' uoperations']
|
||||||
(if (or
|
(if skip-operations?
|
||||||
;; 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)))
|
|
||||||
[roperations uoperations]
|
[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)
|
(recur (next attrs)
|
||||||
roperations'
|
roperations'
|
||||||
uoperations'))
|
uoperations'))
|
||||||
(cond-> changes
|
(cond-> changes
|
||||||
(> (count roperations) 1)
|
(> (count roperations) 1)
|
||||||
(add-update-attr-changes dest-shape container roperations uoperations)
|
(add-update-attr-changes current-shape container roperations uoperations)
|
||||||
|
|
||||||
:always
|
:always
|
||||||
(generate-update-tokens container dest-shape origin-shape touched false))))))
|
(generate-update-tokens container current-shape previous-shape touched false))))))
|
||||||
|
|
||||||
(defn- propagate-attrs
|
(defn- propagate-attrs
|
||||||
"Helper that puts the origin attributes (attrs) into dest but only if
|
"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))
|
(pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values))
|
||||||
|
|
||||||
;; We need to set the same index as the original shape
|
;; 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})
|
:ignore-touched true})
|
||||||
(change-touched new-shape
|
(change-touched new-shape
|
||||||
shape
|
shape
|
||||||
@@ -2123,10 +2370,21 @@
|
|||||||
{}))]))
|
{}))]))
|
||||||
|
|
||||||
(defn generate-component-swap
|
(defn generate-component-swap
|
||||||
[changes objects shape file page libraries id-new-component index target-cell keep-props-values]
|
[changes objects shape file page libraries id-new-component
|
||||||
(let [[all-parents changes]
|
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
|
(-> 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]
|
[new-shape changes]
|
||||||
(-> changes
|
(-> changes
|
||||||
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]
|
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]
|
||||||
|
|||||||
@@ -99,7 +99,14 @@
|
|||||||
(pcb/with-library-data file))
|
(pcb/with-library-data file))
|
||||||
ids
|
ids
|
||||||
options))
|
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)
|
(let [objects (pcb/get-objects changes)
|
||||||
data (pcb/get-library-data changes)
|
data (pcb/get-library-data changes)
|
||||||
page-id (pcb/get-page-id changes)
|
page-id (pcb/get-page-id changes)
|
||||||
@@ -112,11 +119,12 @@
|
|||||||
;; Look for shapes that are inside a component copy, but are
|
;; Look for shapes that are inside a component copy, but are
|
||||||
;; not the root. In this case, they must not be deleted,
|
;; not the root. In this case, they must not be deleted,
|
||||||
;; but hidden (to be able to recover them more easily).
|
;; 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
|
;; to delete the old shape
|
||||||
(let [shape (get objects shape-id)]
|
(let [shape (get objects shape-id)]
|
||||||
(and (ctn/has-any-copy-parent? objects shape)
|
(and (ctn/has-any-copy-parent? objects shape)
|
||||||
(not component-swap))))
|
(not allow-altering-copies))))
|
||||||
|
|
||||||
[ids-to-delete ids-to-hide]
|
[ids-to-delete ids-to-hide]
|
||||||
(loop [ids-seq (seq ids)
|
(loop [ids-seq (seq ids)
|
||||||
@@ -177,10 +185,15 @@
|
|||||||
(d/ordered-set)
|
(d/ordered-set)
|
||||||
(concat ids-to-delete ids-to-hide))
|
(concat ids-to-delete ids-to-hide))
|
||||||
|
|
||||||
all-children
|
;; Descendants of deleted shapes must be also deleted,
|
||||||
(->> ids-to-delete ;; Children 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]
|
(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)
|
(reverse)
|
||||||
(into (d/ordered-set)))
|
(into (d/ordered-set)))
|
||||||
@@ -200,9 +213,10 @@
|
|||||||
|
|
||||||
empty-parents
|
empty-parents
|
||||||
;; Any parent whose children are all deleted, must be deleted too.
|
;; 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.
|
;; 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 #{}))
|
(into (d/ordered-set) (find-all-empty-parents #{}))
|
||||||
#{})
|
#{})
|
||||||
|
|
||||||
@@ -214,7 +228,7 @@
|
|||||||
(conj components (:component-id shape))
|
(conj components (:component-id shape))
|
||||||
components)))
|
components)))
|
||||||
[]
|
[]
|
||||||
(into ids-to-delete all-children))
|
(into ids-to-delete descendants-to-delete))
|
||||||
|
|
||||||
|
|
||||||
ids-set (set ids-to-delete)
|
ids-set (set ids-to-delete)
|
||||||
@@ -241,7 +255,7 @@
|
|||||||
|
|
||||||
changes (-> changes
|
changes (-> changes
|
||||||
(generate-update-shape-flags ids-to-hide objects {:hidden true})
|
(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 ids-to-delete {:ignore-touched ignore-touched})
|
||||||
(pcb/remove-objects empty-parents)
|
(pcb/remove-objects empty-parents)
|
||||||
(pcb/resize-parents all-parents)
|
(pcb/resize-parents all-parents)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
[group-path tokens-lib tokens-lib-theme]
|
[group-path tokens-lib tokens-lib-theme]
|
||||||
(let [deactivate? (contains? #{:all :partial} (ctob/sets-at-path-all-active? tokens-lib group-path))
|
(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)
|
sets-names (->> (ctob/get-sets-at-path tokens-lib group-path)
|
||||||
(map :name)
|
(map ctob/get-name)
|
||||||
(into #{}))]
|
(into #{}))]
|
||||||
(if deactivate?
|
(if deactivate?
|
||||||
(ctob/disable-sets tokens-lib-theme sets-names)
|
(ctob/disable-sets tokens-lib-theme sets-names)
|
||||||
|
|||||||
@@ -18,7 +18,12 @@
|
|||||||
[changes variant-id pos new-name]
|
[changes variant-id pos new-name]
|
||||||
(let [data (pcb/get-library-data changes)
|
(let [data (pcb/get-library-data changes)
|
||||||
objects (pcb/get-objects 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]
|
(reduce (fn [changes component]
|
||||||
(pcb/update-component
|
(pcb/update-component
|
||||||
changes (:id component)
|
changes (:id component)
|
||||||
@@ -81,6 +86,9 @@
|
|||||||
next-prop-num (ctv/next-property-number props)
|
next-prop-num (ctv/next-property-number props)
|
||||||
property-name (or property-name (str ctv/property-prefix next-prop-num))
|
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]
|
[_ changes]
|
||||||
(reduce (fn [[num changes] component]
|
(reduce (fn [[num changes] component]
|
||||||
(let [main-id (:main-instance-id component)
|
(let [main-id (:main-instance-id component)
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
(ns app.common.logic.variants
|
(ns app.common.logic.variants
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.files.changes-builder :as pcb]
|
[app.common.files.changes-builder :as pcb]
|
||||||
[app.common.files.helpers :as cfh]
|
[app.common.files.helpers :as cfh]
|
||||||
[app.common.files.variant :as cfv]
|
[app.common.files.variant :as cfv]
|
||||||
[app.common.logic.libraries :as cll]
|
[app.common.logic.libraries :as cll]
|
||||||
|
[app.common.logic.shapes :as cls]
|
||||||
[app.common.logic.variant-properties :as clvp]
|
[app.common.logic.variant-properties :as clvp]
|
||||||
|
[app.common.types.component :as ctk]
|
||||||
[app.common.types.container :as ctn]
|
[app.common.types.container :as ctn]
|
||||||
[app.common.types.file :as ctf]
|
[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
|
(defn generate-add-new-variant
|
||||||
[changes shape variant-id new-component-id new-shape-id prop-num]
|
[changes shape variant-id new-component-id new-shape-id prop-num]
|
||||||
@@ -62,29 +66,142 @@
|
|||||||
shapes))))
|
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
|
(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)
|
(let [objects (pcb/get-objects changes)
|
||||||
orig-objects (into {} (map (juxt :id identity) original-shapes))
|
container (ctn/make-container page :page)
|
||||||
orig-shapes-w-path (add-unique-path
|
page-objects (:objects page)
|
||||||
(reverse original-shapes)
|
|
||||||
orig-objects
|
;; Get the touched children of the original-shape
|
||||||
(:id 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
|
new-shapes-w-path (add-unique-path
|
||||||
(reverse (cfh/get-children-with-self objects (:id new-shape)))
|
(reverse (cfh/get-children-with-self objects (:id new-shape)))
|
||||||
objects
|
objects
|
||||||
(:id new-shape))
|
(:id new-shape))
|
||||||
new-shapes-map (into {} (map (juxt :shape-path identity) new-shapes-w-path))
|
;; Creates a map to quickly find a child of the new-shape by its shape-path
|
||||||
orig-touched (filter (comp seq :touched) orig-shapes-w-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
|
(reduce
|
||||||
(fn [changes touched-shape]
|
(fn [changes orig-child-touched]
|
||||||
(let [related-shape (get new-shapes-map (:shape-path touched-shape))
|
(let [;; If the orig-child-touched was swapped, get its swap-slot
|
||||||
orig-ref-shape (ctf/find-ref-shape nil container libraries touched-shape)]
|
swap-slot (ctk/get-swap-slot orig-child-touched)
|
||||||
(if related-shape
|
|
||||||
(cll/update-attrs-on-switch
|
;; orig-child-touched is in a copy. Get the referenced shape on the main component
|
||||||
changes related-shape touched-shape new-shape original-shape orig-ref-shape container)
|
;; 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)))
|
||||||
changes
|
changes
|
||||||
orig-touched)))
|
orig-touched)))
|
||||||
|
|||||||
@@ -156,7 +156,7 @@
|
|||||||
|
|
||||||
[new_shape _ changes]
|
[new_shape _ changes]
|
||||||
(-> (pcb/empty-changes nil (:id page))
|
(-> (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)]
|
file' (thf/apply-changes file changes)]
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,14 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.files.changes-builder :as pcb]
|
[app.common.files.changes-builder :as pcb]
|
||||||
|
[app.common.files.helpers :as cfh]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.logic.libraries :as cll]
|
[app.common.logic.libraries :as cll]
|
||||||
[app.common.logic.shapes :as cls]
|
[app.common.logic.shapes :as cls]
|
||||||
|
[app.common.logic.variants :as clv]
|
||||||
[app.common.test-helpers.components :as thc]
|
[app.common.test-helpers.components :as thc]
|
||||||
[app.common.test-helpers.files :as thf]
|
[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.shapes :as ths]
|
||||||
[app.common.text :as txt]
|
[app.common.text :as txt]
|
||||||
[app.common.types.container :as ctn]
|
[app.common.types.container :as ctn]
|
||||||
@@ -275,25 +278,36 @@
|
|||||||
|
|
||||||
(defn swap-component
|
(defn swap-component
|
||||||
"Swap the specified shape by the component specified by component-tag"
|
"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
|
(let [page (if page-label
|
||||||
(thf/get-page file page-label)
|
(thf/get-page file page-label)
|
||||||
(thf/current-page file))
|
(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)
|
(cll/generate-component-swap (pcb/empty-changes)
|
||||||
(:objects page)
|
(:objects page)
|
||||||
shape
|
shape
|
||||||
(:data file)
|
(:data file)
|
||||||
page
|
page
|
||||||
{(:id file) file}
|
libraries
|
||||||
(-> (thc/get-component file component-tag)
|
(-> (thc/get-component file component-tag)
|
||||||
:id)
|
:id)
|
||||||
0
|
0
|
||||||
nil
|
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)]
|
file' (thf/apply-changes file changes)]
|
||||||
|
(when new-shape-label
|
||||||
|
(thi/set-id! new-shape-label (:id new-shape)))
|
||||||
(if propagate-fn
|
(if propagate-fn
|
||||||
(propagate-fn file')
|
(propagate-fn file')
|
||||||
file')))
|
file')))
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.test-helpers.components :as thc]
|
[app.common.test-helpers.components :as thc]
|
||||||
[app.common.test-helpers.ids-map :as thi]
|
[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
|
(defn add-variant
|
||||||
[file variant-label component1-label root1-label component2-label root2-label
|
[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/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/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"}]}))))
|
(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"}]}))))
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
[app.common.types.plugins :as ctpg]
|
[app.common.types.plugins :as ctpg]
|
||||||
[app.common.types.shape-tree :as ctst]
|
[app.common.types.shape-tree :as ctst]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
|
[app.common.types.text :as cttx]
|
||||||
[app.common.types.token :as ctt]
|
[app.common.types.token :as ctt]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[clojure.set :as set]))
|
[clojure.set :as set]))
|
||||||
@@ -293,8 +294,8 @@
|
|||||||
([page component library-data position]
|
([page component library-data position]
|
||||||
(make-component-instance page component library-data position {}))
|
(make-component-instance page component library-data position {}))
|
||||||
([page component library-data position
|
([page component library-data position
|
||||||
{:keys [main-instance? force-id force-frame-id keep-ids?]
|
{: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}}]
|
: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))
|
(let [component-page (ctpl/get-page library-data (:main-instance-page component))
|
||||||
|
|
||||||
component-shape (-> (get-shape component-page (:main-instance-id component))
|
component-shape (-> (get-shape component-page (:main-instance-id component))
|
||||||
@@ -302,7 +303,6 @@
|
|||||||
(assoc :frame-id uuid/zero)
|
(assoc :frame-id uuid/zero)
|
||||||
(remove-swap-keep-attrs))
|
(remove-swap-keep-attrs))
|
||||||
|
|
||||||
|
|
||||||
orig-pos (gpt/point (:x component-shape) (:y component-shape))
|
orig-pos (gpt/point (:x component-shape) (:y component-shape))
|
||||||
delta (gpt/subtract position orig-pos)
|
delta (gpt/subtract position orig-pos)
|
||||||
|
|
||||||
@@ -367,7 +367,7 @@
|
|||||||
|
|
||||||
[new-shape new-shapes _]
|
[new-shape new-shapes _]
|
||||||
(ctst/clone-shape component-shape
|
(ctst/clone-shape component-shape
|
||||||
frame-id
|
(or force-parent-id frame-id)
|
||||||
(:objects component-page)
|
(:objects component-page)
|
||||||
:update-new-shape update-new-shape
|
:update-new-shape update-new-shape
|
||||||
:force-id force-id
|
:force-id force-id
|
||||||
@@ -569,13 +569,16 @@
|
|||||||
(not equal?)
|
(not equal?)
|
||||||
(not (and ignore-geometry is-geometry?)))
|
(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)
|
token-groups (if (= attr :applied-tokens)
|
||||||
(get-token-groups shape val)
|
(get-token-groups shape val)
|
||||||
#{})
|
#{})
|
||||||
|
|
||||||
groups (cond-> token-groups
|
groups (cond-> token-groups
|
||||||
(and group (not equal?))
|
(and group (not equal?))
|
||||||
(set/union #{group}))]
|
(set/union #{group} content-diff-type))]
|
||||||
(cond-> shape
|
(cond-> shape
|
||||||
;; Depending on the origin of the attribute change, we need or not to
|
;; Depending on the origin of the attribute change, we need or not to
|
||||||
;; set the "touched" flag for the group the attribute belongs to.
|
;; set the "touched" flag for the group the attribute belongs to.
|
||||||
|
|||||||
@@ -242,6 +242,13 @@
|
|||||||
(cfh/make-container component-page :page))
|
(cfh/make-container component-page :page))
|
||||||
(cfh/make-container component :component)))
|
(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
|
(defn get-component-root
|
||||||
"Retrieve the root shape of the component."
|
"Retrieve the root shape of the component."
|
||||||
[file-data component]
|
[file-data component]
|
||||||
@@ -390,6 +397,47 @@
|
|||||||
(or (= slot-main slot-inst)
|
(or (= slot-main slot-inst)
|
||||||
(= (:id shape-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
|
(defn get-component-shapes
|
||||||
"Retrieve all shapes of the component"
|
"Retrieve all shapes of the component"
|
||||||
[file-data component]
|
[file-data component]
|
||||||
|
|||||||
@@ -127,7 +127,8 @@
|
|||||||
entries"
|
entries"
|
||||||
[a b]
|
[a b]
|
||||||
(cond
|
(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
|
false
|
||||||
|
|
||||||
(map? a)
|
(map? a)
|
||||||
@@ -148,7 +149,7 @@
|
|||||||
(cond
|
(cond
|
||||||
(map? origin)
|
(map? origin)
|
||||||
(into {}
|
(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
|
(cond
|
||||||
(= :children k)
|
(= :children k)
|
||||||
[k (vec (map #(copy-text-keys %1 %2) (get origin k) (get destiny k)))]
|
[k (vec (map #(copy-text-keys %1 %2) (get origin k) (get destiny k)))]
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
:border-radius "borderRadius"
|
:border-radius "borderRadius"
|
||||||
:color "color"
|
:color "color"
|
||||||
:dimensions "dimension"
|
:dimensions "dimension"
|
||||||
|
:font-size "fontSizes"
|
||||||
|
:letter-spacing "letterSpacing"
|
||||||
:number "number"
|
:number "number"
|
||||||
:opacity "opacity"
|
:opacity "opacity"
|
||||||
:other "other"
|
:other "other"
|
||||||
@@ -101,18 +103,15 @@
|
|||||||
[:m1 {:optional true} token-name-ref]
|
[:m1 {:optional true} token-name-ref]
|
||||||
[:m2 {:optional true} token-name-ref]
|
[:m2 {:optional true} token-name-ref]
|
||||||
[:m3 {:optional true} token-name-ref]
|
[:m3 {:optional true} token-name-ref]
|
||||||
[:m4 {:optional true} token-name-ref]
|
[:m4 {:optional true} token-name-ref]])
|
||||||
[:x {:optional true} token-name-ref]
|
|
||||||
[:y {:optional true} token-name-ref]])
|
|
||||||
|
|
||||||
(def spacing-keys (schema-keys schema:spacing))
|
(def spacing-keys (schema-keys schema:spacing))
|
||||||
|
|
||||||
(def ^:private schema:dimensions
|
(def ^:private schema:dimensions
|
||||||
[:merge
|
(reduce mu/union [schema:sizing
|
||||||
schema:sizing
|
schema:spacing
|
||||||
schema:spacing
|
schema:stroke-width
|
||||||
schema:stroke-width
|
schema:border-radius]))
|
||||||
schema:border-radius])
|
|
||||||
|
|
||||||
(def dimensions-keys (schema-keys schema:dimensions))
|
(def dimensions-keys (schema-keys schema:dimensions))
|
||||||
|
|
||||||
@@ -122,10 +121,27 @@
|
|||||||
|
|
||||||
(def rotation-keys (schema-keys schema:rotation))
|
(def rotation-keys (schema-keys schema:rotation))
|
||||||
|
|
||||||
(def ^:private schema:number
|
(def ^:private schema:font-size
|
||||||
[:map
|
[:map
|
||||||
[:rotation {:optional true} token-name-ref]
|
[:font-size {:optional true} token-name-ref]])
|
||||||
[:line-height {: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))
|
(def number-keys (schema-keys schema:number))
|
||||||
|
|
||||||
@@ -137,6 +153,7 @@
|
|||||||
spacing-keys
|
spacing-keys
|
||||||
dimensions-keys
|
dimensions-keys
|
||||||
rotation-keys
|
rotation-keys
|
||||||
|
typography-keys
|
||||||
number-keys))
|
number-keys))
|
||||||
|
|
||||||
(def ^:private schema:tokens
|
(def ^:private schema:tokens
|
||||||
@@ -150,6 +167,8 @@
|
|||||||
schema:spacing
|
schema:spacing
|
||||||
schema:rotation
|
schema:rotation
|
||||||
schema:number
|
schema:number
|
||||||
|
schema:font-size
|
||||||
|
schema:letter-spacing
|
||||||
schema:dimensions])
|
schema:dimensions])
|
||||||
|
|
||||||
(defn shape-attr->token-attrs
|
(defn shape-attr->token-attrs
|
||||||
@@ -177,6 +196,8 @@
|
|||||||
changed-sub-attr
|
changed-sub-attr
|
||||||
#{:m1 :m2 :m3 :m4})
|
#{: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}
|
(border-radius-keys shape-attr) #{shape-attr}
|
||||||
(sizing-keys shape-attr) #{shape-attr}
|
(sizing-keys shape-attr) #{shape-attr}
|
||||||
(opacity-keys shape-attr) #{shape-attr}
|
(opacity-keys shape-attr) #{shape-attr}
|
||||||
@@ -192,6 +213,56 @@
|
|||||||
:stroke-width :strokes
|
:stroke-width :strokes
|
||||||
token-attr))
|
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
|
;; TOKENS IN SHAPES
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -218,13 +289,5 @@
|
|||||||
:attributes attributes})]
|
:attributes attributes})]
|
||||||
(update shape :applied-tokens #(merge % applied-tokens))))
|
(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]
|
(defn unapply-token-id [shape attributes]
|
||||||
(update shape :applied-tokens d/without-keys attributes))
|
(update shape :applied-tokens d/without-keys attributes))
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
[app.common.transit :as t]
|
[app.common.transit :as t]
|
||||||
[app.common.types.token :as cto]
|
[app.common.types.token :as cto]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
[clojure.core.protocols :as protocols]
|
||||||
[clojure.set :as set]
|
[clojure.set :as set]
|
||||||
[clojure.walk :as walk]
|
[clojure.walk :as walk]
|
||||||
[cuerdas.core :as str]))
|
[cuerdas.core :as str]))
|
||||||
@@ -25,13 +26,6 @@
|
|||||||
|
|
||||||
;; TODO: add again the removed functions and refactor the rest of the module to use them
|
;; 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
|
(def ^:private xf-map-trim
|
||||||
(comp
|
(comp
|
||||||
(map str/trim)
|
(map str/trim)
|
||||||
@@ -60,14 +54,38 @@
|
|||||||
(defn get-path
|
(defn get-path
|
||||||
"Get the path of object by specified separator (E.g. with '.' separator, the
|
"Get the path of object by specified separator (E.g. with '.' separator, the
|
||||||
'group.subgroup.name' -> ['group' 'subgroup'])"
|
'group.subgroup.name' -> ['group' 'subgroup'])"
|
||||||
[item separator]
|
[name separator]
|
||||||
(assert (valid-groupable-item? item) "expected groupable item")
|
(->> (split-path name separator)
|
||||||
(->> (split-path (:name item) separator)
|
|
||||||
(not-empty)))
|
(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
|
;; === 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?
|
(defn token?
|
||||||
[o]
|
[o]
|
||||||
@@ -109,7 +127,7 @@
|
|||||||
|
|
||||||
(defn get-token-path
|
(defn get-token-path
|
||||||
[token]
|
[token]
|
||||||
(get-path token token-separator))
|
(get-path (:name token) token-separator))
|
||||||
|
|
||||||
(defn find-token-value-references
|
(defn find-token-value-references
|
||||||
"Returns set of token references found in `token-value`.
|
"Returns set of token references found in `token-value`.
|
||||||
@@ -146,9 +164,53 @@
|
|||||||
(update-token [_ token-name f] "update a token in the list")
|
(update-token [_ token-name f] "update a token in the list")
|
||||||
(delete-token [_ token-name] "delete a token from the list")
|
(delete-token [_ token-name] "delete a token from the list")
|
||||||
(get-token [_ token-name] "return token by token-name")
|
(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
|
ITokenSet
|
||||||
(add-token [_ token]
|
(add-token [_ token]
|
||||||
(let [token (check-token token)]
|
(let [token (check-token token)]
|
||||||
@@ -184,7 +246,10 @@
|
|||||||
(get tokens token-name))
|
(get tokens token-name))
|
||||||
|
|
||||||
(get-tokens [_]
|
(get-tokens [_]
|
||||||
(vals tokens)))
|
(vals tokens))
|
||||||
|
|
||||||
|
(get-tokens-map [_]
|
||||||
|
tokens))
|
||||||
|
|
||||||
(defn token-set?
|
(defn token-set?
|
||||||
[o]
|
[o]
|
||||||
@@ -218,10 +283,7 @@
|
|||||||
(declare make-token-set)
|
(declare make-token-set)
|
||||||
|
|
||||||
(def schema:token-set
|
(def schema:token-set
|
||||||
[:and {:gen/gen (->> (sg/generator schema:token-set-attrs)
|
(sm/required-keys schema:token-set-attrs))
|
||||||
(sg/fmap #(make-token-set %)))}
|
|
||||||
(sm/required-keys schema:token-set-attrs)
|
|
||||||
[:fn token-set?]])
|
|
||||||
|
|
||||||
(sm/register! ::token-set schema:token-set) ;; need to register for the recursive schema of token-sets
|
(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
|
(defn make-token-set
|
||||||
[& {:as attrs}]
|
[& {:as attrs}]
|
||||||
(-> attrs
|
(let [attrs (-> attrs
|
||||||
(update :id #(or % (uuid/next)))
|
(update :id #(or % (uuid/next)))
|
||||||
(update :modified-at #(or % (dt/now)))
|
(update :modified-at #(or % (dt/now)))
|
||||||
(update :tokens #(into (d/ordered-map) %))
|
(update :tokens #(into (d/ordered-map) %))
|
||||||
(update :description d/nilv "")
|
(update :description d/nilv "")
|
||||||
(check-token-set-attrs)
|
(check-token-set-attrs))]
|
||||||
(map->TokenSet)))
|
(TokenSet. (:id attrs)
|
||||||
|
(:name attrs)
|
||||||
|
(:description attrs)
|
||||||
|
(:modified-at attrs)
|
||||||
|
(:tokens attrs))))
|
||||||
|
|
||||||
(def ^:private set-prefix "S-")
|
(def ^:private set-prefix "S-")
|
||||||
|
|
||||||
@@ -291,7 +357,7 @@
|
|||||||
|
|
||||||
(defn get-set-path
|
(defn get-set-path
|
||||||
[token-set]
|
[token-set]
|
||||||
(get-path token-set set-separator))
|
(get-path (get-name token-set) set-separator))
|
||||||
|
|
||||||
(defn split-set-name
|
(defn split-set-name
|
||||||
[name]
|
[name]
|
||||||
@@ -315,7 +381,7 @@
|
|||||||
(set-full-path->set-prefixed-full-path)))
|
(set-full-path->set-prefixed-full-path)))
|
||||||
|
|
||||||
(defn get-set-prefixed-path [token-set]
|
(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)))
|
(set-full-path->set-prefixed-full-path path)))
|
||||||
|
|
||||||
(defn prefixed-set-path-string->set-name-string [path-str]
|
(defn prefixed-set-path-string->set-name-string [path-str]
|
||||||
@@ -333,7 +399,7 @@
|
|||||||
(conj name)))
|
(conj name)))
|
||||||
|
|
||||||
(defn tokens-tree
|
(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."
|
Optionally use `update-token-fn` option to transform the token."
|
||||||
[tokens & {:keys [update-token-fn]
|
[tokens & {:keys [update-token-fn]
|
||||||
:or {update-token-fn identity}}]
|
:or {update-token-fn identity}}]
|
||||||
@@ -343,7 +409,7 @@
|
|||||||
{} tokens))
|
{} tokens))
|
||||||
|
|
||||||
(defn backtrace-tokens-tree
|
(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).
|
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."
|
The backtrace can't be the name as the name might not exist when the user is creating a token."
|
||||||
[tokens]
|
[tokens]
|
||||||
@@ -392,7 +458,7 @@
|
|||||||
(get-set [_ set-name] "get one set looking for name"))
|
(get-set [_ set-name] "get one set looking for name"))
|
||||||
|
|
||||||
(def schema:token-set-node
|
(def schema:token-set-node
|
||||||
[:schema {:registry {::node [:or ::token-set
|
[:schema {:registry {::node [:or [:fn token-set?]
|
||||||
[:and
|
[:and
|
||||||
[:map-of {:gen/max 5} :string [:ref ::node]]
|
[:map-of {:gen/max 5} :string [:ref ::node]]
|
||||||
[:fn d/ordered-map?]]]}}
|
[:fn d/ordered-map?]]]}}
|
||||||
@@ -443,6 +509,22 @@
|
|||||||
(hidden-theme? [_] "if a theme is the (from the user ui) hidden temporary theme"))
|
(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]
|
(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
|
ITokenTheme
|
||||||
(set-sets [_ set-names]
|
(set-sets [_ set-names]
|
||||||
(TokenTheme. id
|
(TokenTheme. id
|
||||||
@@ -528,13 +610,17 @@
|
|||||||
|
|
||||||
(defn make-token-theme
|
(defn make-token-theme
|
||||||
[& {:as attrs}]
|
[& {:as attrs}]
|
||||||
(let [id (uuid/next)]
|
(let [new-id (uuid/next)]
|
||||||
(-> attrs
|
(-> 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 :group d/nilv top-level-theme-group-name)
|
||||||
(update :description d/nilv "")
|
(update :description d/nilv "")
|
||||||
(update :is-source d/nilv false)
|
(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 :modified-at #(or % (dt/now)))
|
||||||
(update :sets set)
|
(update :sets set)
|
||||||
(check-token-theme-attrs)
|
(check-token-theme-attrs)
|
||||||
@@ -618,7 +704,7 @@
|
|||||||
;; Set
|
;; Set
|
||||||
(and v (instance? TokenSet v))
|
(and v (instance? TokenSet v))
|
||||||
[{:group? false
|
[{:group? false
|
||||||
:path (split-set-name (:name v))
|
:path (split-set-name (get-name v))
|
||||||
:parent-path parent
|
:parent-path parent
|
||||||
:depth depth
|
:depth depth
|
||||||
:set v}]
|
:set v}]
|
||||||
@@ -664,7 +750,7 @@
|
|||||||
|
|
||||||
;; Set
|
;; Set
|
||||||
(and v (instance? TokenSet v))
|
(and v (instance? TokenSet v))
|
||||||
(let [name (:name v)]
|
(let [name (get-name v)]
|
||||||
[{:is-group false
|
[{:is-group false
|
||||||
:path (split-set-name name)
|
:path (split-set-name name)
|
||||||
:id name
|
:id name
|
||||||
@@ -725,8 +811,14 @@ Will return a value that matches this schema:
|
|||||||
(declare export-dtcg-json)
|
(declare export-dtcg-json)
|
||||||
|
|
||||||
(deftype TokensLib [sets themes active-themes]
|
(deftype TokensLib [sets themes active-themes]
|
||||||
;; NOTE: This is only for debug purposes, pending to properly
|
;; This is to convert the TokensLib to a plain map, for debugging or unit tests.
|
||||||
;; implement the toString and alternative printing.
|
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
|
#?@(:clj [clojure.lang.IDeref
|
||||||
(deref [_] {:sets sets
|
(deref [_] {:sets sets
|
||||||
:themes themes
|
:themes themes
|
||||||
@@ -746,8 +838,8 @@ Will return a value that matches this schema:
|
|||||||
|
|
||||||
ITokenSets
|
ITokenSets
|
||||||
(add-set [_ token-set]
|
(add-set [_ token-set]
|
||||||
(let [path (get-set-prefixed-path token-set)
|
(assert (token-set? token-set) "expected valid token-set")
|
||||||
token-set (check-token-set token-set)]
|
(let [path (get-set-prefixed-path token-set)]
|
||||||
(TokensLib. (d/oassoc-in sets path token-set)
|
(TokensLib. (d/oassoc-in sets path token-set)
|
||||||
themes
|
themes
|
||||||
active-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)
|
(let [prefixed-full-path (set-name->prefixed-full-path set-name)
|
||||||
set (get-in sets prefixed-full-path)]
|
set (get-in sets prefixed-full-path)]
|
||||||
(if set
|
(if set
|
||||||
(let [set' (-> (make-token-set (f set))
|
(let [set' (f set)
|
||||||
(assoc :modified-at (dt/now)))
|
|
||||||
prefixed-full-path' (get-set-prefixed-path 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?
|
(if name-changed?
|
||||||
(TokensLib. (-> sets
|
(TokensLib. (-> sets
|
||||||
(d/oassoc-in-before prefixed-full-path prefixed-full-path' set')
|
(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
|
(walk/postwalk
|
||||||
(fn [form]
|
(fn [form]
|
||||||
(if (instance? TokenTheme form)
|
(if (instance? TokenTheme form)
|
||||||
(update-set-name form (:name set) (:name set'))
|
(update-set-name form (get-name set) (get-name set'))
|
||||||
form))
|
form))
|
||||||
themes)
|
themes)
|
||||||
active-themes)
|
active-themes)
|
||||||
@@ -791,7 +882,7 @@ Will return a value that matches this schema:
|
|||||||
(let [path (split-set-name set-group-name)
|
(let [path (split-set-name set-group-name)
|
||||||
prefixed-path (map add-set-group-prefix path)
|
prefixed-path (map add-set-group-prefix path)
|
||||||
child-set-names (->> (get-sets-at-path this path)
|
child-set-names (->> (get-sets-at-path this path)
|
||||||
(map :name)
|
(map get-name)
|
||||||
(into #{}))]
|
(into #{}))]
|
||||||
(TokensLib. (d/dissoc-in sets prefixed-path)
|
(TokensLib. (d/dissoc-in sets prefixed-path)
|
||||||
(walk/postwalk
|
(walk/postwalk
|
||||||
@@ -833,7 +924,7 @@ Will return a value that matches this schema:
|
|||||||
(set-full-path->set-prefixed-full-path before-path)))
|
(set-full-path->set-prefixed-full-path before-path)))
|
||||||
|
|
||||||
set
|
set
|
||||||
(assoc prev-set :name (join-set-path to-path))
|
(rename prev-set (join-set-path to-path))
|
||||||
|
|
||||||
reorder?
|
reorder?
|
||||||
(= prefixed-from-path prefixed-to-path)
|
(= prefixed-from-path prefixed-to-path)
|
||||||
@@ -856,7 +947,7 @@ Will return a value that matches this schema:
|
|||||||
(walk/postwalk
|
(walk/postwalk
|
||||||
(fn [form]
|
(fn [form]
|
||||||
(if (instance? TokenTheme 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))
|
form))
|
||||||
themes))
|
themes))
|
||||||
active-themes))
|
active-themes))
|
||||||
@@ -888,15 +979,15 @@ Will return a value that matches this schema:
|
|||||||
(d/oupdate-in prefixed-to-path (fn [sets]
|
(d/oupdate-in prefixed-to-path (fn [sets]
|
||||||
(walk/prewalk
|
(walk/prewalk
|
||||||
(fn [form]
|
(fn [form]
|
||||||
(if (instance? TokenSet form)
|
(if (token-set? form)
|
||||||
(update form :name #(str to-path-str (str/strip-prefix % from-path-str)))
|
(rename form (str to-path-str (str/strip-prefix (get-name form) from-path-str)))
|
||||||
form))
|
form))
|
||||||
sets)))))
|
sets)))))
|
||||||
themes' (if reorder?
|
themes' (if reorder?
|
||||||
themes
|
themes
|
||||||
(let [rename-sets-map (->> (get-sets-at-path this from-path)
|
(let [rename-sets-map (->> (get-sets-at-path this from-path)
|
||||||
(map (fn [set]
|
(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 {}))]
|
(into {}))]
|
||||||
(walk/postwalk
|
(walk/postwalk
|
||||||
(fn [form]
|
(fn [form]
|
||||||
@@ -934,12 +1025,12 @@ Will return a value that matches this schema:
|
|||||||
sets (get-sets-at-path this path)]
|
sets (get-sets-at-path this path)]
|
||||||
(reduce
|
(reduce
|
||||||
(fn [lib set]
|
(fn [lib set]
|
||||||
(update-set lib (:name set) (fn [set']
|
(update-set lib (get-name set) (fn [set']
|
||||||
(update set' :name #(str to-path-str (str/strip-prefix % from-path-str))))))
|
(rename set' (str to-path-str (str/strip-prefix (get-name set') from-path-str))))))
|
||||||
this sets)))
|
this sets)))
|
||||||
|
|
||||||
(get-ordered-set-names [this]
|
(get-ordered-set-names [this]
|
||||||
(map :name (get-sets this)))
|
(map get-name (get-sets this)))
|
||||||
|
|
||||||
(set-count [this]
|
(set-count [this]
|
||||||
(count (get-sets 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)]
|
prefixed-path-str (set-group-path->set-group-prefixed-path-str group-path)]
|
||||||
(if (seq active-set-names)
|
(if (seq active-set-names)
|
||||||
(let [path-active-set-names (->> (get-sets-at-prefix-path this prefixed-path-str)
|
(let [path-active-set-names (->> (get-sets-at-prefix-path this prefixed-path-str)
|
||||||
(map :name)
|
(map get-name)
|
||||||
(into #{}))
|
(into #{}))
|
||||||
difference (set/difference path-active-set-names active-set-names)]
|
difference (set/difference path-active-set-names active-set-names)]
|
||||||
(cond
|
(cond
|
||||||
@@ -1095,7 +1186,7 @@ Will return a value that matches this schema:
|
|||||||
active-set-names (filter theme-set-names all-set-names)
|
active-set-names (filter theme-set-names all-set-names)
|
||||||
tokens (reduce (fn [tokens set-name]
|
tokens (reduce (fn [tokens set-name]
|
||||||
(let [set (get-set this set-name)]
|
(let [set (get-set this set-name)]
|
||||||
(merge tokens (:tokens set))))
|
(merge tokens (get-tokens-map set))))
|
||||||
(d/ordered-map)
|
(d/ordered-map)
|
||||||
active-set-names)]
|
active-set-names)]
|
||||||
tokens))
|
tokens))
|
||||||
@@ -1160,11 +1251,10 @@ Will return a value that matches this schema:
|
|||||||
|
|
||||||
(defn duplicate-set [set-name lib & {:keys [suffix]}]
|
(defn duplicate-set [set-name lib & {:keys [suffix]}]
|
||||||
(let [sets (get-sets lib)
|
(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)]
|
copy-name (cfh/generate-unique-name set-name unames :suffix suffix)]
|
||||||
(some-> (get-set lib set-name)
|
(some-> (get-set lib set-name)
|
||||||
(assoc :name copy-name)
|
(rename copy-name))))
|
||||||
(assoc :modified-at (dt/now)))))
|
|
||||||
|
|
||||||
;; === Import / Export from JSON format
|
;; === Import / Export from JSON format
|
||||||
|
|
||||||
@@ -1473,8 +1563,10 @@ Will return a value that matches this schema:
|
|||||||
[tokens-lib]
|
[tokens-lib]
|
||||||
(let [{:keys [themes active-themes]} (dtcg-export-themes tokens-lib)
|
(let [{:keys [themes active-themes]} (dtcg-export-themes tokens-lib)
|
||||||
sets (->> (get-sets tokens-lib)
|
sets (->> (get-sets tokens-lib)
|
||||||
(map (fn [{:keys [name tokens]}]
|
(map (fn [token-set]
|
||||||
[(str name ".json") (tokens-tree tokens :update-token-fn token->dtcg-token)]))
|
(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 {}))]
|
(into {}))]
|
||||||
(-> sets
|
(-> sets
|
||||||
(assoc "$themes.json" themes)
|
(assoc "$themes.json" themes)
|
||||||
@@ -1491,8 +1583,9 @@ Will return a value that matches this schema:
|
|||||||
(->> (get-set-tree tokens-lib)
|
(->> (get-set-tree tokens-lib)
|
||||||
(tree-seq d/ordered-map? vals)
|
(tree-seq d/ordered-map? vals)
|
||||||
(filter (partial instance? TokenSet))
|
(filter (partial instance? TokenSet))
|
||||||
(map (fn [{:keys [name tokens]}]
|
(map (fn [set]
|
||||||
[name (tokens-tree tokens :update-token-fn token->dtcg-token)])))
|
[(get-name set)
|
||||||
|
(tokens-tree (get-tokens-map set) :update-token-fn token->dtcg-token)])))
|
||||||
|
|
||||||
ordered-set-names
|
ordered-set-names
|
||||||
(mapv first name-set-tuples)
|
(mapv first name-set-tuples)
|
||||||
@@ -1512,28 +1605,31 @@ Will return a value that matches this schema:
|
|||||||
(defn get-tokens-of-unknown-type
|
(defn get-tokens-of-unknown-type
|
||||||
"Search for all tokens in the decoded json file that have a type that is not currently
|
"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."
|
supported by Penpot. Returns a map token-path -> token type."
|
||||||
([decoded-json]
|
[decoded-json {:keys [json-format parent-path process-token-type]
|
||||||
(get-tokens-of-unknown-type decoded-json "" (get-json-format decoded-json)))
|
:or {json-format (get-json-format decoded-json)
|
||||||
([decoded-json parent-path json-format]
|
parent-path ""
|
||||||
(let [type-key (if (= json-format :json-format/dtcg) "$type" "type")]
|
process-token-type identity}
|
||||||
(reduce-kv
|
:as opts}]
|
||||||
(fn [unknown-tokens k v]
|
(let [type-key (if (= json-format :json-format/dtcg) "$type" "type")]
|
||||||
(let [child-path (if (empty? parent-path)
|
(reduce-kv
|
||||||
(name k)
|
(fn [unknown-tokens k v]
|
||||||
(str parent-path "." k))]
|
(let [child-path (if (empty? parent-path)
|
||||||
(if (and (map? v)
|
(name k)
|
||||||
(not (contains? v type-key)))
|
(str parent-path "." k))]
|
||||||
(let [nested-unknown-tokens (get-tokens-of-unknown-type v child-path json-format)]
|
(if (and (map? v)
|
||||||
(merge unknown-tokens nested-unknown-tokens))
|
(not (contains? v type-key)))
|
||||||
(let [token-type-str (get v type-key)
|
(let [nested-unknown-tokens (get-tokens-of-unknown-type v (assoc opts :parent-path child-path))]
|
||||||
token-type (cto/dtcg-token-type->token-type token-type-str)]
|
(merge unknown-tokens nested-unknown-tokens))
|
||||||
(if (and (not (some? token-type)) (some? token-type-str))
|
(let [token-type-str (get v type-key)
|
||||||
(assoc unknown-tokens child-path token-type-str)
|
token-type (-> (cto/dtcg-token-type->token-type token-type-str)
|
||||||
unknown-tokens)))))
|
(process-token-type))]
|
||||||
nil
|
(if (and (not (some? token-type)) (some? token-type-str))
|
||||||
decoded-json))))
|
(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!
|
(t/add-handlers!
|
||||||
{:id "penpot/tokens-lib"
|
{:id "penpot/tokens-lib"
|
||||||
@@ -1543,8 +1639,8 @@ Will return a value that matches this schema:
|
|||||||
|
|
||||||
{:id "penpot/token-set"
|
{:id "penpot/token-set"
|
||||||
:class TokenSet
|
:class TokenSet
|
||||||
:wfn #(into {} %)
|
:wfn deref
|
||||||
:rfn #(map->TokenSet %)}
|
:rfn #(make-token-set %)}
|
||||||
|
|
||||||
{:id "penpot/token-theme"
|
{:id "penpot/token-theme"
|
||||||
:class TokenTheme
|
:class TokenTheme
|
||||||
@@ -1556,6 +1652,8 @@ Will return a value that matches this schema:
|
|||||||
:wfn #(into {} %)
|
:wfn #(into {} %)
|
||||||
:rfn #(map->Token %)})
|
:rfn #(map->Token %)})
|
||||||
|
|
||||||
|
;; === Serialization handlers for database (fressian)
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(defn- read-tokens-lib-v1-0
|
(defn- read-tokens-lib-v1-0
|
||||||
"Reads the first version of tokens lib, now completly obsolete"
|
"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)))
|
(fres/write-object! w (into {} o)))
|
||||||
:rfn (fn [r]
|
:rfn (fn [r]
|
||||||
(let [obj (fres/read-object! r)]
|
(let [obj (fres/read-object! r)]
|
||||||
(map->Token obj)))}
|
(make-token obj)))}
|
||||||
|
|
||||||
{:name "penpot/token-set/v1"
|
{:name "penpot/token-set/v1"
|
||||||
:class TokenSet
|
:class TokenSet
|
||||||
:wfn (fn [n w o]
|
:wfn (fn [n w o]
|
||||||
(fres/write-tag! w n 1)
|
(fres/write-tag! w n 1)
|
||||||
(fres/write-object! w (into {} o)))
|
(fres/write-object! w (into {} (deref o))))
|
||||||
:rfn (fn [r]
|
:rfn (fn [r]
|
||||||
(let [obj (fres/read-object! r)]
|
(let [obj (fres/read-object! r)]
|
||||||
(map->TokenSet obj)))}
|
(make-token-set obj)))}
|
||||||
|
|
||||||
{:name "penpot/token-theme/v1"
|
{:name "penpot/token-theme/v1"
|
||||||
:class TokenTheme
|
:class TokenTheme
|
||||||
@@ -1693,7 +1791,7 @@ Will return a value that matches this schema:
|
|||||||
(fres/write-object! w (into {} o)))
|
(fres/write-object! w (into {} o)))
|
||||||
:rfn (fn [r]
|
:rfn (fn [r]
|
||||||
(let [obj (fres/read-object! r)]
|
(let [obj (fres/read-object! r)]
|
||||||
(map->TokenTheme obj)))}
|
(make-token-theme obj)))}
|
||||||
|
|
||||||
;; LEGACY TOKENS LIB READERS (with migrations)
|
;; LEGACY TOKENS LIB READERS (with migrations)
|
||||||
{:name "penpot/tokens-lib/v1"
|
{:name "penpot/tokens-lib/v1"
|
||||||
|
|||||||
@@ -93,13 +93,16 @@
|
|||||||
remap-typography
|
remap-typography
|
||||||
content)))))
|
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
|
(defn remove-external-typographies
|
||||||
"Change the shape so that any use of an external typography now is removed"
|
"Change the shape so that any use of an external typography now is removed"
|
||||||
[shape file-id]
|
[shape file-id]
|
||||||
(let [remove-ref-file #(dissoc % :typography-ref-file :typography-ref-id)]
|
(update shape :content
|
||||||
|
(fn [content]
|
||||||
(update shape :content
|
(txt/transform-nodes #(not= (:typography-ref-file %) file-id)
|
||||||
(fn [content]
|
remove-typography-from-node
|
||||||
(txt/transform-nodes #(not= (:typography-ref-file %) file-id)
|
content))))
|
||||||
remove-ref-file
|
|
||||||
content)))))
|
|
||||||
|
|||||||
@@ -139,7 +139,6 @@
|
|||||||
(< (count (first %)) property-max-length)
|
(< (count (first %)) property-max-length)
|
||||||
(< (count (second %)) property-max-length)))))
|
(< (count (second %)) property-max-length)))))
|
||||||
|
|
||||||
|
|
||||||
(defn find-properties-to-remove
|
(defn find-properties-to-remove
|
||||||
"Compares two property maps to find which properties should be removed"
|
"Compares two property maps to find which properties should be removed"
|
||||||
[prev-props upd-props]
|
[prev-props upd-props]
|
||||||
@@ -161,6 +160,46 @@
|
|||||||
(filterv #(not (contains? prev-names (:name %))) upd-props)))
|
(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
|
(defn find-index-for-property-name
|
||||||
"Finds the index of a name in a property map"
|
"Finds the index of a name in a property map"
|
||||||
[props name]
|
[props name]
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
(:require
|
(:require
|
||||||
#?(:cljs [goog.color :as gcolors])
|
#?(:cljs [goog.color :as gcolors])
|
||||||
[app.common.colors :as colors]
|
[app.common.colors :as colors]
|
||||||
[app.common.data :as d]
|
|
||||||
[clojure.test :as t]))
|
[clojure.test :as t]))
|
||||||
|
|
||||||
(t/deftest valid-hex-color
|
(t/deftest valid-hex-color
|
||||||
@@ -52,8 +51,8 @@
|
|||||||
(t/is (= [1 2 3] (colors/hex->rgb "#010203"))))
|
(t/is (= [1 2 3] (colors/hex->rgb "#010203"))))
|
||||||
|
|
||||||
(t/deftest format-hsla
|
(t/deftest format-hsla
|
||||||
(t/is (= "210, 50%, 0.78%, 1" (colors/format-hsla [210.0 0.5 0.00784313725490196 1])))
|
(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 (= "220 5% 30% / 0.8" (colors/format-hsla [220.0 0.05 0.3 0.8]))))
|
||||||
|
|
||||||
(t/deftest format-rgba
|
(t/deftest format-rgba
|
||||||
(t/is (= "210, 199, 12, 0.08" (colors/format-rgba [210 199 12 0.08])))
|
(t/is (= "210, 199, 12, 0.08" (colors/format-rgba [210 199 12 0.08])))
|
||||||
|
|||||||
881
common/test/common_tests/logic/text_sync_test.cljc
Normal file
881
common/test/common_tests/logic/text_sync_test.cljc
Normal file
@@ -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)))))
|
||||||
132
common/test/common_tests/logic/text_touched_test.cljc
Normal file
132
common/test/common_tests/logic/text_touched_test.cljc
Normal file
@@ -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')))))
|
||||||
|
|
||||||
@@ -54,9 +54,17 @@
|
|||||||
(ctob/add-token-in-set "test-token-set"
|
(ctob/add-token-in-set "test-token-set"
|
||||||
(ctob/make-token :name "token-dimensions"
|
(ctob/make-token :name "token-dimensions"
|
||||||
:type :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-frame :frame1)
|
||||||
(tho/add-text :text1 "Hello World")))
|
(tho/add-text :text1 "Hello World!")))
|
||||||
|
|
||||||
(defn- apply-all-tokens
|
(defn- apply-all-tokens
|
||||||
[file]
|
[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" [:stroke-color] [:stroke-color] "#00ff00")
|
||||||
(tht/apply-token-to-shape :frame1 "token-color" [:fill] [:fill] "#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 :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
|
(t/deftest apply-tokens-to-shape
|
||||||
(let [;; ==== Setup
|
(let [;; ==== Setup
|
||||||
file (setup-file)
|
file (setup-file)
|
||||||
page (thf/current-page file)
|
page (thf/current-page file)
|
||||||
frame1 (ths/get-shape file :frame1)
|
frame1 (ths/get-shape file :frame1)
|
||||||
|
text1 (ths/get-shape file :text1)
|
||||||
token-radius (tht/get-token file "test-token-set" "token-radius")
|
token-radius (tht/get-token file "test-token-set" "token-radius")
|
||||||
token-rotation (tht/get-token file "test-token-set" "token-rotation")
|
token-rotation (tht/get-token file "test-token-set" "token-rotation")
|
||||||
token-opacity (tht/get-token file "test-token-set" "token-opacity")
|
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-stroke-width (tht/get-token file "test-token-set" "token-stroke-width")
|
||||||
token-color (tht/get-token file "test-token-set" "token-color")
|
token-color (tht/get-token file "test-token-set" "token-color")
|
||||||
token-dimensions (tht/get-token file "test-token-set" "token-dimensions")
|
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
|
;; ==== Action
|
||||||
changes (-> (-> (pcb/empty-changes nil)
|
changes (-> (-> (pcb/empty-changes nil)
|
||||||
@@ -89,38 +101,48 @@
|
|||||||
(cls/generate-update-shapes [(:id frame1)]
|
(cls/generate-update-shapes [(:id frame1)]
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
(as-> shape $
|
(as-> shape $
|
||||||
(cto/maybe-apply-token-to-shape {:token nil ; test nil case
|
(cto/apply-token-to-shape {:token token-radius
|
||||||
:shape $
|
:shape $
|
||||||
:attributes []})
|
:attributes [:r1 :r2 :r3 :r4]})
|
||||||
(cto/maybe-apply-token-to-shape {:token token-radius
|
(cto/apply-token-to-shape {:token token-rotation
|
||||||
:shape $
|
:shape $
|
||||||
:attributes [:r1 :r2 :r3 :r4]})
|
:attributes [:rotation]})
|
||||||
(cto/maybe-apply-token-to-shape {:token token-rotation
|
(cto/apply-token-to-shape {:token token-opacity
|
||||||
:shape $
|
:shape $
|
||||||
:attributes [:rotation]})
|
:attributes [:opacity]})
|
||||||
(cto/maybe-apply-token-to-shape {:token token-opacity
|
(cto/apply-token-to-shape {:token token-stroke-width
|
||||||
:shape $
|
:shape $
|
||||||
:attributes [:opacity]})
|
:attributes [:stroke-width]})
|
||||||
(cto/maybe-apply-token-to-shape {:token token-stroke-width
|
(cto/apply-token-to-shape {:token token-color
|
||||||
:shape $
|
:shape $
|
||||||
:attributes [:stroke-width]})
|
:attributes [:stroke-color]})
|
||||||
(cto/maybe-apply-token-to-shape {:token token-color
|
(cto/apply-token-to-shape {:token token-color
|
||||||
:shape $
|
:shape $
|
||||||
:attributes [:stroke-color]})
|
:attributes [:fill]})
|
||||||
(cto/maybe-apply-token-to-shape {:token token-color
|
(cto/apply-token-to-shape {:token token-dimensions
|
||||||
:shape $
|
:shape $
|
||||||
:attributes [:fill]})
|
:attributes [:width :height]})))
|
||||||
(cto/maybe-apply-token-to-shape {:token token-dimensions
|
(:objects page)
|
||||||
:shape $
|
{})
|
||||||
:attributes [:width :height]})))
|
(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)
|
(:objects page)
|
||||||
{}))
|
{}))
|
||||||
|
|
||||||
file' (thf/apply-changes file changes)
|
file' (thf/apply-changes file changes)
|
||||||
|
|
||||||
;; ==== Get
|
;; ==== Get
|
||||||
frame1' (ths/get-shape file' :frame1)
|
frame1' (ths/get-shape file' :frame1)
|
||||||
applied-tokens' (:applied-tokens frame1')]
|
applied-tokens' (:applied-tokens frame1')
|
||||||
|
text1' (ths/get-shape file' :text1)
|
||||||
|
text1-applied-tokens (:applied-tokens text1')]
|
||||||
|
|
||||||
;; ==== Check
|
;; ==== Check
|
||||||
(t/is (= (count applied-tokens') 11))
|
(t/is (= (count applied-tokens') 11))
|
||||||
@@ -134,7 +156,10 @@
|
|||||||
(t/is (= (:stroke-color applied-tokens') "token-color"))
|
(t/is (= (:stroke-color applied-tokens') "token-color"))
|
||||||
(t/is (= (:fill applied-tokens') "token-color"))
|
(t/is (= (:fill applied-tokens') "token-color"))
|
||||||
(t/is (= (:width applied-tokens') "token-dimensions"))
|
(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
|
(t/deftest unapply-tokens-from-shape
|
||||||
(let [;; ==== Setup
|
(let [;; ==== Setup
|
||||||
@@ -142,6 +167,7 @@
|
|||||||
(apply-all-tokens))
|
(apply-all-tokens))
|
||||||
page (thf/current-page file)
|
page (thf/current-page file)
|
||||||
frame1 (ths/get-shape file :frame1)
|
frame1 (ths/get-shape file :frame1)
|
||||||
|
text1 (ths/get-shape file :text1)
|
||||||
|
|
||||||
;; ==== Action
|
;; ==== Action
|
||||||
changes (-> (-> (pcb/empty-changes nil)
|
changes (-> (-> (pcb/empty-changes nil)
|
||||||
@@ -158,16 +184,26 @@
|
|||||||
(cto/unapply-token-id [:fill])
|
(cto/unapply-token-id [:fill])
|
||||||
(cto/unapply-token-id [:width :height])))
|
(cto/unapply-token-id [:width :height])))
|
||||||
(:objects page)
|
(: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)
|
file' (thf/apply-changes file changes)
|
||||||
|
|
||||||
;; ==== Get
|
;; ==== Get
|
||||||
frame1' (ths/get-shape file' :frame1)
|
frame1' (ths/get-shape file' :frame1)
|
||||||
applied-tokens' (:applied-tokens frame1')]
|
applied-tokens' (:applied-tokens frame1')
|
||||||
|
text1' (ths/get-shape file' :text1)
|
||||||
|
text1-applied-tokens (:applied-tokens text1')]
|
||||||
|
|
||||||
;; ==== Check
|
;; ==== 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
|
(t/deftest unapply-tokens-automatic
|
||||||
(let [;; ==== Setup
|
(let [;; ==== Setup
|
||||||
@@ -202,7 +238,9 @@
|
|||||||
shape
|
shape
|
||||||
txt/is-content-node?
|
txt/is-content-node?
|
||||||
d/txt-merge
|
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)
|
(:objects page)
|
||||||
{}))
|
{}))
|
||||||
|
|
||||||
@@ -216,4 +254,4 @@
|
|||||||
|
|
||||||
;; ==== Check
|
;; ==== Check
|
||||||
(t/is (= (count applied-tokens-frame') 0))
|
(t/is (= (count applied-tokens-frame') 0))
|
||||||
(t/is (= (count applied-tokens-text') 0))))
|
(t/is (= (count applied-tokens-text') 0))))
|
||||||
|
|||||||
@@ -269,8 +269,7 @@
|
|||||||
new-set-name "foo1"
|
new-set-name "foo1"
|
||||||
changes (-> (pcb/empty-changes)
|
changes (-> (pcb/empty-changes)
|
||||||
(pcb/with-library-data (:data file))
|
(pcb/with-library-data (:data file))
|
||||||
(pcb/set-token-set set-name false (assoc prev-token-set
|
(pcb/set-token-set set-name false (ctob/rename prev-token-set new-set-name)))
|
||||||
:name new-set-name)))
|
|
||||||
redo (thf/apply-changes file changes)
|
redo (thf/apply-changes file changes)
|
||||||
redo-lib (tht/get-tokens-lib redo)
|
redo-lib (tht/get-tokens-lib redo)
|
||||||
undo (thf/apply-undo-changes redo changes)
|
undo (thf/apply-undo-changes redo changes)
|
||||||
|
|||||||
1125
common/test/common_tests/logic/variants_switch_test.cljc
Normal file
1125
common/test/common_tests/logic/variants_switch_test.cljc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@
|
|||||||
[app.common.time :as dt]
|
[app.common.time :as dt]
|
||||||
[app.common.transit :as tr]
|
[app.common.transit :as tr]
|
||||||
[app.common.types.tokens-lib :as ctob]
|
[app.common.types.tokens-lib :as ctob]
|
||||||
|
[clojure.datafy :refer [datafy]]
|
||||||
[clojure.test :as t]))
|
[clojure.test :as t]))
|
||||||
|
|
||||||
(defn setup-virtual-time
|
(defn setup-virtual-time
|
||||||
@@ -72,14 +73,14 @@
|
|||||||
:modified-at now
|
:modified-at now
|
||||||
:tokens [])]
|
:tokens [])]
|
||||||
|
|
||||||
(t/is (= (:name token-set1) "test-token-set-1"))
|
(t/is (= (ctob/get-name token-set1) "test-token-set-1"))
|
||||||
(t/is (= (:description token-set1) ""))
|
(t/is (= (ctob/get-description token-set1) ""))
|
||||||
(t/is (some? (:modified-at token-set1)))
|
(t/is (some? (ctob/get-modified-at token-set1)))
|
||||||
(t/is (empty? (:tokens token-set1)))
|
(t/is (empty? (ctob/get-tokens-map token-set1)))
|
||||||
(t/is (= (:name token-set2) "test-token-set-2"))
|
(t/is (= (ctob/get-name token-set2) "test-token-set-2"))
|
||||||
(t/is (= (:description token-set2) "test description"))
|
(t/is (= (ctob/get-description token-set2) "test description"))
|
||||||
(t/is (= (:modified-at token-set2) now))
|
(t/is (= (ctob/get-modified-at token-set2) now))
|
||||||
(t/is (empty? (:tokens token-set2)))))
|
(t/is (empty? (ctob/get-tokens-map token-set2)))))
|
||||||
|
|
||||||
(t/deftest make-invalid-token-set
|
(t/deftest make-invalid-token-set
|
||||||
(let [params {:name 777 :description 999}]
|
(let [params {:name 777 :description 999}]
|
||||||
@@ -183,7 +184,7 @@
|
|||||||
:type :boolean
|
:type :boolean
|
||||||
:value true)})))
|
:value true)})))
|
||||||
expected (-> (ctob/get-set tokens-lib "A")
|
expected (-> (ctob/get-set tokens-lib "A")
|
||||||
(get :tokens)
|
(ctob/get-tokens-map)
|
||||||
(ctob/tokens-tree))]
|
(ctob/tokens-tree))]
|
||||||
(t/is (= (get-in expected ["foo" "bar" "baz" :name]) "foo.bar.baz"))
|
(t/is (= (get-in expected ["foo" "bar" "baz" :name]) "foo.bar.baz"))
|
||||||
(t/is (= (get-in expected ["foo" "bar" "bam" :name]) "foo.bar.bam"))
|
(t/is (= (get-in expected ["foo" "bar" "bam" :name]) "foo.bar.bam"))
|
||||||
@@ -249,20 +250,18 @@
|
|||||||
tokens-lib' (-> tokens-lib
|
tokens-lib' (-> tokens-lib
|
||||||
(ctob/update-set "test-token-set"
|
(ctob/update-set "test-token-set"
|
||||||
(fn [token-set]
|
(fn [token-set]
|
||||||
(assoc token-set
|
(ctob/set-description token-set "some description")))
|
||||||
:description "some description")))
|
|
||||||
(ctob/update-set "not-existing-set"
|
(ctob/update-set "not-existing-set"
|
||||||
(fn [token-set]
|
(fn [token-set]
|
||||||
(assoc token-set
|
(ctob/set-description token-set "no-effect"))))
|
||||||
:description "no-effect"))))
|
|
||||||
|
|
||||||
token-set (ctob/get-set tokens-lib "test-token-set")
|
token-set (ctob/get-set tokens-lib "test-token-set")
|
||||||
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 (= (ctob/set-count tokens-lib') 1))
|
||||||
(t/is (= (:name token-set') "test-token-set"))
|
(t/is (= (ctob/get-name token-set') "test-token-set"))
|
||||||
(t/is (= (:description token-set') "some description"))
|
(t/is (= (ctob/get-description token-set') "some description"))
|
||||||
(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 rename-token-set
|
(t/deftest rename-token-set
|
||||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
@@ -271,15 +270,14 @@
|
|||||||
tokens-lib' (-> tokens-lib
|
tokens-lib' (-> tokens-lib
|
||||||
(ctob/update-set "test-token-set"
|
(ctob/update-set "test-token-set"
|
||||||
(fn [token-set]
|
(fn [token-set]
|
||||||
(assoc token-set
|
(ctob/rename token-set "updated-name"))))
|
||||||
:name "updated-name"))))
|
|
||||||
|
|
||||||
token-set (ctob/get-set tokens-lib "test-token-set")
|
token-set (ctob/get-set tokens-lib "test-token-set")
|
||||||
token-set' (ctob/get-set tokens-lib' "updated-name")]
|
token-set' (ctob/get-set tokens-lib' "updated-name")]
|
||||||
|
|
||||||
(t/is (= (ctob/set-count tokens-lib') 1))
|
(t/is (= (ctob/set-count tokens-lib') 1))
|
||||||
(t/is (= (:name token-set') "updated-name"))
|
(t/is (= (ctob/get-name token-set') "updated-name"))
|
||||||
(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 rename-token-set-group
|
(t/deftest rename-token-set-group
|
||||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
@@ -323,11 +321,11 @@
|
|||||||
:type :boolean
|
:type :boolean
|
||||||
:value true)})))
|
:value true)})))
|
||||||
token-set-copy (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 (some? token-set-copy))
|
||||||
(t/is (= (:name token-set-copy) "test-token-set-copy"))
|
(t/is (= (ctob/get-name token-set-copy) "test-token-set-copy"))
|
||||||
(t/is (= (count (:tokens token-set-copy)) 1))
|
(t/is (= (count (ctob/get-tokens-map token-set-copy)) 1))
|
||||||
(t/is (= (:name token) "test-token"))))
|
(t/is (= (:name token) "test-token"))))
|
||||||
|
|
||||||
(t/deftest duplicate-token-set-twice
|
(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"}))
|
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-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 (some? token-set-copy))
|
||||||
(t/is (= (:name token-set-copy) "test-token-set-copy-2"))
|
(t/is (= (ctob/get-name token-set-copy) "test-token-set-copy-2"))
|
||||||
(t/is (= (count (:tokens token-set-copy)) 1))
|
(t/is (= (count (ctob/get-tokens-map token-set-copy)) 1))
|
||||||
(t/is (= (:name token) "test-token"))))
|
(t/is (= (:name token) "test-token"))))
|
||||||
|
|
||||||
(t/deftest duplicate-empty-token-set
|
(t/deftest duplicate-empty-token-set
|
||||||
@@ -353,11 +351,11 @@
|
|||||||
(ctob/add-set (ctob/make-token-set :name "test-token-set")))
|
(ctob/add-set (ctob/make-token-set :name "test-token-set")))
|
||||||
|
|
||||||
token-set-copy (ctob/duplicate-set "test-token-set" tokens-lib {:suffix "copy"})
|
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 (some? token-set-copy))
|
||||||
(t/is (= (:name token-set-copy) "test-token-set-copy"))
|
(t/is (= (ctob/get-name token-set-copy) "test-token-set-copy"))
|
||||||
(t/is (= (count (:tokens token-set-copy)) 0))
|
(t/is (= (count (ctob/get-tokens-map token-set-copy)) 0))
|
||||||
(t/is (= (count tokens) 0))))
|
(t/is (= (count tokens) 0))))
|
||||||
|
|
||||||
(t/deftest duplicate-not-existing-token-set
|
(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-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 (= (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 (= (: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
|
(t/deftest update-token
|
||||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
(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-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 (ctob/get-token token-set "test-token-1")
|
||||||
token' (get-in token-set' [:tokens "test-token-1"])]
|
token' (ctob/get-token token-set' "test-token-1")]
|
||||||
|
|
||||||
(t/is (= (ctob/set-count tokens-lib') 1))
|
(t/is (= (ctob/set-count tokens-lib') 1))
|
||||||
(t/is (= (count (:tokens token-set')) 2))
|
(t/is (= (count (ctob/get-tokens-map token-set')) 2))
|
||||||
(t/is (= (d/index-of (keys (:tokens token-set')) "test-token-1") 0))
|
(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 (= (:name token') "test-token-1"))
|
||||||
(t/is (= (:description token') "some description"))
|
(t/is (= (:description token') "some description"))
|
||||||
(t/is (= (:value token') false))
|
(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/is (dt/is-after? (:modified-at token') (:modified-at token)))))
|
||||||
|
|
||||||
(t/deftest rename-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-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 (ctob/get-token token-set "test-token-1")
|
||||||
token' (get-in token-set' [:tokens "updated-name"])]
|
token' (ctob/get-token token-set' "updated-name")]
|
||||||
|
|
||||||
(t/is (= (ctob/set-count tokens-lib') 1))
|
(t/is (= (ctob/set-count tokens-lib') 1))
|
||||||
(t/is (= (count (:tokens token-set')) 2))
|
(t/is (= (count (ctob/get-tokens-map token-set')) 2))
|
||||||
(t/is (= (d/index-of (keys (:tokens token-set')) "updated-name") 0))
|
(t/is (= (d/index-of (keys (ctob/get-tokens-map token-set')) "updated-name") 0))
|
||||||
(t/is (= (:name token') "updated-name"))
|
(t/is (= (:name token') "updated-name"))
|
||||||
(t/is (= (:description token') ""))
|
(t/is (= (:description token') ""))
|
||||||
(t/is (= (:value token') true))
|
(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/is (dt/is-after? (:modified-at token') (:modified-at token)))))
|
||||||
|
|
||||||
(t/deftest delete-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-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 (= (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 (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
|
(t/deftest get-ordered-sets
|
||||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
@@ -897,7 +895,7 @@
|
|||||||
:value true)))
|
:value true)))
|
||||||
|
|
||||||
set (ctob/get-set tokens-lib "test-token-set")
|
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 (= (count tokens-list) 5))
|
||||||
(t/is (= (:name (nth tokens-list 0)) "token1"))
|
(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-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")
|
||||||
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 (= (ctob/set-count tokens-lib') 1))
|
||||||
(t/is (= (:name token') "group1.test-token-2"))
|
(t/is (= (:name token') "group1.test-token-2"))
|
||||||
(t/is (= (:description token') "some description"))
|
(t/is (= (:description token') "some description"))
|
||||||
(t/is (= (:value token') false))
|
(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/is (dt/is-after? (:modified-at token') (:modified-at token)))))
|
||||||
|
|
||||||
(t/deftest update-token-in-sets-rename
|
(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-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")
|
||||||
token' (get-in token-set' [:tokens "group1.updated-name"])]
|
token' (ctob/get-token token-set' "group1.updated-name")]
|
||||||
|
|
||||||
(t/is (= (ctob/set-count tokens-lib') 1))
|
(t/is (= (ctob/set-count tokens-lib') 1))
|
||||||
(t/is (= (:name token') "group1.updated-name"))
|
(t/is (= (:name token') "group1.updated-name"))
|
||||||
(t/is (= (:description token') ""))
|
(t/is (= (:description token') ""))
|
||||||
(t/is (= (:value token') true))
|
(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/is (dt/is-after? (:modified-at token') (:modified-at token)))))
|
||||||
|
|
||||||
(t/deftest move-token-of-group
|
(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-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")
|
||||||
token' (get-in token-set' [:tokens "group2.updated-name"])]
|
token' (ctob/get-token token-set' "group2.updated-name")]
|
||||||
|
|
||||||
(t/is (= (ctob/set-count tokens-lib') 1))
|
(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 (= (:name token') "group2.updated-name"))
|
||||||
(t/is (= (:description token') ""))
|
(t/is (= (:description token') ""))
|
||||||
(t/is (= (:value token') true))
|
(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/is (dt/is-after? (:modified-at token') (:modified-at token)))))
|
||||||
|
|
||||||
(t/deftest delete-token-in-group
|
(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-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 (= (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 (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
|
(t/deftest update-token-set-in-groups
|
||||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
@@ -1044,7 +1042,7 @@
|
|||||||
tokens-lib' (-> tokens-lib
|
tokens-lib' (-> tokens-lib
|
||||||
(ctob/update-set "group1/token-set-2"
|
(ctob/update-set "group1/token-set-2"
|
||||||
(fn [token-set]
|
(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)
|
||||||
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 (= (ctob/set-count tokens-lib') 5))
|
||||||
(t/is (= (count group1') 3))
|
(t/is (= (count group1') 3))
|
||||||
(t/is (= (d/index-of (keys group1') "S-token-set-2") 0))
|
(t/is (= (d/index-of (keys group1') "S-token-set-2") 0))
|
||||||
(t/is (= (:name token-set') "group1/token-set-2"))
|
(t/is (= (ctob/get-name token-set') "group1/token-set-2"))
|
||||||
(t/is (= (:description token-set') "some description"))
|
(t/is (= (ctob/get-description token-set') "some description"))
|
||||||
(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 rename-token-set-in-groups
|
(t/deftest rename-token-set-in-groups
|
||||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
@@ -1070,8 +1068,7 @@
|
|||||||
tokens-lib' (-> tokens-lib
|
tokens-lib' (-> tokens-lib
|
||||||
(ctob/update-set "group1/token-set-2"
|
(ctob/update-set "group1/token-set-2"
|
||||||
(fn [token-set]
|
(fn [token-set]
|
||||||
(assoc token-set
|
(ctob/rename token-set "group1/updated-name"))))
|
||||||
:name "group1/updated-name"))))
|
|
||||||
|
|
||||||
sets-tree (ctob/get-set-tree tokens-lib)
|
sets-tree (ctob/get-set-tree tokens-lib)
|
||||||
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 (= (ctob/set-count tokens-lib') 5))
|
||||||
(t/is (= (count group1') 3))
|
(t/is (= (count group1') 3))
|
||||||
(t/is (= (d/index-of (keys group1') "S-updated-name") 0))
|
(t/is (= (d/index-of (keys group1') "S-updated-name") 0))
|
||||||
(t/is (= (:name token-set') "group1/updated-name"))
|
(t/is (= (ctob/get-name token-set') "group1/updated-name"))
|
||||||
(t/is (= (:description token-set') ""))
|
(t/is (= (ctob/get-description token-set') ""))
|
||||||
(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 move-token-set-of-group
|
(t/deftest move-token-set-of-group
|
||||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
@@ -1097,8 +1094,7 @@
|
|||||||
tokens-lib' (-> tokens-lib
|
tokens-lib' (-> tokens-lib
|
||||||
(ctob/update-set "group1/token-set-2"
|
(ctob/update-set "group1/token-set-2"
|
||||||
(fn [token-set]
|
(fn [token-set]
|
||||||
(assoc token-set
|
(ctob/rename token-set "group2/updated-name"))))
|
||||||
:name "group2/updated-name"))))
|
|
||||||
|
|
||||||
sets-tree (ctob/get-set-tree tokens-lib)
|
sets-tree (ctob/get-set-tree tokens-lib)
|
||||||
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 group1') 2))
|
||||||
(t/is (= (count group2') 1))
|
(t/is (= (count group2') 1))
|
||||||
(t/is (nil? (get group1' "S-updated-name")))
|
(t/is (nil? (get group1' "S-updated-name")))
|
||||||
(t/is (= (:name token-set') "group2/updated-name"))
|
(t/is (= (ctob/get-name token-set') "group2/updated-name"))
|
||||||
(t/is (= (:description token-set') ""))
|
(t/is (= (ctob/get-description token-set') ""))
|
||||||
(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 delete-token-set-in-group
|
(t/deftest delete-token-set-in-group
|
||||||
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
(let [tokens-lib (-> (ctob/make-tokens-lib)
|
||||||
@@ -1413,7 +1409,7 @@
|
|||||||
tokens-lib' (ctob/parse-decoded-json encoded "")]
|
tokens-lib' (ctob/parse-decoded-json encoded "")]
|
||||||
(t/testing "library got updated but data is equal"
|
(t/testing "library got updated but data is equal"
|
||||||
(t/is (not= tokens-lib' tokens-lib))
|
(t/is (not= tokens-lib' tokens-lib))
|
||||||
(t/is (= @tokens-lib' @tokens-lib)))))))
|
(t/is (= (datafy tokens-lib') (datafy tokens-lib))))))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(t/deftest export-dtcg-json-with-default-theme
|
(t/deftest export-dtcg-json-with-default-theme
|
||||||
|
|||||||
@@ -15,13 +15,13 @@
|
|||||||
map-with-two-props-dashes [{:name "border" :value "no"} {:name "color" :value "--"}]
|
map-with-two-props-dashes [{:name "border" :value "no"} {:name "color" :value "--"}]
|
||||||
map-with-one-prop [{:name "border" :value "no"}]
|
map-with-one-prop [{:name "border" :value "no"}]
|
||||||
map-with-equal [{:name "border" :value "yes color=yes"}]
|
map-with-equal [{:name "border" :value "yes color=yes"}]
|
||||||
map-with-spaces [{:name "border 1" :value "of course"}
|
map-with-spaces [{:name "border (1)" :value "of course"}
|
||||||
{:name "color 2" :value "dark gray"}
|
{:name "color (2)" :value "dark gray"}
|
||||||
{:name "background 3" :value "anoth€r co-lor"}]
|
{:name "background (3)" :value "anoth€r co-lor"}]
|
||||||
|
|
||||||
string-valid-with-two-props "border=yes, color=gray"
|
string-valid-with-two-props "border=yes, color=gray"
|
||||||
string-valid-with-one-prop "border=no"
|
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-no-value "border=no, color="
|
||||||
string-valid-with-dashes "border=no, color=--"
|
string-valid-with-dashes "border=no, color=--"
|
||||||
string-valid-with-equal "border=yes color=yes"
|
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-2) false))
|
||||||
(t/is (= (ctv/same-variant? components-3) false))
|
(t/is (= (ctv/same-variant? components-3) false))
|
||||||
(t/is (= (ctv/same-variant? components-4) 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)))))
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ RUN set -ex; \
|
|||||||
build-essential autoconf libtool pkg-config
|
build-essential autoconf libtool pkg-config
|
||||||
|
|
||||||
|
|
||||||
COPY files/apt.sources /etc/apt/sources.list.d/ubuntu.sources
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
## IMAGE MAGICK
|
## IMAGE MAGICK
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|||||||
@@ -96,6 +96,10 @@ services:
|
|||||||
- ./files/postgresql.conf:/etc/postgresql.conf:z
|
- ./files/postgresql.conf:/etc/postgresql.conf:z
|
||||||
- ./files/postgresql_init.sql:/docker-entrypoint-initdb.d/init.sql:z
|
- ./files/postgresql_init.sql:/docker-entrypoint-initdb.d/init.sql:z
|
||||||
- postgres_data_pg16:/var/lib/postgresql/data
|
- postgres_data_pg16:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- postgres
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: valkey/valkey:8.1
|
image: valkey/valkey:8.1
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ cp /root/.bashrc /home/penpot/.bashrc
|
|||||||
cp /root/.vimrc /home/penpot/.vimrc
|
cp /root/.vimrc /home/penpot/.vimrc
|
||||||
cp /root/.tmux.conf /home/penpot/.tmux.conf
|
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/
|
rsync -ar --chown=penpot:users /opt/cargo/ /home/penpot/.cargo/
|
||||||
|
|
||||||
export PATH="/home/penpot/.cargo/bin:$PATH"
|
export PATH="/home/penpot/.cargo/bin:$PATH"
|
||||||
|
|||||||
@@ -4,14 +4,22 @@ title: 3.06. Backend Guide
|
|||||||
|
|
||||||
# Backend guide #
|
# Backend guide #
|
||||||
|
|
||||||
This guide intends to explain the essential details of the backend
|
This guide collects some basic information on the backend application.
|
||||||
application.
|
|
||||||
|
|
||||||
|
|
||||||
## REPL ##
|
## REPL ##
|
||||||
|
|
||||||
In the devenv environment you can execute <code class="language-clojure">scripts/repl</code> to open a
|
_Note:_ When in development mode, the backend spins up a traditional nREPL socket on port 6064.
|
||||||
Clojure interactive shell ([REPL](https://codewith.mu/en/tutorials/1.0/repl)).
|
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 <code class="language-clojure">(restart)</code> to load and execute the backend
|
Once there, you can execute <code class="language-clojure">(restart)</code> to load and execute the backend
|
||||||
process, or to reload it after making changes to the source code.
|
process, or to reload it after making changes to the source code.
|
||||||
@@ -39,11 +47,11 @@ For example:
|
|||||||
|
|
||||||
## Fixtures ##
|
## Fixtures ##
|
||||||
|
|
||||||
This is a development feature that allows populate the database with a
|
This is a development feature that allows populating the database with a
|
||||||
good amount of content (usually used for just test the application or
|
good amount of content (typically used to test the application or to run
|
||||||
perform performance tweaks on queries).
|
performance tweaks on queries).
|
||||||
|
|
||||||
In order to load fixtures, enter to the REPL environment with the <code class="language-clojure">scripts/repl</code>
|
In order to load fixtures, enter the REPL environment with the <code class="language-clojure">backend/scripts/repl</code>
|
||||||
script, and then execute <code class="language-clojure">(app.cli.fixtures/run {:preset :small})</code>.
|
script, and then execute <code class="language-clojure">(app.cli.fixtures/run {:preset :small})</code>.
|
||||||
|
|
||||||
You also can execute this as a standalone script with:
|
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
|
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.
|
empty database.
|
||||||
|
|
||||||
This by default will create a bunch of users that can be used to login
|
The above will create several users that can be used to login
|
||||||
in the application. All users uses the following pattern:
|
into the application. All of them follow the pattern:
|
||||||
|
|
||||||
- Username: <code class="language-text">profileN@example.com</code>
|
- Username: <code class="language-text">profileN@example.com</code>
|
||||||
- Password: <code class="language-text">123123</code>
|
- Password: <code class="language-text">123123</code>
|
||||||
|
|||||||
@@ -170,6 +170,23 @@ similar to a webmail client. Simply navigate to:
|
|||||||
|
|
||||||
[http://localhost:1080](http://localhost:1080)
|
[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
|
## Team Feature Flags
|
||||||
|
|
||||||
To test a Feature Flag, you can enable or disable them by team through the `dbg` page:
|
To test a Feature Flag, you can enable or disable them by team through the `dbg` page:
|
||||||
|
|||||||
@@ -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
|
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.
|
**NOTE:** the exact container name depends on your docker version and platform.
|
||||||
For example it could be <code class="language-bash">penpot-penpot-backend-1</code> or <code class="language-bash">penpot_penpot-backend-1</code>.
|
For example it could be <code class="language-bash">penpot-penpot-backend-1</code> or <code class="language-bash">penpot_penpot-backend-1</code>.
|
||||||
You can check the correct name executing <code class="language-bash">docker ps</code>.
|
You can check the correct name executing <code class="language-bash">docker ps</code>.
|
||||||
|
|||||||
@@ -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.
|
and do not want to depend on any external provider, or need to do any special customization.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Or you can try <a href="#unofficial-self-host-options">other options</a>,
|
Or you can try [other options][1], offered by Penpot community.
|
||||||
offered by Penpot community.
|
|
||||||
|
[1]: /technical-guide/getting-started/unofficial-options/
|
||||||
|
|||||||
@@ -281,12 +281,15 @@ press <kbd>Shift/⇧</kbd> + left click over the right arrow of a group or a boa
|
|||||||
|
|
||||||
|
|
||||||
<h2 id="focus-mode">Focus mode</h2>
|
<h2 id="focus-mode">Focus mode</h2>
|
||||||
<p>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.</p>
|
<p>Focus mode zooms into the elements of a page you want to work with in a specific moment, and hides the rest so that they don’t get in the way. When the page has many elements, focus mode can also improve performance.</p>
|
||||||
<p>To activate focus mode:</p>
|
<p>To activate focus mode:</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li>Select one or more elements.</li>
|
<li>Select one or more elements.</li>
|
||||||
<li>Right click to show the menu and select the option "Focus on" or press <kbd>F</kbd>.</li>
|
<li>Right click on the selection to show the menu and select the option “Focus on” or press <kbd>F</kbd>.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
<p>Notice that the layer panel will now only show the focused layers. A focus mode status line will also appear at the top.</p>
|
||||||
|
<p>To exit focus mode and return to the original viewport and selection, right click anywhere and select “Focus off” or just press <kbd>F</kbd> again. You can also click anywhere on the focus mode status line at the top of the layer panel.
|
||||||
|
</p>
|
||||||
<figure>
|
<figure>
|
||||||
<video title="Focus mode" muted="" playsinline="" controls="" width="100%" poster="/img/layers/layers-focus.webp" height="auto">
|
<video title="Focus mode" muted="" playsinline="" controls="" width="100%" poster="/img/layers/layers-focus.webp" height="auto">
|
||||||
<source src="/img/layers/layers-focus.mp4" type="video/mp4">
|
<source src="/img/layers/layers-focus.mp4" type="video/mp4">
|
||||||
|
|||||||
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@@ -11,3 +11,4 @@ node_modules/
|
|||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
/playwright/**/visual-specs/**/*.png
|
/playwright/**/visual-specs/**/*.png
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
:git/url "https://github.com/funcool/beicon.git"}
|
:git/url "https://github.com/funcool/beicon.git"}
|
||||||
|
|
||||||
funcool/rumext
|
funcool/rumext
|
||||||
{:git/tag "v2.22"
|
{:git/tag "v2.24"
|
||||||
:git/sha "92879b6"
|
:git/sha "17a0c94"
|
||||||
:git/url "https://github.com/funcool/rumext.git"}
|
:git/url "https://github.com/funcool/rumext.git"}
|
||||||
|
|
||||||
instaparse/instaparse {:mvn/version "1.5.0"}
|
instaparse/instaparse {:mvn/version "1.5.0"}
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
:dev
|
:dev
|
||||||
{:extra-paths ["dev"]
|
{:extra-paths ["dev"]
|
||||||
:extra-deps
|
: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"}
|
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
||||||
org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
||||||
criterium/criterium {:mvn/version "RELEASE"}
|
criterium/criterium {:mvn/version "RELEASE"}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@
|
|||||||
"@penpot/draft-js": "portal:./vendor/draft-js",
|
"@penpot/draft-js": "portal:./vendor/draft-js",
|
||||||
"@penpot/hljs": "portal:./vendor/hljs",
|
"@penpot/hljs": "portal:./vendor/hljs",
|
||||||
"@penpot/mousetrap": "portal:./vendor/mousetrap",
|
"@penpot/mousetrap": "portal:./vendor/mousetrap",
|
||||||
|
"@penpot/plugins-runtime": "1.3.2",
|
||||||
"@penpot/svgo": "penpot/svgo#v3.1",
|
"@penpot/svgo": "penpot/svgo#v3.1",
|
||||||
"@penpot/text-editor": "portal:./text-editor",
|
"@penpot/text-editor": "portal:./text-editor",
|
||||||
"@tokens-studio/sd-transforms": "1.2.11",
|
"@tokens-studio/sd-transforms": "1.2.11",
|
||||||
|
|||||||
@@ -53,6 +53,21 @@ export default defineConfig({
|
|||||||
toHaveScreenshot: { maxDiffPixelRatio: 0.005 },
|
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 */
|
/* Run your local dev server before starting the tests */
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"~:type": "~:restriction",
|
||||||
|
"~:code": "~:email-does-not-match-invitation",
|
||||||
|
"~:hint": "email should match the invitation"
|
||||||
|
}
|
||||||
BIN
frontend/playwright/data/render-wasm/assets/ebgaramond.ttf
Normal file
BIN
frontend/playwright/data/render-wasm/assets/ebgaramond.ttf
Normal file
Binary file not shown.
BIN
frontend/playwright/data/render-wasm/assets/firacode.ttf
Normal file
BIN
frontend/playwright/data/render-wasm/assets/firacode.ttf
Normal file
Binary file not shown.
BIN
frontend/playwright/data/render-wasm/assets/landscape.jpg
Normal file
BIN
frontend/playwright/data/render-wasm/assets/landscape.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 344 KiB |
BIN
frontend/playwright/data/render-wasm/assets/mreaves.ttf
Normal file
BIN
frontend/playwright/data/render-wasm/assets/mreaves.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
frontend/playwright/data/render-wasm/assets/notosansjpsubset.ttf
Normal file
BIN
frontend/playwright/data/render-wasm/assets/notosansjpsubset.ttf
Normal file
Binary file not shown.
BIN
frontend/playwright/data/render-wasm/assets/pattern.png
Normal file
BIN
frontend/playwright/data/render-wasm/assets/pattern.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
frontend/playwright/data/render-wasm/assets/penguins.jpg
Normal file
BIN
frontend/playwright/data/render-wasm/assets/penguins.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 964 KiB |
4595
frontend/playwright/data/render-wasm/get-file-blend-modes.json
Normal file
4595
frontend/playwright/data/render-wasm/get-file-blend-modes.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1047
frontend/playwright/data/render-wasm/get-file-shapes-fills.json
Normal file
1047
frontend/playwright/data/render-wasm/get-file-shapes-fills.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1210
frontend/playwright/data/render-wasm/get-file-shapes-strokes.json
Normal file
1210
frontend/playwright/data/render-wasm/get-file-shapes-strokes.json
Normal file
File diff suppressed because it is too large
Load Diff
2365
frontend/playwright/data/render-wasm/get-file-text-align.json
Normal file
2365
frontend/playwright/data/render-wasm/get-file-text-align.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2950
frontend/playwright/data/render-wasm/get-file-text-decoration.json
Normal file
2950
frontend/playwright/data/render-wasm/get-file-text-decoration.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2761
frontend/playwright/data/render-wasm/get-file-text-images.json
Normal file
2761
frontend/playwright/data/render-wasm/get-file-text-images.json
Normal file
File diff suppressed because it is too large
Load Diff
3098
frontend/playwright/data/render-wasm/get-file-text-styles.json
Normal file
3098
frontend/playwright/data/render-wasm/get-file-text-styles.json
Normal file
File diff suppressed because it is too large
Load Diff
349
frontend/playwright/data/render-wasm/get-file-text.json
Normal file
349
frontend/playwright/data/render-wasm/get-file-text.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
1858
frontend/playwright/data/render-wasm/get-multiple-texts-base.json
Normal file
1858
frontend/playwright/data/render-wasm/get-multiple-texts-base.json
Normal file
File diff suppressed because it is too large
Load Diff
14
frontend/playwright/data/subscription/get-owned-teams.json
Normal file
14
frontend/playwright/data/subscription/get-owned-teams.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"~:id": "~uc7ce0794-0992-8105-8004-38e630f7920a",
|
||||||
|
"~:is-default": false
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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
|
||||||
|
}]
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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
|
||||||
|
}]
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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;
|
#page = null;
|
||||||
|
|
||||||
constructor(page) {
|
constructor(page) {
|
||||||
@@ -38,15 +97,21 @@ export class BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async mockConfigFlags(flags) {
|
async mockConfigFlags(flags) {
|
||||||
const url = "**/js/config.js?ts=*";
|
return BasePage.mockConfigFlags(this.page, flags);
|
||||||
return await this.page.route(url, (route) =>
|
}
|
||||||
route.fulfill({
|
|
||||||
status: 200,
|
async mockFileMediaAsset(assetId, assetFilename, options) {
|
||||||
contentType: "application/javascript",
|
return BasePage.mockFileMediaAsset(
|
||||||
body: `var penpotFlags = "${flags.join(" ")}";`,
|
this.page,
|
||||||
}),
|
assetId,
|
||||||
|
assetFilename,
|
||||||
|
options,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async mockAsset(assetId, assetFilename, options) {
|
||||||
|
return BasePage.mockAsset(this.page, assetId, assetFilename, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BasePage;
|
export default BasePage;
|
||||||
|
|||||||
35
frontend/playwright/ui/pages/RegisterPage.js
Normal file
35
frontend/playwright/ui/pages/RegisterPage.js
Normal file
@@ -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;
|
||||||
30
frontend/playwright/ui/pages/SubscriptionProfilePage.js
Normal file
30
frontend/playwright/ui/pages/SubscriptionProfilePage.js
Normal file
@@ -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;
|
||||||
62
frontend/playwright/ui/pages/WasmWorkspacePage.js
Normal file
62
frontend/playwright/ui/pages/WasmWorkspacePage.js
Normal file
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,7 +99,7 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||||||
this.tokenThemeUpdateCreateModal = page.getByTestId(
|
this.tokenThemeUpdateCreateModal = page.getByTestId(
|
||||||
"token-theme-update-create-modal",
|
"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.tokensSidebar = page.getByTestId("tokens-sidebar");
|
||||||
this.tokenSetItems = page.getByTestId("tokens-set-item");
|
this.tokenSetItems = page.getByTestId("tokens-set-item");
|
||||||
this.tokenSetGroupItems = page.getByTestId("tokens-set-group-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() {
|
async setupFileWithComments() {
|
||||||
await this.mockRPC(
|
await this.mockRPC(
|
||||||
"get-comment-threads?file-id=*",
|
"get-comment-threads?file-id=*",
|
||||||
@@ -309,3 +317,5 @@ export class WorkspacePage extends BaseWebSocketPage {
|
|||||||
.click(clickOptions);
|
.click(clickOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default WorkspacePage;
|
||||||
140
frontend/playwright/ui/render-wasm-specs/shapes.spec.js
Normal file
140
frontend/playwright/ui/render-wasm-specs/shapes.spec.js
Normal file
@@ -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();
|
||||||
|
});
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user