Compare commits
115 Commits
refactor-b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e7851f6ad | ||
|
|
8f43c7518e | ||
|
|
96a5874d6c | ||
|
|
b697b26302 | ||
|
|
0a7f8ef8d1 | ||
|
|
e10570be4d | ||
|
|
f88036c74c | ||
|
|
708d8f79f4 | ||
|
|
ceae223c3a | ||
|
|
f167c42b9a | ||
|
|
bd68ba8a6b | ||
|
|
4052de4ae2 | ||
|
|
bcb7b1c910 | ||
|
|
f028ddf17b | ||
|
|
25b7a0da31 | ||
|
|
52d96af200 | ||
|
|
81e5593a3e | ||
|
|
a5334e2ee6 | ||
|
|
09ca7f1c0c | ||
|
|
efcdcba9cd | ||
|
|
37369af369 | ||
|
|
aa95ec219d | ||
|
|
5f3525d56b | ||
|
|
3caa84b7fb | ||
|
|
57a6eb2759 | ||
|
|
ae5a9293d6 | ||
|
|
fca99f09ea | ||
|
|
a7949f6c8c | ||
|
|
cb71ac3ae4 | ||
|
|
1a948a21d2 | ||
|
|
6947ac0e5e | ||
|
|
eff608b9f5 | ||
|
|
9efcd8a9c6 | ||
|
|
6af1d1f6e5 | ||
|
|
07f1320f6d | ||
|
|
5211faf140 | ||
|
|
1f709a10be | ||
|
|
382675c1ba | ||
|
|
495083b416 | ||
|
|
9dbfdacd9f | ||
|
|
95321ced95 | ||
|
|
d03c3de36b | ||
|
|
fd78229570 | ||
|
|
e06a105a68 | ||
|
|
a82960ca15 | ||
|
|
20d21c210e | ||
|
|
f1eddf1821 | ||
|
|
fff10097fd | ||
|
|
d10928bb5a | ||
|
|
0243b3b9e8 | ||
|
|
66d7dfc45f | ||
|
|
a38cd90c16 | ||
|
|
bdd3bab3a5 | ||
|
|
53aaa8a670 | ||
|
|
64631caaaa | ||
|
|
8e1f042350 | ||
|
|
128a7c49b9 | ||
|
|
e0fa421459 | ||
|
|
0438480620 | ||
|
|
d64f87847c | ||
|
|
c6fd4a8275 | ||
|
|
bfeb1c55b9 | ||
|
|
9949fb8335 | ||
|
|
86f84ef998 | ||
|
|
fc68d407b7 | ||
|
|
16e0b5862e | ||
|
|
d36fca5cc6 | ||
|
|
7f379c0ae6 | ||
|
|
45847df63d | ||
|
|
c177a16b33 | ||
|
|
92a967721a | ||
|
|
1b8ab3938d | ||
|
|
dcc90dd954 | ||
|
|
565b69db9f | ||
|
|
5004dd140e | ||
|
|
ede5f276cf | ||
|
|
c7d30dc392 | ||
|
|
8b386e3263 | ||
|
|
1912e53672 | ||
|
|
b9139469d4 | ||
|
|
c2b0532e96 | ||
|
|
22718369e1 | ||
|
|
4403c27c76 | ||
|
|
14866c8aaf | ||
|
|
4a85f86c95 | ||
|
|
a4365861c9 | ||
|
|
81ddfc58c5 | ||
|
|
5f2d80755e | ||
|
|
ee57b43d0e | ||
|
|
dc892e6f61 | ||
|
|
9fad1bc5a1 | ||
|
|
ddb1bcae97 | ||
|
|
e8ab297af0 | ||
|
|
94850c99b1 | ||
|
|
6d6ce019f1 | ||
|
|
b3ab3b1193 | ||
|
|
81c3056a10 | ||
|
|
80c384a57a | ||
|
|
769615c2cc | ||
|
|
cce577add3 | ||
|
|
16bfd244e6 | ||
|
|
d1ef2fa3f8 | ||
|
|
b1abb4651d | ||
|
|
da25b7692e | ||
|
|
0a2a8601c1 | ||
|
|
0f7d9944a8 | ||
|
|
aa54f9b0bb | ||
|
|
6aff8d37e4 | ||
|
|
217dedc121 | ||
|
|
71c2bfd949 | ||
|
|
76dd829a78 | ||
|
|
9439eface6 | ||
|
|
2898bdd817 | ||
|
|
f3cd37b1f9 | ||
|
|
ed333d6ee6 |
27
.github/ISSUE_TEMPLATE/plugin_request.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Plugin Request
|
||||||
|
description: Request support for a third-party library
|
||||||
|
title: "[Plugin Request]: "
|
||||||
|
labels: ["plugin"]
|
||||||
|
assignees:
|
||||||
|
- PaulleDemon
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to request a new plugin.
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: plugin
|
||||||
|
attributes:
|
||||||
|
label: Name of the plugin
|
||||||
|
placeholder: Enter the plugin name
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: plugin-link
|
||||||
|
attributes:
|
||||||
|
label: Link to the plugin
|
||||||
|
placeholder: Enter the plugin link
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
1
.gitignore
vendored
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
.env-cmdrc.json
|
.env-cmdrc.json
|
||||||
build
|
build
|
||||||
|
dist
|
||||||
|
|
||||||
python-tests/
|
python-tests/
|
||||||
|
|
||||||
|
|||||||
14
CONTRIBUTING.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
## Contributing
|
||||||
|
|
||||||
|
Currently if you find bugs or typos, Please create an issue first, instead of a Pull request.
|
||||||
|
That is the best way to contribute to this repo in it's current state.
|
||||||
|
|
||||||
|
**The repo will be going through drastic changes including migrations to TS.**
|
||||||
|
|
||||||
|
So any changes you bring may have to be rewritten. Plus, I may not get time to review every pull request, unless you are bringing in significant improvement that would have probably taken me weeks to do it myself, I may not be able to take time out of development to review it, and don't take closing pull request personally. It's just that I currently don't have time.
|
||||||
|
|
||||||
|
Thank you for understanding.
|
||||||
|
|
||||||
|
## Other Best ways to contribute.
|
||||||
|
1. Creating tutorials and helping others on [discord server](https://discord.gg/dHXjrrCA7G)
|
||||||
|
2. Sharing the tool with others.
|
||||||
13
License.txt
@@ -5,11 +5,10 @@ Below is the License for source code of PyUIBuilder. For license regarding sourc
|
|||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
The source code for PyUIBuilder is Dual licensed, most parts of the code comes under AGPL,
|
The source code for PyUIBuilder is Dual licensed, some parts of the code comes under AGPL,
|
||||||
some parts under a different non-commercial use license. Read the second part after the definition of the AGPL license.
|
some parts under a different non-commercial use license. Read the second part after the definition of the AGPL license.
|
||||||
|
|
||||||
Sub-folders with non-commercial use license will contain the second license as well.
|
Sub-folders with non-commercial use license will contain the second license as well.
|
||||||
- Only the code inside src/canvas/ is kept under a non-commercial use license.
|
|
||||||
|
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
@@ -678,17 +677,11 @@ For more information on this, and how to apply and follow the GNU AGPL, see
|
|||||||
|
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
Below is the license for code under src/canvas/
|
|
||||||
|
|
||||||
License for non-commercial use in short
|
License for non-commercial use in short
|
||||||
* You are free fork, modify and redistribute it given the original or derived works is kept free and open-source and under the same licese.
|
* You are free fork, modify and redistribute it given the original or derived works is kept free and source-available and under the same licese.
|
||||||
* Another restriction is you can't redistribute in binary or executable formats without asking me first.
|
* Another restriction is you can't redistribute in binary or executable formats without asking me first.
|
||||||
* If you plan on making money on this work or derived work you need permission first.
|
* If you plan on making money on this work or derived work you need permission first.
|
||||||
|
|
||||||
License was planned to be kept under one AGPL or less restrictive license, but since its difficult for me to continue providing free and open-source software without proper funding. It's under this license.
|
|
||||||
|
|
||||||
Once there enough money to fund this project and my upcoming FOSS projects, The license will be brought back to a single AGPL or much lesser restrictive license. The fastest way to make this happen is by purchasing a one-time license or fund me.
|
|
||||||
|
|
||||||
Read the full license below.
|
Read the full license below.
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
@@ -696,7 +689,7 @@ Read the full license below.
|
|||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, publish, and distribute the source code, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, publish, and distribute the source code, subject to the following conditions:
|
||||||
|
|
||||||
2. Derived Work License
|
2. Derived Work License
|
||||||
Any modifications, derivative works, or copies of the Software must retain this license. All derivative works must be open-sourced under the same license terms. You are not permitted to relicense or sub-license any portion of the Software under a different license without explicit prior written permission from the original author.
|
Any modifications, derivative works, or copies of the Software must retain this license. All derivative works must be source-available and under the same license terms. You are not permitted to relicense or sub-license any portion of the Software under a different license without explicit prior written permission from the original author.
|
||||||
|
|
||||||
3. Distribution Restriction on Executables
|
3. Distribution Restriction on Executables
|
||||||
You are not permitted to distribute the Software in compiled, executable, or binary form without prior written consent from the original author. This applies to both original and derivative works.
|
You are not permitted to distribute the Software in compiled, executable, or binary form without prior written consent from the original author. This applies to both original and derivative works.
|
||||||
|
|||||||
101
README.md
@@ -1,34 +1,67 @@
|
|||||||
# PyUIBuilder - Build python GUIs like Webflow
|
|
||||||
<sub>Not affiliated to Webflow</sub>
|
<p align="center"><img src="./repo-assets/logo/PyUi.png" alt="font tester logo" width="100" height="100"></p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
|
<h1> PyUIBuilder - Python GUI Builder
|
||||||
|
</h1>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Build python GUIs like Canva
|
||||||
|
|
||||||
|
<!-- <p align="center">
|
||||||
<a href="https://twitter.com/share?url=https://github.com/PaulleDemon/PyUIBuilder&text=Check out PyUIBuilder tool">
|
<a href="https://twitter.com/share?url=https://github.com/PaulleDemon/PyUIBuilder&text=Check out PyUIBuilder tool">
|
||||||
<img src="./repo-assets/share/1.png" height="35" />
|
<img src="./repo-assets/share/1.png" height="35" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://www.reddit.com/submit?url=https://github.com/PaulleDemon/PyUIBuilder&title=Check out PyUIBuilder tool">
|
<a href="https://www.reddit.com/submit?url=https://github.com/PaulleDemon/PyUIBuilder&title=Check out PyUIBuilder tool">
|
||||||
<img src="./repo-assets/share/4.png" height="35" />
|
<img src="./repo-assets/share/4.png" height="35" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://www.linkedin.com/shareArticle?mini=true&url=https://github.com/PaulleDemon/PyUIBuilder&title=check out PyUIBuilder tool">
|
<a href="https://www.linkedin.com/shareArticle?mini=true&url=https://github.com/**PaulleDemon**/PyUIBuilder&title=check out PyUIBuilder tool">
|
||||||
<img src="./repo-assets/share/2.png" height="35" />
|
<img src="./repo-assets/share/2.png" height="35" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://youtube.com/">
|
<a href="https://youtube.com/">
|
||||||
|
Build Python GUI's with ease of Drag and drop builders.
|
||||||
<img src="./repo-assets/share/3.png" height="35" />
|
<img src="./repo-assets/share/3.png" height="35" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p> -->
|
||||||
|
|
||||||
<p><img src="./repo-assets/logo/PyUi.png" alt="font tester logo" width="100" height="100"></p>
|
|
||||||
|
|
||||||
|
|
||||||
Build Python GUI's with ease of Drag and drop builders.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/ac91aa98-843d-4578-b646-88e66bc113de
|
<a href="https://www.producthunt.com/posts/pyui-builder?embed=true&utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-pyui-builder" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=949514&theme=light&period=daily&t=1744818177482" alt="PyUI Builder - Build Python GUIs like Canva | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||||
|
|
||||||
<sub>**Don't like background music? fell free to mute it**</sub>
|
|
||||||
|
|
||||||
|
https://github.com/user-attachments/assets/57addb9c-d018-4be9-894d-c7cbce5ff55f
|
||||||
|
|
||||||
|
|
||||||
|
<!-- https://github.com/user-attachments/assets/ac91aa98-843d-4578-b646-88e66bc113de -->
|
||||||
|
|
||||||
|
<sub>**Don't like background music? feel free to mute it**</sub>
|
||||||
|
|
||||||
## Try PyUIBuilder
|
## Try PyUIBuilder
|
||||||
Try [PyUIBuilder](https://pyuibuilder.pages.dev/)
|
**Try it** [PyUIBuilder](https://pyuibuilder.com)
|
||||||
|
|
||||||
|
**About** [About PyUiBuilder](https://about.pyuibuilder.com)
|
||||||
|
|
||||||
|
[PyuiBuilder - Python GUI Builder](https://pyuibuilder.com/python-gui-builder)
|
||||||
|
|
||||||
|
**Roadmap:** You can check whats upcoming on [Roadmap](https://scrawny-droplet-e97.notion.site/PyUIBuilder-Roadmap-21e45dc1685b8092a47ed4c9f333233c)
|
||||||
|
|
||||||
|
**What's new:** You can check what's new [here](https://scrawny-droplet-e97.notion.site/Pyuibuilder-What-s-new-25845dc1685b80a2a802e6f3943085f1)
|
||||||
|
|
||||||
|
**Funding development:**
|
||||||
|
<br>
|
||||||
|
I am Paul, a solo-developer, which means I work on this project all by myself from design to coding to maintaing the project.
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
so, if you'd like to support the development, you can [purchase a license](https://about.pyuibuilder.com/#pricing) with a one time payment, for a limited time, which will give you instant access to all the premium features
|
||||||
|
for life. Thank you for your support! appreciate it very much
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
Best,
|
||||||
|
<br/>
|
||||||
|
Paul
|
||||||
|
|
||||||
|
**Note:** premium features are being added and maintained in private
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
@@ -45,13 +78,18 @@ Try [PyUIBuilder](https://pyuibuilder.pages.dev/)
|
|||||||
- [Web based Editor](#webbased-editor)
|
- [Web based Editor](#webbased-editor)
|
||||||
- [Electron App - Hobbyist License](#electron-app---hobbyist-license)
|
- [Electron App - Hobbyist License](#electron-app---hobbyist-license)
|
||||||
- [Electron App - Commercial License](#electron-app---commercial-license)
|
- [Electron App - Commercial License](#electron-app---commercial-license)
|
||||||
|
- [Contributing](#contributing)
|
||||||
- [Some of my other open-source](#some-of-my-other-open-source)
|
- [Some of my other open-source](#some-of-my-other-open-source)
|
||||||
- [Author](#author)
|
- [Author](#author)
|
||||||
|
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
1. Youtube - [PyUibuilder playlist](https://youtube.com/playlist?list=PL0VamwghCfX-KXtGKGLak-C_-Jcx_eOiK&si=vnVr8vdU_JkIEL2f)
|
||||||
|
2. [Creating sign up form Blog](https://medium.com/python-in-plain-english/create-tkinter-guis-using-tkinter-gui-builder-pyuibuilder-a7422489c55e)
|
||||||
|
3. [Discord Invite](https://discord.gg/dHXjrrCA7G)
|
||||||
|
|
||||||
## Docs - Getting started
|
## Docs - Getting started
|
||||||
Read the docs on the [Docs page](https://pyuibuilder-docs.pages.dev/)
|
Read the docs on the [Docs page](https://docs.pyuibuilder.com/)
|
||||||
|
|
||||||
## Example app
|
## Example app
|
||||||
|
|
||||||
@@ -107,7 +145,7 @@ While there are a lot of features, here are few you need to know.
|
|||||||
* Framework agnostic - Can outputs code in multiple frameworks.
|
* Framework agnostic - Can outputs code in multiple frameworks.
|
||||||
* Pre-built UI widgets
|
* Pre-built UI widgets
|
||||||
* Plugins to extend 3rd party UI libraries
|
* Plugins to extend 3rd party UI libraries
|
||||||
* Supports layout managers, such as flex, grid and absolute positioning [read docs](https://pyuibuilder-docs.pages.dev/)
|
* Supports layout managers, such as flex, grid and absolute positioning [read docs](https://docs.pyuibuilder.com/)
|
||||||
* Generates python Code.
|
* Generates python Code.
|
||||||
* Support to upload local assets.
|
* Support to upload local assets.
|
||||||
* Generates requirements.txt file when needed
|
* Generates requirements.txt file when needed
|
||||||
@@ -121,21 +159,19 @@ While there are a lot of features, here are few you need to know.
|
|||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
Here are some of the upcoming features.
|
Here are some of the upcoming features.
|
||||||
* Treeview on the sidebar
|
|
||||||
* Support for Event Handlers
|
* Support for Event Handlers
|
||||||
* Kivy Framework support
|
|
||||||
* Pyqt/PySide Support
|
* Pyqt/PySide Support
|
||||||
* **Downloadable Electron app** and more.
|
* **Downloadable Electron app** and more.
|
||||||
|
|
||||||
To learn more/ see upcoming features visit [roadmap](./roadmap.md)
|
To learn more/ see upcoming features visit [roadmap](./roadmap.md)
|
||||||
|
|
||||||
To stay in loop, subscribe to the free [newsletter](https://paulfreeman.substack.com/subscribe?utm_source=Github-Pybuilder)
|
To stay in loop, subscribe to the free [newsletter](https://tally.so/r/mVDY7N)
|
||||||
|
|
||||||
## License - Fund the development
|
## License - Fund the development
|
||||||
|
|
||||||
Help fund open-source work and development of this and upcoming projects by purchasing a one-time license.
|
Help fund development of this and upcoming projects by purchasing a one-time license.
|
||||||
|
|
||||||
Purchasing License will allow me to focus on development of this tool and provide you access to more advance features, early access and more.
|
Purchasing License will give you discounted price and provide you access to more advance features, early access and more.
|
||||||
|
|
||||||
The discount's will be available for limited time only on pre-orders.
|
The discount's will be available for limited time only on pre-orders.
|
||||||
|
|
||||||
@@ -146,15 +182,16 @@ The discount's will be available for limited time only on pre-orders.
|
|||||||
| **Lifetime license** (one-time purchase) | 👍️ | ✅ | ✅ |
|
| **Lifetime license** (one-time purchase) | 👍️ | ✅ | ✅ |
|
||||||
| **Early access** to upcoming features | ❌ | ✅ | ✅ |
|
| **Early access** to upcoming features | ❌ | ✅ | ✅ |
|
||||||
| **Downloadable Electron App** (upcoming) | ❌ | ✅ | ✅ |
|
| **Downloadable Electron App** (upcoming) | ❌ | ✅ | ✅ |
|
||||||
|
| **Premium widgets**(tabbed widget, scroll widget etc) | ❌ | ✅ | ✅ |
|
||||||
| **Run Preview live**(upcoming) | ❌ | ✅ | ✅ |
|
| **Run Preview live**(upcoming) | ❌ | ✅ | ✅ |
|
||||||
| **Save and Load UI files** (upcoming) | ❌ | ✅ | ✅ |
|
| **Save and Load UI files** | ❌ | ✅ | ✅ |
|
||||||
| **Load 3rd party plugins locally** | ❌ | ✅ | ✅ |
|
| **Load 3rd party plugins locally** | ❌ | ✅ | ✅ |
|
||||||
| **Dark theme** (upcoming) | ❌ | ✅ | ✅ |
|
| **Dark theme** | ❌ | ✅ | ✅ |
|
||||||
| **Commercial Use** | ✅ | ❌ | ✅ |
|
| **Commercial Use** | ❌ | ❌ | ✅ |
|
||||||
| **Support for PyQt/PySide frameworks** (upcoming) | ❌ | ❌ | ✅ |
|
| **Support for PyQt/PySide frameworks** (upcoming) | ❌ | ❌ | ✅ |
|
||||||
| **More upcoming features and support** | ❓️ | ✅ | ✅ |
|
| **More upcoming features and support** | ❓️ | ✅ | ✅ |
|
||||||
| **Price** | - | ~~$129~~ **$29** (save 77.52% for limited time on pre-order) | ~~180~~ **$49** (Save 72.78% for a limited time on pre-orders) |
|
| **Price** | - | ~~$129~~ **$29** (save 77.52% for limited time on pre-order) | ~~180~~ **$49** (Save 72.78% for a limited time on pre-orders) |
|
||||||
| Pre-order now! | | [Get license](https://ko-fi.com/s/4a3dffb3b9) | [Get license](https://ko-fi.com/s/560a3b6b05) |
|
| order now! | | [Get license](https://about.pyuibuilder.com) | [Get license](https://about.pyuibuilder.com) |
|
||||||
|
|
||||||
## Newsletter
|
## Newsletter
|
||||||
|
|
||||||
@@ -218,6 +255,24 @@ This is meant for students and hobbyist
|
|||||||
This is meant for business usecases, you can use the code even for commercial use.
|
This is meant for business usecases, you can use the code even for commercial use.
|
||||||
* All code generated by the builder tools are free to use for commercial and non-commercial purposes. If you are using this for a startup or your business you'll need to get a commercial license.
|
* All code generated by the builder tools are free to use for commercial and non-commercial purposes. If you are using this for a startup or your business you'll need to get a commercial license.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
To run it locally you'll need to add .env-cmdrc.json with the following in it. This file should be in the root directory
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"development":{
|
||||||
|
"REACT_APP_ANALYTICS_SCRIPT_ID": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
"production":{
|
||||||
|
"REACT_APP_ANALYTICS_SCRIPT_ID": ""
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For contributing read [here](./CONTRIBUTING.md)
|
||||||
|
|
||||||
|
|
||||||
## Some of my other open-source
|
## Some of my other open-source
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,7 @@
|
|||||||
|
|
||||||
# PyUIBuilder Docs
|
# PyUIBuilder Docs
|
||||||
|
|
||||||
> A Free Python GUI builder tool, like Webflow, but for python GUIs
|
> A Powerful Python GUI builder tool, Build your Python GUis like Canva
|
||||||
|
|
||||||
- Simple and powerful
|
- Simple and powerful
|
||||||
- Do more with plugins
|
- Do more with plugins
|
||||||
- Works for multiple frameworks
|
- Works for multiple frameworks
|
||||||
|
|
||||||
[GitHub](https://github.com/PaulleDemon/PyUIBuilder)
|
|
||||||
[Get Started](#pyuibuilder-documentation)
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 151 KiB |
BIN
docs/assets/basics.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
docs/assets/grids.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
docs/assets/logout desktop.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
@@ -2,10 +2,10 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Document</title>
|
<title>PyUIBuilder - Documentation</title>
|
||||||
<link rel="icon" href="_media/favicon.ico">
|
<link rel="icon" href="_media/favicon.ico">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||||
<meta name="description" content="Description">
|
<meta name="description" content="Documentation for PyUiBuilder">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||||
<!-- <link rel="stylesheet" href="//cdn.jsdelivr.net/gh/LIGMATV/docsify-theme-github/github.css"> -->
|
<!-- <link rel="stylesheet" href="//cdn.jsdelivr.net/gh/LIGMATV/docsify-theme-github/github.css"> -->
|
||||||
<!-- <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css"> -->
|
<!-- <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css"> -->
|
||||||
|
|||||||
103
docs/intro.md
@@ -5,14 +5,74 @@
|
|||||||
This page is still under work in progress. This page will be updated from time to time. This contains the basic documentation for PYUIBuilder
|
This page is still under work in progress. This page will be updated from time to time. This contains the basic documentation for PYUIBuilder
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Getting started video](https://youtu.be/9dYv3VnchDA?si=kJoSXh0i-r1qm3AU)
|
||||||
|
|
||||||
|
- [Creating Calculator apps short/reel](https://youtube.com/shorts/1K2cM1gt13o?si=oxwNcoIpRU-8IkzS)
|
||||||
|
|
||||||
|
- [Youtube Tutorials Playlist](https://youtube.com/playlist?list=PL0VamwghCfX-KXtGKGLak-C_-Jcx_eOiK&si=1LagzyjBaifqDcND)
|
||||||
|
|
||||||
|
- [Create a signup form](https://medium.com/python-in-plain-english/create-tkinter-guis-using-tkinter-gui-builder-pyuibuilder-a7422489c55e)
|
||||||
|
|
||||||
|
- [Discord invite](https://discord.gg/dHXjrrCA7G)
|
||||||
|
|
||||||
|
- [basic pack Layout tutorial - Tkinter](https://www.youtube.com/watch?v=rbW1iJO1psk)
|
||||||
|
|
||||||
|
|
||||||
|
## FAQs
|
||||||
|
|
||||||
|
### Common faqs
|
||||||
|
**1. Why does the design I created in tkinter look a bit off from my actual output**
|
||||||
|
|
||||||
|
Tkinter is OS dependent framework, and Pyuibuilder provides you with a common interface for you to develop. In the future we may consider rendering the design based on the OS you are using.
|
||||||
|
|
||||||
|
For more accurate design to output ratio, you can try using customtk framework that's available in Pyuibuilder.
|
||||||
|
|
||||||
|
**2. Why do I see a small padding in design, but not in output tkinter**
|
||||||
|
|
||||||
|
The default padding in the design exists, so you can easily drag and drop your widgets. You can adjust the output padding from the toolbar. We may in the near future, make the outputs to have a default padding.
|
||||||
|
|
||||||
|
### Login and License FAQ
|
||||||
|
|
||||||
|
**I had purchased a paid plan, where's my license key?**
|
||||||
|
|
||||||
|
If you had purchased a paid plan, your account has already been upgraded to pro/hobby plan depending on the plan you had selected. You don't require license key, The license key is only required if you want to purchase license in bulk.
|
||||||
|
|
||||||
|
The oauth system is used instead of license key to make it convient to login to both the web version as well as desktop versions.
|
||||||
|
|
||||||
|
**I am on paid plan but the desktop app splash screen is asking for license key**
|
||||||
|
|
||||||
|
It's likely you had logged into a wrong account while logging into the desktop.
|
||||||
|
|
||||||
|
Logout by clicking the logout button on the bottom left below the active key button and relogin
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Deskop app FAQs
|
||||||
|
|
||||||
|
**Should I have the Python pre-installed**
|
||||||
|
|
||||||
|
If you are on desktop and if the app detects your system doesn't have a python installed already, it will prompt you to install python so you can run the exported code.
|
||||||
|
|
||||||
|
The desktop apps comes pre-packaged with python in them, except for the Mac intel versions. The desktop app uses the python built-into the app, except for Mac intel versions.
|
||||||
|
|
||||||
|
**Does workspace automatically store my design?**
|
||||||
|
|
||||||
|
No, the workspace you create is used create a virtual environment for the app to run python.
|
||||||
|
|
||||||
|
## Profile
|
||||||
|
To check which plan you are on or to logout hover over the profile icon on the top right corner with a profile icon.
|
||||||
|
|
||||||
## UI Basics
|
## UI Basics
|
||||||
Let's start with the basics of UI
|
Let's start with the basics of UI
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
1. The sidebar on the left will have multiple buttons, each button will provide you with necessary tools.
|
1. The sidebar on the left will have multiple tabs, each tabs will provide you with necessary tools.
|
||||||
2. The Place where you drag and drop widgets is the canvas
|
2. The Place where you drag and drop widgets is the canvas
|
||||||
3. The toolbar will only appear if a widget is selected.
|
3. The toolbar will only appear if a widget is selected.
|
||||||
|
4. The code editor is to help you write event handler codes.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -23,14 +83,26 @@ Things you can do on canvas.
|
|||||||
1. Add widgets from sidebar.
|
1. Add widgets from sidebar.
|
||||||
2. Zoom and pan using mouse.
|
2. Zoom and pan using mouse.
|
||||||
3. zoom using `+`/`-` keys
|
3. zoom using `+`/`-` keys
|
||||||
4. Delete widgets using `del` key or right clicking on the widget
|
4. Delete widgets using `del` key or right clicking on the widget (on mac function + delete)
|
||||||
|
5. You can use ctrl/cmd + D to duplicate the selected widget.
|
||||||
|
|
||||||
## Project name
|
## Project name
|
||||||
By default all project's are named untitled project, you can change this from the header input next to export code.
|
By default all project's are named `"untitled project"`, you can change this from the header input next to export code.
|
||||||
|
|
||||||
## Selecting a UI library
|
## Selecting a UI library
|
||||||
You can select the UI library from the header dropdown. Once selected changing the UI library in between your work, will erase the canvas.
|
You can select the UI library from the header dropdown. Once selected changing the UI library in between your work, will erase the canvas.
|
||||||
|
|
||||||
|
## Enabling grids and snapping
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
You can open the grid controls from the top left near the delete icons
|
||||||
|
|
||||||
|
To enable grid snapping just click on the enable snap switch, you can adjust the grid size using the slider
|
||||||
|
|
||||||
|
You can also enable grids for parent widgets by switching the widget grid on.
|
||||||
|
|
||||||
|
|
||||||
## Widgets
|
## Widgets
|
||||||
|
|
||||||
Every widget has its own attributes, some of the attributes may be common.
|
Every widget has its own attributes, some of the attributes may be common.
|
||||||
@@ -43,11 +115,10 @@ Every widget has its own attributes, some of the attributes may be common.
|
|||||||
|
|
||||||
1. Flex(also known as pack)
|
1. Flex(also known as pack)
|
||||||
2. Grid
|
2. Grid
|
||||||
3. Absolute/Place
|
3. Absolute/Place (flex)
|
||||||
|
|
||||||
The parents of the child widgets controls the layout. The layout properties such as grid position will be available to the child under the grid-manager/flex-manager section.
|
The parents of the child widgets controls the layout. The layout properties such as grid position will be available to the child under the grid-manager/flex-manager section.
|
||||||
|
|
||||||
|
|
||||||
All Widgets attributes are available on the toolbar.
|
All Widgets attributes are available on the toolbar.
|
||||||
The toolbar contains collapsible, which can be opened to modify the widgets attributes such
|
The toolbar contains collapsible, which can be opened to modify the widgets attributes such
|
||||||
as foreground / background colors, themes and more.
|
as foreground / background colors, themes and more.
|
||||||
@@ -126,10 +197,10 @@ flex-manager / grid-manager.
|
|||||||
|
|
||||||
### Flex
|
### Flex
|
||||||
Flex is similar to pack in tkinter, the widgets will be arranged horizontally/vertically
|
Flex is similar to pack in tkinter, the widgets will be arranged horizontally/vertically
|
||||||
depending on the flex-direction
|
depending on the side. You can use anchor to position within the side. [Understanding the working of pack](https://www.youtube.com/watch?v=rbW1iJO1psk)
|
||||||
|
|
||||||
### Grid
|
### Grid
|
||||||
Grid is a 2d layout manager, you can position each widget by clicking on widget -> toolbar -> grid-manager
|
Grid is a 2d layout manager, you can position each widget by clicking on widget(the child) -> toolbar -> grid-manager. You'll also have to define the no of rows and cols using grid configure accord and Grid weight accord on the parent.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -139,16 +210,6 @@ You can use position absolute for specific widget by checking the absolute posit
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Swapping widgets on flex layout
|
|
||||||
|
|
||||||
You can swap widget by bringing the widget near the edge of the other widget.
|
|
||||||
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<div class="alert-title">Warning ⚠️</div>
|
|
||||||
This feature is still work in progress and sometimes may not work as expected
|
|
||||||
</div>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Plugins
|
## Plugins
|
||||||
|
|
||||||
@@ -163,6 +224,8 @@ Once you are happy with the UI, you can click on export code from the header and
|
|||||||
## Requirements.txt
|
## Requirements.txt
|
||||||
The requirements.txt files are auto generated, before running the code ensure you have installed the dependencies by using `pip install -r requirements.txt`
|
The requirements.txt files are auto generated, before running the code ensure you have installed the dependencies by using `pip install -r requirements.txt`
|
||||||
|
|
||||||
## Saving the file
|
## Saving/load the file
|
||||||
|
|
||||||
Files are not saved or stored. However this is an upcoming feature for the [Premium users](https://github.com/PaulleDemon/PyUIBuilder?tab=readme-ov-file#license---fund-the-development)
|
You can save the design file by clicking on the save button from the file dropdown
|
||||||
|
|
||||||
|
Similiarly, you can load the design by clicking the load button in the file dropdown.
|
||||||
BIN
landingpages/landingpage/assets/images/home/benefits/1.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
landingpages/landingpage/assets/images/home/benefits/2.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
landingpages/landingpage/assets/images/home/benefits/3.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
landingpages/landingpage/assets/images/home/benefits/4.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
landingpages/landingpage/assets/images/home/benefits/5.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
landingpages/landingpage/assets/images/home/benefits/6.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
--bg-color: #fff;
|
--bg-color: #fff;
|
||||||
|
|
||||||
--btn-color: #0F1727;/* button color*/
|
--btn-color: #ffffff;/* button color*/
|
||||||
--btn-bg: #f1efef;/* button bg color*/
|
--btn-bg: #0F1727;/* button bg color*/
|
||||||
|
|
||||||
--primary-text-color: #000;
|
--primary-text-color: #000;
|
||||||
--header-link-hover: #000;
|
--header-link-hover: #000;
|
||||||
@@ -181,7 +181,7 @@ header > .collapsible-header{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer-link:hover{
|
.footer-link:hover{
|
||||||
color: #fff;
|
color: #0b0b0b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navigation dots styling */
|
/* Navigation dots styling */
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<meta name="description" content="A Drag and Drop builder for Python GUIs, including Tkinter, Custom Tkinter and more" />
|
<meta name="description" content="A Drag and Drop builder for Python GUIs, including Tkinter, Custom Tkinter and more" />
|
||||||
<link
|
<link
|
||||||
rel="shortcut icon"
|
rel="shortcut icon"
|
||||||
href="./assets/logo/icon48.png"
|
href="./assets/logo/logo.png"
|
||||||
type="image/x-icon"
|
type="image/x-icon"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -15,12 +15,12 @@
|
|||||||
<meta property="og:title" content="PyUIBuilder - A Drag and Drop builder for python GUIs" />
|
<meta property="og:title" content="PyUIBuilder - A Drag and Drop builder for python GUIs" />
|
||||||
<meta property="og:description" content="A Drag and Drop builder for Python GUIs, including Tkinter, Custom Tkinter and more" />
|
<meta property="og:description" content="A Drag and Drop builder for Python GUIs, including Tkinter, Custom Tkinter and more" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="https://github.com/PaulleDemon/PyUIBuilder" />
|
<meta property="og:url" content="https://about.pyuibuilder.com" />
|
||||||
<!--Replace with the current website url-->
|
<!--Replace with the current website url-->
|
||||||
<meta property="og:image" content="" />
|
<meta property="og:image" content="" />
|
||||||
|
|
||||||
<link rel="stylesheet" href="../tailwind/tailwind-runtime.css" />
|
<!-- <link rel="stylesheet" href="../tailwind/tailwind-runtime.css" /> -->
|
||||||
<!-- <link rel="stylesheet" href="./css/tailwind-build.css"> -->
|
<link rel="stylesheet" href="./css/tailwind-build.css">
|
||||||
<link rel="stylesheet" href="css/index.css" />
|
<link rel="stylesheet" href="css/index.css" />
|
||||||
|
|
||||||
<link
|
<link
|
||||||
@@ -66,14 +66,17 @@
|
|||||||
<a class="header-links" href=""> About </a>
|
<a class="header-links" href=""> About </a>
|
||||||
<a class="header-links" href="#pricing"> Pricing </a>
|
<a class="header-links" href="#pricing"> Pricing </a>
|
||||||
<a class="header-links" href="#features"> Features </a>
|
<a class="header-links" href="#features"> Features </a>
|
||||||
<a class="header-links" href="https://github.com/PaulleDemon/PyUIBuilder"> Github </a>
|
<a class="header-links" target="_blank" rel="noopener noreferrer" href="https://docs.pyuibuilder.com"> Docs </a>
|
||||||
|
<a class="header-links" target="_blank" rel="noopener noreferrer" href="https://github.com/PaulleDemon/PyUIBuilder"> Github </a>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="tw-mx-4 tw-flex tw-place-items-center tw-gap-[20px] tw-text-base max-md:tw-w-full max-md:tw-flex-col max-md:tw-place-content-center"
|
class="tw-mx-4 tw-flex tw-place-items-center tw-gap-[20px] tw-text-base max-md:tw-w-full max-md:tw-flex-col max-md:tw-place-content-center"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="#pricing"
|
href="https://pyuibuilder.com"
|
||||||
aria-label="start"
|
aria-label="start"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
class="tw-rounded-md tw-border-[1px] tw-border-black
|
class="tw-rounded-md tw-border-[1px] tw-border-black
|
||||||
tw-bg-[#0F1727] tw-px-3 tw-py-2 tw-text-white
|
tw-bg-[#0F1727] tw-px-3 tw-py-2 tw-text-white
|
||||||
tw-duration-[0.3s]
|
tw-duration-[0.3s]
|
||||||
@@ -130,11 +133,11 @@
|
|||||||
class="tw-flex tw-flex-col tw-place-content-center tw-items-center"
|
class="tw-flex tw-flex-col tw-place-content-center tw-items-center"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="reveal-up tw-text-center tw-text-6xl tw-font-semibold tw-uppercase tw-leading-[90px]
|
class="tw-text-center tw-text-6xl tw-font-semibold tw-uppercase tw-leading-[90px]
|
||||||
max-lg:tw-text-4xl tw-text-[#0F1727]
|
max-lg:tw-text-4xl tw-text-[#0F1727]
|
||||||
max-md:tw-leading-snug"
|
max-md:tw-leading-snug"
|
||||||
>
|
>
|
||||||
<span class=""> Build Python UI's using <span class="tw-px-3 t"></span>
|
<span class=""> Build Python UI's using <span class="tw-px-3"></span>
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<span class=" tw-px-3 tw-text-5xl"> Drag and Drop editor </span>
|
<span class=" tw-px-3 tw-text-5xl"> Drag and Drop editor </span>
|
||||||
@@ -146,7 +149,7 @@
|
|||||||
<div
|
<div
|
||||||
class="reveal-up tw-mt-3 tw-max-w-[450px] tw-p-2 tw-text-center tw-text-gray-700 max-lg:tw-max-w-full"
|
class="reveal-up tw-mt-3 tw-max-w-[450px] tw-p-2 tw-text-center tw-text-gray-700 max-lg:tw-max-w-full"
|
||||||
>
|
>
|
||||||
Tired of writing code to build you python GUIs, well now you can easily
|
The most awaited python tool is here. Now you can use Drag and drop to build Tkinter and customTk GUIs. Soon wil be made available for kivy and PySide
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -154,7 +157,9 @@
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="btn !tw-bg-[#0F1727] !tw-text-white tw-shadow-lg tw-shadow-[#4d395e] tw-transition-transform tw-duration-[0.3s] hover:tw-scale-x-[1.03]"
|
class="btn !tw-bg-[#0F1727] !tw-text-white tw-shadow-lg tw-shadow-[#4d395e] tw-transition-transform tw-duration-[0.3s] hover:tw-scale-x-[1.03]"
|
||||||
href="#pricing"
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="https://pyuibuilder.com"
|
||||||
>
|
>
|
||||||
Start for free
|
Start for free
|
||||||
</a>
|
</a>
|
||||||
@@ -183,14 +188,152 @@
|
|||||||
<div class="tw-relative tw-bg-black tw-min-w-full tw-min-h-full tw-overflow-clip tw-rounded-md">
|
<div class="tw-relative tw-bg-black tw-min-w-full tw-min-h-full tw-overflow-clip tw-rounded-md">
|
||||||
<!-- <iframe class="tw-absolute tw-top-[50%] tw--translate-y-[50%] tw-left-[50%] tw--translate-x-[50%] tw-w-full tw-h-full" src="https://www.youtube.com/embed/aXq0A6iJoKU?si=dF3UZT2eTRDU8u7a?si=QOMSCki8Jl30_CkW&controls=1&rel=0&showinfo=0&autoplay=1&loop=1&mute=1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> -->
|
<!-- <iframe class="tw-absolute tw-top-[50%] tw--translate-y-[50%] tw-left-[50%] tw--translate-x-[50%] tw-w-full tw-h-full" src="https://www.youtube.com/embed/aXq0A6iJoKU?si=dF3UZT2eTRDU8u7a?si=QOMSCki8Jl30_CkW&controls=1&rel=0&showinfo=0&autoplay=1&loop=1&mute=1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> -->
|
||||||
<img class="tw-object-contain tw-w-full tw-h-full"
|
<img class="tw-object-contain tw-w-full tw-h-full"
|
||||||
alt="font tester gif" src="./assets/images/home/promo.gif" >
|
alt="Pyui gif" src="./assets/images/home/promo.gif" >
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
class="tw-relative tw-flex tw-w-full tw-min-h-[100vh] tw-max-w-[100vw] tw-flex-col tw-place-content-center tw-place-items-center tw-overflow-hidden tw-p-8"
|
||||||
|
id="features"
|
||||||
|
>
|
||||||
|
|
||||||
|
<h2 class="tw-text-5xl tw-text-center tw-font-medium tw-uppercase">
|
||||||
|
Everything you need <br>
|
||||||
|
to perfect Python GUIs
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class="tw-flex tw-flex-col tw-max-w-[1150px] max-lg:tw-max-w-full tw-h-full
|
||||||
|
tw-p-4 tw-mt-4 max-lg:tw-place-content-center tw-gap-8"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="max-xl:tw-flex max-xl:tw-flex-col tw-place-items-center tw-grid tw-grid-cols-3 tw-gap-8
|
||||||
|
tw-place-content-center tw-auto-rows-auto">
|
||||||
|
|
||||||
|
<div class="tw-w-[350px] tw-h-[500px] max-lg:tw-h-fit tw-flex max-md:tw-w-full">
|
||||||
|
<div href="#" class=" tw-relative tw-p-10 tw-transition-all tw-duration-300 tw-group/card tw-gap-5 tw-flex
|
||||||
|
tw-flex-col tw-w-full tw-h-full tw-bg-[#f2f2f2] tw-border-2 tw-border-white tw-rounded-3xl
|
||||||
|
hover:tw-scale-[1.02]">
|
||||||
|
<div class="tw-w-full tw-min-h-[200px] tw-h-[200px] tw-overflow-hidden">
|
||||||
|
<img src="./assets/images/home/benefits/1.png"
|
||||||
|
alt="Drag and drop" class="tw-w-full tw-h-full tw-object-contain">
|
||||||
|
<!-- <i class="bi bi-grid-1x2-fill"></i> -->
|
||||||
|
</div>
|
||||||
|
<h2 class="tw-text-3xl max-md:tw-text-2xl tw-font-medium tw-uppercase">Drag and Drop editor</h2>
|
||||||
|
<p class="tw-leading-normal tw-text-gray-800 tw-overflow-clip">
|
||||||
|
Easily drag and drop desired widgets from sidebar and create beautiful Python GUI's
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tw-w-[350px] tw-h-[500px] max-lg:tw-h-fit tw-flex max-md:tw-w-full">
|
||||||
|
<div href="#" class=" tw-relative tw-p-10 tw-transition-all tw-duration-300 tw-group/card tw-gap-5 tw-flex
|
||||||
|
tw-flex-col tw-w-full tw-h-full tw-bg-[#f2f2f2] tw-border-2 tw-border-white tw-rounded-3xl
|
||||||
|
hover:tw-scale-[1.02]">
|
||||||
|
<div class="tw-w-full tw-min-h-[200px] tw-h-[200px] tw-overflow-hidden">
|
||||||
|
<img src="./assets/images/home/benefits/2.png"
|
||||||
|
alt="live preview" class="tw-w-full tw-h-full tw-object-contain">
|
||||||
|
<!-- <i class="bi bi-grid-1x2-fill"></i> -->
|
||||||
|
</div>
|
||||||
|
<h2 class="tw-text-3xl max-md:tw-text-2xl tw-font-medium tw-uppercase">Support for multiple UI libraries</h2>
|
||||||
|
<p class="tw-leading-normal tw-text-gray-800 tw-overflow-clip">
|
||||||
|
We currently support multiple UI libraries such as Tkinter, CustomTk and soon will offer support for PySide and
|
||||||
|
kivy.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tw-w-[350px] tw-h-[500px] max-lg:tw-h-fit tw-flex max-md:tw-w-full">
|
||||||
|
<div href="#" class=" tw-relative tw-p-10 tw-transition-all tw-duration-300 tw-group/card tw-gap-5 tw-flex
|
||||||
|
tw-flex-col tw-w-full tw-h-full tw-bg-[#f2f2f2] tw-border-2 tw-border-white tw-rounded-3xl
|
||||||
|
hover:tw-scale-[1.02]">
|
||||||
|
<div class="tw-w-full tw-min-h-[200px] tw-h-[200px] tw-overflow-hidden">
|
||||||
|
<img src="./assets/images/home/benefits/3.png"
|
||||||
|
alt="live preview" class="tw-w-full tw-h-full tw-object-contain">
|
||||||
|
<!-- <i class="bi bi-grid-1x2-fill"></i> -->
|
||||||
|
</div>
|
||||||
|
<h2 class="tw-text-3xl max-md:tw-text-2xl tw-font-medium tw-uppercase">Supports many UI Widgets</h2>
|
||||||
|
<p class="tw-leading-normal tw-text-gray-800 tw-overflow-clip">
|
||||||
|
Our editor includes pre-defined widgets like Buttons and Labels, compatible with Pack/Flex, Grid, and absolute layouts.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-w-[350px] tw-h-[500px] max-lg:tw-h-fit tw-flex max-md:tw-w-full">
|
||||||
|
<div href="#" class=" tw-relative tw-p-10 tw-transition-all tw-duration-300 tw-group/card tw-gap-5 tw-flex
|
||||||
|
tw-flex-col tw-w-full tw-h-full tw-bg-[#f2f2f2] tw-border-2 tw-border-white tw-rounded-3xl
|
||||||
|
hover:tw-scale-[1.02]">
|
||||||
|
<div class="tw-w-full tw-min-h-[200px] tw-h-[200px] tw-overflow-hidden">
|
||||||
|
<img src="./assets/images/home/benefits/4.png"
|
||||||
|
alt="live preview" class="tw-w-full tw-h-full tw-object-contain">
|
||||||
|
<!-- <i class="bi bi-grid-1x2-fill"></i> -->
|
||||||
|
</div>
|
||||||
|
<h2 class="tw-text-3xl max-md:tw-text-2xl tw-font-medium tw-uppercase">Generate clean Python code</h2>
|
||||||
|
<p class="tw-leading-normal tw-text-gray-800">
|
||||||
|
Our editor doesn't just let you drag and drop, but also lets you generate clean pyhon code in
|
||||||
|
library of your choice
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tw-w-[350px] tw-h-[500px] max-lg:tw-h-fit tw-flex max-md:tw-w-full">
|
||||||
|
<div href="#" class=" tw-relative tw-p-10 tw-transition-all tw-duration-300 tw-group/card tw-gap-5 tw-flex
|
||||||
|
tw-flex-col tw-w-full tw-h-full tw-bg-[#f2f2f2] tw-border-2 tw-border-white tw-rounded-3xl
|
||||||
|
hover:tw-scale-[1.02]">
|
||||||
|
<div class="tw-w-full tw-min-h-[200px] tw-h-[200px] tw-overflow-hidden">
|
||||||
|
<img src="./assets/images/home/benefits/5.png"
|
||||||
|
alt="live preview" class="tw-w-full tw-h-full tw-object-contain">
|
||||||
|
<!-- <i class="bi bi-grid-1x2-fill"></i> -->
|
||||||
|
</div>
|
||||||
|
<h2 class="tw-text-3xl max-md:tw-text-2xl tw-font-medium tw-uppercase">3rd Party Plugin support</h2>
|
||||||
|
<p class="tw-leading-normal tw-text-gray-800">
|
||||||
|
We support your favorite 3rd party widgets such as video player, Map viewer and more.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tw-w-[350px] tw-h-[500px] max-lg:tw-h-fit tw-flex max-md:tw-w-full">
|
||||||
|
<div href="#" class=" tw-relative tw-p-10 tw-transition-all tw-duration-300 tw-group/card tw-gap-5 tw-flex
|
||||||
|
tw-flex-col tw-w-full tw-h-full tw-bg-[#f2f2f2] tw-border-2 tw-border-white tw-rounded-3xl
|
||||||
|
hover:tw-scale-[1.02]">
|
||||||
|
<div class="tw-w-full tw-min-h-[200px] tw-h-[200px] tw-overflow-hidden">
|
||||||
|
<img src="./assets/images/home/benefits/6.png"
|
||||||
|
alt="live preview" class="tw-w-full tw-h-full tw-object-contain">
|
||||||
|
<!-- <i class="bi bi-grid-1x2-fill"></i> -->
|
||||||
|
</div>
|
||||||
|
<h2 class="tw-text-3xl max-md:tw-text-2xl tw-font-medium tw-uppercase">Supports asset upload</h2>
|
||||||
|
<p class="tw-leading-normal tw-text-gray-800">
|
||||||
|
Adding images and logos is important while developing GUIs so we support asset uploads.
|
||||||
|
The best part is nothing is sent to server.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="tw-mt-6 btn tw-my-6 tw-flex tw-gap-1 tw-bg-[#0F1727]
|
||||||
|
tw-transition-transform tw-duration-[0.3s] hover:tw-scale-x-[1.03] tw-group"
|
||||||
|
href="#pricing"
|
||||||
|
>
|
||||||
|
Make it yours
|
||||||
|
<i class="bi bi-arrow-right tw-transition-transform group-hover:tw-translate-x-1 tw-duration-300"></i>
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="tw-mt-1 tw-flex tw-w-full tw-flex-col tw-place-items-center tw-p-[2%] max-lg:tw-p-2" id="pricing">
|
<section class="tw-mt-1 tw-flex tw-w-full tw-flex-col tw-place-items-center tw-p-[2%] max-lg:tw-p-2" id="pricing">
|
||||||
<h3 class="tw-text-2xl tw-font-medium max-md:tw-text-2xl">Pre-order your License</h3>
|
<h3 class="tw-text-2xl tw-font-medium max-md:tw-text-2xl">Pre-order your License</h3>
|
||||||
|
|
||||||
|
<div class="tw-max-w-[650px] tw-my-2">
|
||||||
|
Pre-orders for PyUiBuilder premium features are open. Premium features will start rolling out
|
||||||
|
from end of April phase wise. This is your last chance to get it before price increase from end of April.
|
||||||
|
</div>
|
||||||
<div class="tw-mt-10 tw-flex tw-place-content-center tw-gap-8 max-lg:tw-flex-col">
|
<div class="tw-mt-10 tw-flex tw-place-content-center tw-gap-8 max-lg:tw-flex-col">
|
||||||
<div class="tw-flex tw-w-[380px] tw-border-[1px] tw-border-gray-300 tw-border-solid tw-flex-col
|
<div class="tw-flex tw-w-[380px] tw-border-[1px] tw-border-gray-300 tw-border-solid tw-flex-col
|
||||||
tw-place-items-center tw-gap-2 tw-rounded-lg tw-p-8 tw-shadow-xl max-lg:tw-w-[340px]">
|
tw-place-items-center tw-gap-2 tw-rounded-lg tw-p-8 tw-shadow-xl max-lg:tw-w-[340px]">
|
||||||
@@ -198,7 +341,7 @@
|
|||||||
<span class="tw-text-5xl tw-font-semibold">$0</span>
|
<span class="tw-text-5xl tw-font-semibold">$0</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="tw-mt-3 tw-text-base tw-text-center tw-text-gray-800">
|
<p class="tw-mt-3 tw-text-base tw-text-center tw-text-gray-800">
|
||||||
Free to use forever, but for added features and to support open-source development, consider buying a lifetime license.
|
Free to use forever, but for added features and to support development, consider buying a lifetime license.
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
<ul class="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
<ul class="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
||||||
@@ -214,6 +357,14 @@
|
|||||||
<i class="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
<i class="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
||||||
<span>Downloadable UI builder exe for local development</span>
|
<span>Downloadable UI builder exe for local development</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i class="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
||||||
|
<span>Premium widgets <small class="tw-text-sm">(eg: scroll widget, tab widget, Canvas etc)</small></span>
|
||||||
|
</li>
|
||||||
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i class="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
||||||
|
<span>Event handlers and code editor</span>
|
||||||
|
</li>
|
||||||
<li class="tw-flex tw-place-items-center tw-gap-2">
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
<i class="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
<i class="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
||||||
<span>Support for PySlide/PyQt</span>
|
<span>Support for PySlide/PyQt</span>
|
||||||
@@ -249,7 +400,7 @@
|
|||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
<a
|
<a
|
||||||
href="https://pyuibuilder.pages.dev/"
|
href="https://pyuibuilder.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
class=" !tw-bg-[#0F1727] tw-duration-[0.3s] hover:tw-transition-transform hover:tw-scale-[1.01] !tw-mt-auto !tw-text-white tw-gap-2 tw-text-lg tw-rounded-md tw-w-full tw-flex tw-place-content-center tw-p-2 tw-mx-2"
|
class=" !tw-bg-[#0F1727] tw-duration-[0.3s] hover:tw-transition-transform hover:tw-scale-[1.01] !tw-mt-auto !tw-text-white tw-gap-2 tw-text-lg tw-rounded-md tw-w-full tw-flex tw-place-content-center tw-p-2 tw-mx-2"
|
||||||
@@ -275,7 +426,7 @@
|
|||||||
<span class="tw-text-2xl tw-text-gray-600">Forever</span>
|
<span class="tw-text-2xl tw-text-gray-600">Forever</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="tw-mt-3 tw-text-center tw-text-gray-600">
|
<p class="tw-mt-3 tw-text-center tw-text-gray-600">
|
||||||
Support open-source development 🚀. Plus, get added benefits.
|
Best for hobby users and people who want to learn basic UI development without PySide
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
<ul class="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
<ul class="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
||||||
@@ -287,6 +438,14 @@
|
|||||||
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Downloadable UI builder exe for local development</span>
|
<span>Downloadable UI builder exe for local development</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
|
<span>Premium widgets <small class="tw-text-sm">(eg: scroll widget, tab widget, Canvas etc)</small></span>
|
||||||
|
</li>
|
||||||
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
|
<span>Event handlers and code editor</span>
|
||||||
|
</li>
|
||||||
<li class="tw-flex tw-place-items-center tw-gap-2">
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Preview live</span>
|
<span>Preview live</span>
|
||||||
@@ -338,7 +497,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tw-flex tw-w-[380px] tw-flex-col tw-place-items-center tw-gap-2 tw-rounded-lg tw-border-[3px] tw-border-solid
|
<div class="tw-flex tw-w-[380px] tw-flex-col tw-place-items-center tw-gap-2 tw-rounded-lg tw-border-[3px] tw-border-solid
|
||||||
!tw-border-green-600 tw-p-8 tw-shadow-xl max-lg:tw-w-[340px]">
|
!tw-border-blue-600 tw-p-8 tw-shadow-xl max-lg:tw-w-[340px]">
|
||||||
<div class="tw-text-white tw-p-1 tw-px-3 tw-bg-blue-500 tw-rounded-full">
|
<div class="tw-text-white tw-p-1 tw-px-3 tw-bg-blue-500 tw-rounded-full">
|
||||||
Limited time offer
|
Limited time offer
|
||||||
</div>
|
</div>
|
||||||
@@ -353,7 +512,7 @@
|
|||||||
<span class="tw-text-2xl tw-text-gray-600">Forever</span>
|
<span class="tw-text-2xl tw-text-gray-600">Forever</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="tw-mt-3 tw-text-center tw-text-gray-600">
|
<p class="tw-mt-3 tw-text-center tw-text-gray-600">
|
||||||
Support open-source development 🚀. Plus, get added benefits.
|
Best for startups and teams and people who want to use internally or serves a commercial purpose and want support for PySide
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
<ul class="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
<ul class="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
||||||
@@ -365,6 +524,14 @@
|
|||||||
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Downloadable UI builder exe for local development</span>
|
<span>Downloadable UI builder exe for local development</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
|
<span>Premium widgets <small class="tw-text-sm">(eg: scroll widget, tab widget, Canvas etc)</small></span>
|
||||||
|
</li>
|
||||||
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
|
<span>Event handlers and code editor</span>
|
||||||
|
</li>
|
||||||
<li class="tw-flex tw-place-items-center tw-gap-2">
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Preview live</span>
|
<span>Preview live</span>
|
||||||
@@ -458,7 +625,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
Yes, PyUIBuilder is free to use forever, however to support
|
Yes, PyUIBuilder is free to use forever, however to support
|
||||||
open-source development, some features are only available to premium users.
|
development, some features are only available to premium users.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -483,25 +650,82 @@
|
|||||||
<div
|
<div
|
||||||
class="faq-accordion tw-flex tw-w-full tw-select-none tw-text-xl max-md:tw-text-lg"
|
class="faq-accordion tw-flex tw-w-full tw-select-none tw-text-xl max-md:tw-text-lg"
|
||||||
>
|
>
|
||||||
<span>Where can I find the upcoming features?</span>
|
<span>When will premium features roll out</span>
|
||||||
<i class="bi bi-plus tw-ml-auto tw-font-semibold"></i>
|
<i class="bi bi-plus tw-ml-auto tw-font-semibold"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
You can find the upcoming features on the
|
Premium features will start rolling out towards the end of April in phase wise.
|
||||||
<a
|
</div>
|
||||||
target="_blank"
|
</div>
|
||||||
rel="noopener noreferrer"
|
<div
|
||||||
href="https://github.com/PaulleDemon/font-tester-chrome/blob/main/roadmap.md"
|
class="faq tw-w-full"
|
||||||
class="tw-underline"
|
>
|
||||||
>Roadmap</a
|
<div
|
||||||
>
|
class="faq-accordion tw-flex tw-w-full tw-select-none tw-text-xl max-md:tw-text-lg"
|
||||||
|
>
|
||||||
|
<span>Can I get a free license?</span>
|
||||||
|
<i class="bi bi-plus tw-ml-auto tw-font-semibold"></i>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
Yes, we have a program by which you can get a free license, <a href="https://tally.so/r/mJM22X" class="tw-underline tw-underline-offset-1">read more</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-flex tw-flex-col tw-gap-2 tw-place-items-center tw-mt-4">
|
||||||
|
<div class="tw-text-2xl">Want more help?</div>
|
||||||
|
<a class="btn hover:tw-scale-[1.02] tw-duration-75" target="_blank" rel="noopener noreferrer"
|
||||||
|
href="https://discord.gg/dHXjrrCA7G">Discord invite</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
class="tw-flex tw-w-full tw-flex-col tw-place-content-center tw-place-items-center tw-gap-[5%] tw-p-[3%] tw-px-[10%]"
|
||||||
|
id="blog-section"
|
||||||
|
>
|
||||||
|
<h3 class="tw-text-4xl tw-font-medium tw-text-gray-800 max-md:tw-text-2xl">Blogs & Tutorials</h3>
|
||||||
|
<div class="tw-mt-5 tw-flex tw-gap-8 tw-place-items-center max-lg:tw-flex-col
|
||||||
|
tw-place-content-center tw-min-h-[300px] tw-w-full">
|
||||||
|
<div class="tw-flex tw-gap-4" id="blogs">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<a href="https://youtu.be/MkeMv0X-w_4?si=qeutS58Js7Padd9m&t=49" target="_blank" rel="noopener noreferrer"
|
||||||
|
class="tw-w-[350px] tw-min-w-[350px] tw-max-w-[350px] max-lg:tw-w-full tw-h-[360px] tw-shadow-xl tw-rounded-md tw-overflow-hidden">
|
||||||
|
<div class="tw-h-[230px] tw-w-full tw-bg-gray-800 tw-flex tw-place-content-center">
|
||||||
|
<img src="https://img.youtube.com/vi/MkeMv0X-w_4/hqdefault.jpg"
|
||||||
|
alt="" class="tw-object-contain tw-w-full">
|
||||||
|
</div>
|
||||||
|
<div class="tw-flex tw-flex-col tw-gap-2 tw-p-3">
|
||||||
|
<h3 class="tw-text-lg tw-font-medium">Introduction to PyUiBuilder - YouTube</h3>
|
||||||
|
<p class="tw-text-bg-gray-500">Introduction to PyUIBuilder</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="https://youtu.be/4w3Oy17FE8U?si=-0V0cD5DQAEzCkKm" target="_blank" rel="noopener noreferrer"
|
||||||
|
class="tw-w-[350px] tw-min-w-[350px] tw-max-w-[350px] max-lg:tw-w-full tw-h-[360px] tw-shadow-xl tw-rounded-md tw-overflow-hidden">
|
||||||
|
<div class="tw-h-[230px] tw-w-full tw-bg-gray-800 tw-flex tw-place-content-center">
|
||||||
|
<img src="https://img.youtube.com/vi/4w3Oy17FE8U/hqdefault.jpg"
|
||||||
|
alt="" class="tw-object-contain tw-w-full">
|
||||||
|
</div>
|
||||||
|
<div class="tw-flex tw-flex-col tw-gap-2 tw-p-3">
|
||||||
|
<h3 class="tw-text-lg tw-font-medium">Create Signup form using PyUIBuilder - YouTube</h3>
|
||||||
|
<p class="tw-text-bg-gray-500">Learn how to use this tool, via practical example</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
class="tw-flex tw-w-full tw-flex-col tw-place-content-center tw-place-items-center tw-gap-2 tw-p-[3%] tw-px-[10%]"
|
||||||
|
>
|
||||||
|
<h3 class="tw-text-xl">Get updates right in your mailbox</h3>
|
||||||
|
|
||||||
|
<button class="btn hover:tw-scale-[1.05] tw-duration-75" data-tally-open="mVDY7N" data-tally-layout="modal" data-tally-hide-title="1" data-tally-emoji-text="👋" data-tally-emoji-animation="wave" data-tally-auto-close="0">
|
||||||
|
Get updates
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
<footer
|
<footer
|
||||||
class="tw-mt-auto tw-flex tw-w-full tw-place-content-around tw-gap-3 tw-p-[5%] tw-px-[10%] tw-text-black max-md:tw-flex-col"
|
class="tw-mt-auto tw-flex tw-w-full tw-place-content-around tw-gap-3 tw-p-[5%] tw-px-[10%] tw-text-black max-md:tw-flex-col"
|
||||||
@@ -549,7 +773,8 @@
|
|||||||
<div class="tw-flex tw-flex-col tw-gap-3 max-md:tw-text-sm">
|
<div class="tw-flex tw-flex-col tw-gap-3 max-md:tw-text-sm">
|
||||||
<a href="#features" class="footer-link">Features</a>
|
<a href="#features" class="footer-link">Features</a>
|
||||||
<a href="#pricing" class="footer-link">Pricing</a>
|
<a href="#pricing" class="footer-link">Pricing</a>
|
||||||
<a href="https://github.com/PaulleDemon/font-tester-chrome/blob/main/roadmap.md" class="footer-link">Roadmap</a>
|
<a href="https://github.com/PaulleDemon/PyUIBuilder/blob/main/roadmap.md" class="footer-link">Roadmap</a>
|
||||||
|
<a href="./privacy.html" class="footer-link">Privacy</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -557,12 +782,43 @@
|
|||||||
<div class="tw-flex tw-flex-col tw-gap-3 max-md:tw-text-sm">
|
<div class="tw-flex tw-flex-col tw-gap-3 max-md:tw-text-sm">
|
||||||
<a href="" class="footer-link">About</a>
|
<a href="" class="footer-link">About</a>
|
||||||
<a href="#faq" class="footer-link">FAQ</a>
|
<a href="#faq" class="footer-link">FAQ</a>
|
||||||
<a href="https://github.com/PaulleDemon/font-tester-chrome" class="footer-link">Github</a>
|
<a href="https://github.com/PaulleDemon/PyUIBuilder" class="footer-link">Github</a>
|
||||||
<a href="https://github.com/PaulleDemon/font-tester-chrome" class="footer-link">Report issue</a>
|
<a href="https://github.com/PaulleDemon/PyUIBuilder/issues" class="footer-link">Report issue</a>
|
||||||
|
<a href="https://discord.gg/dHXjrrCA7G" class="footer-link">Discord invite</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
<script async src="https://tally.so/widgets/embed.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const blogsList = [
|
||||||
|
{
|
||||||
|
"title": "How to create Signup form - Blog",
|
||||||
|
"description": "If your starting out with tkinter, it can be overwhelming without prior understanding of GUIs....",
|
||||||
|
"url": "https://medium.com/python-in-plain-english/create-tkinter-guis-using-tkinter-gui-builder-pyuibuilder-a7422489c55e",
|
||||||
|
"image_url": "https://miro.medium.com/v2/resize:fit:720/format:webp/1*BaSis1UO3_zJeQtCnOe3Qg.png"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const blogContainer = document.getElementById('blogs');
|
||||||
|
blogContainer.innerHTML = blogsList.map(blog => {
|
||||||
|
return (`
|
||||||
|
<a href="${blog.url}" target="_blank" rel="noopener noreferrer"
|
||||||
|
class="tw-w-[350px] tw-h-[360px] tw-shadow-xl tw-rounded-md tw-overflow-hidden">
|
||||||
|
|
||||||
|
<div class="tw-h-[230px] tw-w-full tw-bg-gray-800 tw-flex tw-place-content-center">
|
||||||
|
<img src="${blog.image_url}" alt="${blog.title}" class="tw-object-contain tw-w-full">
|
||||||
|
</div>
|
||||||
|
<div class="tw-flex tw-flex-col tw-gap-2 tw-p-3">
|
||||||
|
<h3 class="tw-text-lg tw-font-medium">${blog.title}</h3>
|
||||||
|
<p class="tw-text-bg-gray-500">${blog.description}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
`)
|
||||||
|
}).join('')
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
<script
|
<script
|
||||||
src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.0/gsap.min.js"
|
src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.0/gsap.min.js"
|
||||||
|
|||||||
50
landingpages/landingpage/privacy.html
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="Privacy Statement for PyUIBuilder, explaining what data we collect and how we protect it." />
|
||||||
|
<title>Privacy Statement – PyUIBuilder</title>
|
||||||
|
<link rel="stylesheet" href="./css/tailwind-build.css">
|
||||||
|
<link rel="stylesheet" href="css/index.css" />
|
||||||
|
</head>
|
||||||
|
<body class="tw-bg-gray-100 tw-flex tw-flex-col tw-place-content-center tw-font-sans tw-text-gray-800 tw-min-h-screen tw-px-4 tw-py-10">
|
||||||
|
|
||||||
|
<section class="tw-max-w-3xl tw-mx-auto tw-p-6 tw-bg-white tw-rounded-2xl tw-shadow-lg">
|
||||||
|
<h1 class="tw-text-3xl tw-font-bold tw-mb-4">Privacy Statement</h1>
|
||||||
|
|
||||||
|
<p class="tw-mb-4">
|
||||||
|
At <span class="tw-font-semibold">PyUIBuilder</span>, we respect your privacy and are committed to protecting your personal information. This Privacy Statement outlines the data we collect and how it is used.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 class="tw-text-xl tw-font-semibold tw-mt-6 tw-mb-2">Information We Collect</h2>
|
||||||
|
<ul class="tw-list-disc tw-pl-6 tw-mb-4">
|
||||||
|
<li>Your email address</li>
|
||||||
|
<li>Your name</li>
|
||||||
|
<li>Your profile information</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 class="tw-text-xl tw-font-semibold tw-mt-6 tw-mb-2">How We Use Your Information</h2>
|
||||||
|
<p class="tw-mb-4">
|
||||||
|
The information we collect is used solely to provide and improve our service, personalize your experience, and ensure platform security.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 class="tw-text-xl tw-font-semibold tw-mt-6 tw-mb-2">Data Sharing</h2>
|
||||||
|
<p class="tw-mb-4">
|
||||||
|
<span class="tw-font-semibold">We do not share your personal information with third parties.</span> All data remains secure and confidential within PyUIBuilder.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 class="tw-text-xl tw-font-semibold tw-mt-6 tw-mb-2">Your Consent</h2>
|
||||||
|
<p class="tw-mb-4">
|
||||||
|
By using our website, you consent to the collection and use of your information as described in this Privacy Statement.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 class="tw-text-xl tw-font-semibold tw-mt-6 tw-mb-2">Contact Us</h2>
|
||||||
|
<p class="tw-text-gray-700">
|
||||||
|
If you have any questions or concerns about our privacy practices, please contact us at
|
||||||
|
<a href="mailto:info@pyuibuilder.com" class="tw-text-blue-600 hover:tw-underline">info@pyuibuilder.com</a>.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com
|
! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -541,7 +541,7 @@ video {
|
|||||||
|
|
||||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||||
|
|
||||||
[hidden] {
|
[hidden]:where(:not([hidden="until-found"])) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,6 +619,20 @@ video {
|
|||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-my-2 {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-my-6 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\!tw-mt-auto {
|
||||||
|
margin-top: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-ml-auto {
|
.tw-ml-auto {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
@@ -643,6 +657,10 @@ video {
|
|||||||
margin-top: 1.25rem;
|
margin-top: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-mt-6 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-mt-8 {
|
.tw-mt-8 {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
@@ -651,14 +669,14 @@ video {
|
|||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.\!tw-mt-auto {
|
|
||||||
margin-top: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-flex {
|
.tw-flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-grid {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
.\!tw-hidden {
|
.\!tw-hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
@@ -683,10 +701,26 @@ video {
|
|||||||
height: 150px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-h-\[200px\] {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-h-\[230px\] {
|
||||||
|
height: 230px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-h-\[360px\] {
|
||||||
|
height: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-h-\[40px\] {
|
.tw-h-\[40px\] {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-h-\[500px\] {
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-h-\[50px\] {
|
.tw-h-\[50px\] {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
@@ -727,6 +761,10 @@ video {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-min-h-\[200px\] {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-min-h-\[300px\] {
|
.tw-min-h-\[300px\] {
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
}
|
}
|
||||||
@@ -739,22 +777,18 @@ video {
|
|||||||
min-height: 550px;
|
min-height: 550px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-min-h-\[60vh\] {
|
|
||||||
min-height: 60vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-min-h-\[70vh\] {
|
.tw-min-h-\[70vh\] {
|
||||||
min-height: 70vh;
|
min-height: 70vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-min-h-full {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-min-h-\[80vh\] {
|
.tw-min-h-\[80vh\] {
|
||||||
min-height: 80vh;
|
min-height: 80vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-min-h-full {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-w-8 {
|
.tw-w-8 {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
}
|
}
|
||||||
@@ -767,6 +801,10 @@ video {
|
|||||||
width: 250px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-w-\[350px\] {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-w-\[380px\] {
|
.tw-w-\[380px\] {
|
||||||
width: 380px;
|
width: 380px;
|
||||||
}
|
}
|
||||||
@@ -787,6 +825,10 @@ video {
|
|||||||
width: max-content;
|
width: max-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-min-w-\[350px\] {
|
||||||
|
min-width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-min-w-full {
|
.tw-min-w-full {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
@@ -795,14 +837,26 @@ video {
|
|||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-max-w-\[1150px\] {
|
||||||
|
max-width: 1150px;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-max-w-\[120px\] {
|
.tw-max-w-\[120px\] {
|
||||||
max-width: 120px;
|
max-width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-max-w-\[350px\] {
|
||||||
|
max-width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-max-w-\[450px\] {
|
.tw-max-w-\[450px\] {
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-max-w-\[650px\] {
|
||||||
|
max-width: 650px;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-max-w-\[80vw\] {
|
.tw-max-w-\[80vw\] {
|
||||||
max-width: 80vw;
|
max-width: 80vw;
|
||||||
}
|
}
|
||||||
@@ -856,6 +910,14 @@ video {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-auto-rows-auto {
|
||||||
|
grid-auto-rows: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-grid-cols-3 {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.tw-flex-col {
|
.tw-flex-col {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -920,6 +982,10 @@ video {
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-gap-\[5\%\] {
|
||||||
|
gap: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-overflow-hidden {
|
.tw-overflow-hidden {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -940,6 +1006,10 @@ video {
|
|||||||
text-wrap: wrap;
|
text-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-rounded-3xl {
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-rounded-full {
|
.tw-rounded-full {
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
}
|
}
|
||||||
@@ -968,10 +1038,6 @@ video {
|
|||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-border-4 {
|
|
||||||
border-width: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-border-\[3px\] {
|
.tw-border-\[3px\] {
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
}
|
}
|
||||||
@@ -984,118 +1050,96 @@ video {
|
|||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.\!tw-border-white {
|
.\!tw-border-\[\#0F1727\] {
|
||||||
--tw-border-opacity: 1 !important;
|
--tw-border-opacity: 1 !important;
|
||||||
border-color: rgb(255 255 255 / var(--tw-border-opacity)) !important;
|
border-color: rgb(15 23 39 / var(--tw-border-opacity, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\!tw-border-blue-600 {
|
||||||
|
--tw-border-opacity: 1 !important;
|
||||||
|
border-color: rgb(37 99 235 / var(--tw-border-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-border-black {
|
.tw-border-black {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(0 0 0 / var(--tw-border-opacity));
|
border-color: rgb(0 0 0 / var(--tw-border-opacity, 1));
|
||||||
}
|
|
||||||
|
|
||||||
.tw-border-blue-500 {
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(59 130 246 / var(--tw-border-opacity));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-border-gray-300 {
|
.tw-border-gray-300 {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||||
}
|
|
||||||
|
|
||||||
.tw-border-green-600 {
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(22 163 74 / var(--tw-border-opacity));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-border-white {
|
.tw-border-white {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(255 255 255 / var(--tw-border-opacity));
|
border-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.\!tw-border-\[\#0F1727\] {
|
.\!tw-bg-\[\#0F1727\] {
|
||||||
--tw-border-opacity: 1 !important;
|
|
||||||
border-color: rgb(15 23 39 / var(--tw-border-opacity)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.\!tw-border-green-600 {
|
|
||||||
--tw-border-opacity: 1 !important;
|
|
||||||
border-color: rgb(22 163 74 / var(--tw-border-opacity)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.\!tw-bg-\[\#7E22CE\] {
|
|
||||||
--tw-bg-opacity: 1 !important;
|
--tw-bg-opacity: 1 !important;
|
||||||
background-color: rgb(126 34 206 / var(--tw-bg-opacity)) !important;
|
background-color: rgb(15 23 39 / var(--tw-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.\!tw-bg-gray-100 {
|
.\!tw-bg-gray-100 {
|
||||||
--tw-bg-opacity: 1 !important;
|
--tw-bg-opacity: 1 !important;
|
||||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity)) !important;
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.\!tw-bg-purple-500 {
|
.\!tw-bg-purple-500 {
|
||||||
--tw-bg-opacity: 1 !important;
|
--tw-bg-opacity: 1 !important;
|
||||||
background-color: rgb(168 85 247 / var(--tw-bg-opacity)) !important;
|
background-color: rgb(168 85 247 / var(--tw-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.\!tw-bg-transparent {
|
.\!tw-bg-transparent {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-bg-\[\#0F1727\] {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(15 23 39 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.tw-bg-\[\#f0f0f0ef\] {
|
.tw-bg-\[\#f0f0f0ef\] {
|
||||||
background-color: #f0f0f0ef;
|
background-color: #f0f0f0ef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-bg-\[\#f2f2f2\] {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(242 242 242 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
.tw-bg-\[\#fdfdfdaf\] {
|
.tw-bg-\[\#fdfdfdaf\] {
|
||||||
background-color: #fdfdfdaf;
|
background-color: #fdfdfdaf;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-bg-black {
|
.tw-bg-black {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-bg-blue-500 {
|
.tw-bg-blue-500 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
|
background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-bg-gray-100 {
|
.tw-bg-gray-100 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-bg-gray-800 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-bg-green-700 {
|
.tw-bg-green-700 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
|
background-color: rgb(21 128 61 / var(--tw-bg-opacity, 1));
|
||||||
}
|
|
||||||
|
|
||||||
.tw-bg-transparent {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-bg-white {
|
.tw-bg-white {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
||||||
}
|
|
||||||
|
|
||||||
.\!tw-bg-\[\#0F1727\] {
|
|
||||||
--tw-bg-opacity: 1 !important;
|
|
||||||
background-color: rgb(15 23 39 / var(--tw-bg-opacity)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-bg-\[\#0F1727\] {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(15 23 39 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.\!tw-bg-\[\#\#0F1727\] {
|
|
||||||
background-color: ##0F1727 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-bg-\[\#\#0F1727\] {
|
|
||||||
background-color: ##0F1727;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-bg-opacity-0 {
|
.tw-bg-opacity-0 {
|
||||||
@@ -1110,6 +1154,10 @@ video {
|
|||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-p-10 {
|
||||||
|
padding: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-p-2 {
|
.tw-p-2 {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
@@ -1118,6 +1166,10 @@ video {
|
|||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-p-4 {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-p-6 {
|
.tw-p-6 {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
@@ -1130,6 +1182,10 @@ video {
|
|||||||
padding: 2%;
|
padding: 2%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-p-\[3\%\] {
|
||||||
|
padding: 3%;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-p-\[4px\] {
|
.tw-p-\[4px\] {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
@@ -1216,6 +1272,11 @@ video {
|
|||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-text-sm {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-text-xl {
|
.tw-text-xl {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
@@ -1233,10 +1294,6 @@ video {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.\!tw-font-medium {
|
|
||||||
font-weight: 500 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-uppercase {
|
.tw-uppercase {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
@@ -1250,84 +1307,73 @@ video {
|
|||||||
line-height: 90px;
|
line-height: 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.\!tw-text-black {
|
.tw-leading-normal {
|
||||||
--tw-text-opacity: 1 !important;
|
line-height: 1.5;
|
||||||
color: rgb(0 0 0 / var(--tw-text-opacity)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.\!tw-text-blue-500 {
|
|
||||||
--tw-text-opacity: 1 !important;
|
|
||||||
color: rgb(59 130 246 / var(--tw-text-opacity)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.\!tw-text-white {
|
|
||||||
--tw-text-opacity: 1 !important;
|
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-text-black {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-text-gray-200 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(229 231 235 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-text-gray-300 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(209 213 219 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-text-gray-400 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(156 163 175 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-text-gray-600 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(75 85 99 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-text-gray-700 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(55 65 81 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-text-green-600 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(22 163 74 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-text-purple-500 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(168 85 247 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-text-red-600 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(220 38 38 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-text-white {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.\!tw-text-\[\#0F1727\] {
|
.\!tw-text-\[\#0F1727\] {
|
||||||
--tw-text-opacity: 1 !important;
|
--tw-text-opacity: 1 !important;
|
||||||
color: rgb(15 23 39 / var(--tw-text-opacity)) !important;
|
color: rgb(15 23 39 / var(--tw-text-opacity, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\!tw-text-black {
|
||||||
|
--tw-text-opacity: 1 !important;
|
||||||
|
color: rgb(0 0 0 / var(--tw-text-opacity, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\!tw-text-blue-500 {
|
||||||
|
--tw-text-opacity: 1 !important;
|
||||||
|
color: rgb(59 130 246 / var(--tw-text-opacity, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\!tw-text-white {
|
||||||
|
--tw-text-opacity: 1 !important;
|
||||||
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-text-\[\#0F1727\] {
|
.tw-text-\[\#0F1727\] {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(15 23 39 / var(--tw-text-opacity));
|
color: rgb(15 23 39 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-text-black {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(0 0 0 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-text-gray-600 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(75 85 99 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-text-gray-700 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(55 65 81 / var(--tw-text-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-text-gray-800 {
|
.tw-text-gray-800 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(31 41 55 / var(--tw-text-opacity));
|
color: rgb(31 41 55 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-text-green-600 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(22 163 74 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-text-purple-500 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(168 85 247 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-text-red-600 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(220 38 38 / var(--tw-text-opacity, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.tw-text-white {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-underline {
|
.tw-underline {
|
||||||
@@ -1364,16 +1410,17 @@ video {
|
|||||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-shadow-\[\#7E22CE\] {
|
|
||||||
--tw-shadow-color: #7E22CE;
|
|
||||||
--tw-shadow: var(--tw-shadow-colored);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-shadow-\[\#4d395e\] {
|
.tw-shadow-\[\#4d395e\] {
|
||||||
--tw-shadow-color: #4d395e;
|
--tw-shadow-color: #4d395e;
|
||||||
--tw-shadow: var(--tw-shadow-colored);
|
--tw-shadow: var(--tw-shadow-colored);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-transition-all {
|
||||||
|
transition-property: all;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition-duration: 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
.tw-transition-colors {
|
.tw-transition-colors {
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
@@ -1392,12 +1439,6 @@ video {
|
|||||||
transition-duration: 150ms;
|
transition-duration: 150ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-transition-all {
|
|
||||||
transition-property: all;
|
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
transition-duration: 150ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tw-duration-300 {
|
.tw-duration-300 {
|
||||||
transition-duration: 300ms;
|
transition-duration: 300ms;
|
||||||
}
|
}
|
||||||
@@ -1406,13 +1447,12 @@ video {
|
|||||||
transition-duration: 500ms;
|
transition-duration: 500ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tw-duration-\[0\.3s\] {
|
.tw-duration-75 {
|
||||||
transition-duration: 0.3s;
|
transition-duration: 75ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:tw-translate-x-1:hover {
|
.tw-duration-\[0\.3s\] {
|
||||||
--tw-translate-x: 0.25rem;
|
transition-duration: 0.3s;
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:tw-translate-x-2:hover {
|
.hover\:tw-translate-x-2:hover {
|
||||||
@@ -1432,6 +1472,12 @@ video {
|
|||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:tw-scale-\[1\.05\]:hover {
|
||||||
|
--tw-scale-x: 1.05;
|
||||||
|
--tw-scale-y: 1.05;
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:tw-scale-x-\[1\.03\]:hover {
|
.hover\:tw-scale-x-\[1\.03\]:hover {
|
||||||
--tw-scale-x: 1.03;
|
--tw-scale-x: 1.03;
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
@@ -1439,47 +1485,22 @@ video {
|
|||||||
|
|
||||||
.hover\:\!tw-bg-gray-100:hover {
|
.hover\:\!tw-bg-gray-100:hover {
|
||||||
--tw-bg-opacity: 1 !important;
|
--tw-bg-opacity: 1 !important;
|
||||||
background-color: rgb(243 244 246 / var(--tw-bg-opacity)) !important;
|
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:\!tw-bg-gray-300:hover {
|
.hover\:\!tw-bg-gray-300:hover {
|
||||||
--tw-bg-opacity: 1 !important;
|
--tw-bg-opacity: 1 !important;
|
||||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important;
|
background-color: rgb(209 213 219 / var(--tw-bg-opacity, 1)) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:\!tw-bg-white:hover {
|
.hover\:\!tw-bg-white:hover {
|
||||||
--tw-bg-opacity: 1 !important;
|
--tw-bg-opacity: 1 !important;
|
||||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity)) !important;
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)) !important;
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:tw-bg-white:hover {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:tw-bg-black:hover {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:tw-bg-\[\#0F1727\]:hover {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(15 23 39 / var(--tw-bg-opacity));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:\!tw-text-black:hover {
|
.hover\:\!tw-text-black:hover {
|
||||||
--tw-text-opacity: 1 !important;
|
--tw-text-opacity: 1 !important;
|
||||||
color: rgb(0 0 0 / var(--tw-text-opacity)) !important;
|
color: rgb(0 0 0 / var(--tw-text-opacity, 1)) !important;
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:tw-text-black:hover {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:tw-text-white:hover {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:tw-transition-transform:hover {
|
.hover\:tw-transition-transform:hover {
|
||||||
@@ -1488,9 +1509,14 @@ video {
|
|||||||
transition-duration: 150ms;
|
transition-duration: 150ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tw-group:hover .group-hover\:tw-translate-x-1 {
|
||||||
|
--tw-translate-x: 0.25rem;
|
||||||
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
|
}
|
||||||
|
|
||||||
.dark\:tw-bg-\[\#16171A\]:is(.tw-dark *) {
|
.dark\:tw-bg-\[\#16171A\]:is(.tw-dark *) {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(22 23 26 / var(--tw-bg-opacity));
|
background-color: rgb(22 23 26 / var(--tw-bg-opacity, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark\:tw-bg-\[\#80808085\]:is(.tw-dark *) {
|
.dark\:tw-bg-\[\#80808085\]:is(.tw-dark *) {
|
||||||
@@ -1498,6 +1524,14 @@ video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media not all and (min-width: 1280px) {
|
@media not all and (min-width: 1280px) {
|
||||||
|
.max-xl\:tw-flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-xl\:tw-flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.max-xl\:tw-place-items-center {
|
.max-xl\:tw-place-items-center {
|
||||||
place-items: center;
|
place-items: center;
|
||||||
}
|
}
|
||||||
@@ -1520,6 +1554,10 @@ video {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.max-lg\:tw-h-fit {
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
.max-lg\:tw-min-h-\[250px\] {
|
.max-lg\:tw-min-h-\[250px\] {
|
||||||
min-height: 250px;
|
min-height: 250px;
|
||||||
}
|
}
|
||||||
@@ -1528,10 +1566,6 @@ video {
|
|||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.max-lg\:tw-min-h-\[60vh\] {
|
|
||||||
min-height: 60vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-lg\:tw-min-h-\[80vh\] {
|
.max-lg\:tw-min-h-\[80vh\] {
|
||||||
min-height: 80vh;
|
min-height: 80vh;
|
||||||
}
|
}
|
||||||
@@ -1556,6 +1590,10 @@ video {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.max-lg\:tw-place-content-center {
|
||||||
|
place-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.max-lg\:tw-place-items-end {
|
.max-lg\:tw-place-items-end {
|
||||||
place-items: end;
|
place-items: end;
|
||||||
}
|
}
|
||||||
|
|||||||
15687
package-lock.json
generated
36
package.json
@@ -5,6 +5,10 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"picomatch": "^3.0.0",
|
||||||
|
"postcss-selector-parser": "7.1.0"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.4.0",
|
"@ant-design/icons": "^5.4.0",
|
||||||
"@dnd-kit/core": "^6.1.0",
|
"@dnd-kit/core": "^6.1.0",
|
||||||
@@ -22,6 +26,7 @@
|
|||||||
"fabric": "^6.1.0",
|
"fabric": "^6.1.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"postcss-cli": "^11.0.0",
|
"postcss-cli": "^11.0.0",
|
||||||
"re-resizable": "^6.9.17",
|
"re-resizable": "^6.9.17",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
@@ -34,9 +39,9 @@
|
|||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "env-cmd -e development cross-env NODE_ENV=development webpack serve",
|
||||||
"build": "GENERATE_SOURCEMAP=false react-scripts build",
|
"build": "NODE_ENV=production webpack",
|
||||||
"build:local": "GENERATE_SOURCEMAP=false env-cmd -e production react-scripts build",
|
"build:local": "env-cmd -e production cross-env NODE_ENV=production webpack",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"start:tailwind": "cross-env NODE_ENV=development tailwindcss --postcss -i ./landingpages/tailwind/tailwind.css -o ./landingpages/tailwind/tailwind-runtime.css -w",
|
"start:tailwind": "cross-env NODE_ENV=development tailwindcss --postcss -i ./landingpages/tailwind/tailwind.css -o ./landingpages/tailwind/tailwind-runtime.css -w",
|
||||||
@@ -62,8 +67,29 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ajv": "^7.2.4",
|
"@babel/core": "^7.26.10",
|
||||||
|
"@babel/preset-env": "^7.26.9",
|
||||||
|
"@babel/preset-react": "^7.26.3",
|
||||||
|
"@svgr/webpack": "^8.1.0",
|
||||||
|
"@types/react": "^18.3.20",
|
||||||
|
"ajv": "^8.17.1",
|
||||||
|
"babel-loader": "^10.0.0",
|
||||||
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
|
"copy-webpack-plugin": "^13.0.0",
|
||||||
|
"css-loader": "^7.1.2",
|
||||||
|
"css-minimizer-webpack-plugin": "^7.0.2",
|
||||||
"docsify-cli": "^4.4.4",
|
"docsify-cli": "^4.4.4",
|
||||||
"typescript": "^4.9.5"
|
"file-loader": "^6.2.0",
|
||||||
|
"html-webpack-plugin": "^5.6.3",
|
||||||
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
|
"raw-loader": "^4.0.2",
|
||||||
|
"react-app-rewired": "^2.2.1",
|
||||||
|
"sass-loader": "^16.0.5",
|
||||||
|
"style-loader": "^4.0.0",
|
||||||
|
"terser-webpack-plugin": "^5.3.14",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"webpack": "^5.98.0",
|
||||||
|
"webpack-cli": "^6.0.1",
|
||||||
|
"webpack-dev-server": "^5.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,29 +2,31 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="A python GUI builder. Create tkinter, Customtk, Kivy and PySide using GUI builder"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="logo192.png" />
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
-->
|
-->
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" integrity="sha512-dPXYcDub/aeb08c63jRq/k6GaKccl256JQy/AnOq7CAnEZ9FzSL9wSbcZkMp4R26vBsMLFYH4kQ67/bbV8XaCQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" integrity="sha512-dPXYcDub/aeb08c63jRq/k6GaKccl256JQy/AnOq7CAnEZ9FzSL9wSbcZkMp4R26vBsMLFYH4kQ67/bbV8XaCQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
|
|
||||||
|
<script async src="https://tally.so/widgets/embed.js"></script>
|
||||||
|
|
||||||
<!-- Google tag (gtag.js) -->
|
<!-- Google tag (gtag.js) -->
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=%REACT_APP_ANALYTICS_SCRIPT_ID%"></script>
|
<script async src="https://www.googletagmanager.com/gtag/js?id=<%= REACT_APP_ANALYTICS_SCRIPT_ID %>"></script>
|
||||||
<script>
|
<script>
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
function gtag(){dataLayer.push(arguments);}
|
function gtag(){dataLayer.push(arguments);}
|
||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
|
|
||||||
gtag('config', '%REACT_APP_ANALYTICS_SCRIPT_ID%');
|
gtag('config', '<%= REACT_APP_ANALYTICS_SCRIPT_ID %>');
|
||||||
</script>
|
</script>
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
@@ -42,5 +44,6 @@
|
|||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<!-- Author: Paul Github: PaulleDemon -->
|
<!-- Author: Paul Github: PaulleDemon -->
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"short_name": "React App",
|
"short_name": "PyUiBuilder",
|
||||||
"name": "Create React App Sample",
|
"name": "PyUiBuilder - Python GUI Builder",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
|
|||||||
27
roadmap.md
@@ -9,21 +9,21 @@ Any feature that has 👑 beside it, is meant only for [premium users](./README.
|
|||||||
### 1.2.0
|
### 1.2.0
|
||||||
- [ ] UI fixes and enhancement
|
- [ ] UI fixes and enhancement
|
||||||
- [ ] Documentation
|
- [ ] Documentation
|
||||||
- [ ] Tree view for elements on the canvas
|
- [X] Tree view for elements on the canvas
|
||||||
- [ ] Add text editor to support event handlers
|
- [X] Add text editor to support event handlers
|
||||||
- [ ] Support for Event handlers
|
- [X] Support for Event handlers
|
||||||
- [ ] Support more pre-built widgets such as ttk.Notebook, multi-page etc.
|
- [X] Support more pre-built widgets such as ttk.Notebook, multi-page etc.
|
||||||
- [ ] Rewrite DND for better feedback - (swappy/react-dnd-kit/ GSAP draggable)
|
- [ ] Rewrite DND for better feedback - (swappy/react-dnd-kit/ GSAP draggable) - least priority
|
||||||
- [ ] Duplicate widgets
|
- [ ] Duplicate widgets
|
||||||
|
|
||||||
### 1.5.0
|
### 1.5.0
|
||||||
- [ ] Add canvas support tools (lines, rect etc) (try fabricjs)
|
- [ ] Add canvas support tools (lines, rect etc) (try fabricjs)
|
||||||
- [ ] Initial version for Electron App exe 👑
|
- [X] Initial version for Electron App exe 👑
|
||||||
- [ ] Save files locally 👑
|
- [X] Save files locally 👑
|
||||||
- [ ] Load UI files 👑
|
- [X] Load UI files 👑
|
||||||
- [ ] Light/Dark theme 👑
|
- [X] Light/Dark theme 👑
|
||||||
- [ ] Run the preview 👑
|
- [X] Run the preview 👑
|
||||||
- [ ] Load templates 👑
|
- [X] Load templates 👑
|
||||||
|
|
||||||
### 2.0.0
|
### 2.0.0
|
||||||
- [ ] Support for more third party plugins
|
- [ ] Support for more third party plugins
|
||||||
@@ -31,11 +31,8 @@ Any feature that has 👑 beside it, is meant only for [premium users](./README.
|
|||||||
- [ ] Allow creating components
|
- [ ] Allow creating components
|
||||||
- [ ] Support for Kivy
|
- [ ] Support for Kivy
|
||||||
- [ ] Sharable Templates
|
- [ ] Sharable Templates
|
||||||
- [ ] Dark theme 👑
|
|
||||||
|
|
||||||
|
|
||||||
### 3.0.0
|
### 3.0.0
|
||||||
- [ ] Allow 3rd party UI plugin developers
|
- [ ] Allow 3rd party UI plugin developers
|
||||||
- [ ] Allow Templates to be sharable
|
- [ ] Allow Templates to be sharable
|
||||||
- [ ] Node based System (eg: like blender/ unity node system)
|
- [ ] Support for PySide / PyQt 👑 (commercial license only)
|
||||||
- [ ] Support for PySide / PyQt 👑 (commercial license only)
|
|
||||||
|
|||||||
91
src/App.js
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import { LayoutFilled, ProductFilled, CloudUploadOutlined, DatabaseFilled } from "@ant-design/icons"
|
import { LayoutFilled, ProductFilled, CloudUploadOutlined, DatabaseFilled, AlignLeftOutlined } from "@ant-design/icons"
|
||||||
// import { DndContext, useSensors, useSensor, PointerSensor, closestCorners, DragOverlay, rectIntersection } from '@dnd-kit/core'
|
// import { DndContext, useSensors, useSensor, PointerSensor, closestCorners, DragOverlay, rectIntersection } from '@dnd-kit/core'
|
||||||
// import { snapCenterToCursor } from '@dnd-kit/modifiers'
|
// import { snapCenterToCursor } from '@dnd-kit/modifiers'
|
||||||
|
|
||||||
@@ -31,6 +31,10 @@ import generateTkinterCode from './frameworks/tkinter/engine/code'
|
|||||||
|
|
||||||
import TkMainWindow from './frameworks/tkinter/widgets/mainWindow'
|
import TkMainWindow from './frameworks/tkinter/widgets/mainWindow'
|
||||||
import CTkMainWindow from './frameworks/customtk/widgets/mainWindow'
|
import CTkMainWindow from './frameworks/customtk/widgets/mainWindow'
|
||||||
|
import TreeviewContainer from './sidebar/treeviewContainer'
|
||||||
|
import { WidgetContextProvider } from './canvas/context/widgetContext'
|
||||||
|
import { isChromium } from './utils/system'
|
||||||
|
import { Modal } from 'antd'
|
||||||
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -42,6 +46,8 @@ function App() {
|
|||||||
|
|
||||||
const [projectName, setProjectName] = useState('untitled project')
|
const [projectName, setProjectName] = useState('untitled project')
|
||||||
const [UIFramework, setUIFramework] = useState(FrameWorks.TKINTER)
|
const [UIFramework, setUIFramework] = useState(FrameWorks.TKINTER)
|
||||||
|
|
||||||
|
const [shownNotChromiumAlert, setShownNotChromiumAlert] = useState(false) // if the user isn't using a chromium based browser alerts the user
|
||||||
|
|
||||||
// const [uploadedAssets, setUploadedAssets] = useState([]) // a global storage for assets, since redux can't store files(serialize files)
|
// const [uploadedAssets, setUploadedAssets] = useState([]) // a global storage for assets, since redux can't store files(serialize files)
|
||||||
|
|
||||||
@@ -53,6 +59,28 @@ function App() {
|
|||||||
// NOTE: the below reference is no longer required
|
// NOTE: the below reference is no longer required
|
||||||
const [canvasWidgets, setCanvasWidgets] = useState([]) // contains the reference to the widgets inside the canvas
|
const [canvasWidgets, setCanvasWidgets] = useState([]) // contains the reference to the widgets inside the canvas
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (!shownNotChromiumAlert){
|
||||||
|
// this modal may rerender twice only in dev mode because of how react works
|
||||||
|
isChromium().then((isChrome) => {
|
||||||
|
|
||||||
|
if (!isChrome){
|
||||||
|
Modal.warning({
|
||||||
|
title: "Use Chromium browser",
|
||||||
|
onOk: () => setShownNotChromiumAlert(true),
|
||||||
|
content: (<span>We recommend using Chromium based browser such as Chrome, Brave, Edge etc for best results.
|
||||||
|
<br />
|
||||||
|
Join us on
|
||||||
|
<a href="https://discord.gg/dHXjrrCA7G" target='_blank' rel='noreferrer noopener'> Discord</a> for help and updates</span>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setShownNotChromiumAlert(true)
|
||||||
|
|
||||||
|
}, [shownNotChromiumAlert])
|
||||||
|
|
||||||
const sidebarTabs = [
|
const sidebarTabs = [
|
||||||
{
|
{
|
||||||
name: "Widgets",
|
name: "Widgets",
|
||||||
@@ -64,6 +92,11 @@ function App() {
|
|||||||
icon: <ProductFilled />,
|
icon: <ProductFilled />,
|
||||||
content: <PluginsContainer sidebarContent={sidebarPlugins}/>
|
content: <PluginsContainer sidebarContent={sidebarPlugins}/>
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Tree view",
|
||||||
|
icon: <AlignLeftOutlined />,
|
||||||
|
content: <TreeviewContainer />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Uploads",
|
name: "Uploads",
|
||||||
icon: <CloudUploadOutlined />,
|
icon: <CloudUploadOutlined />,
|
||||||
@@ -78,15 +111,59 @@ function App() {
|
|||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
if (!canvasRef)
|
||||||
|
return
|
||||||
|
|
||||||
|
const canvasBoundingBox = canvasRef.current.getCanvasContainerBoundingRect()
|
||||||
|
const canvasCenterX = (canvasBoundingBox.width - canvasBoundingBox.left) / 2
|
||||||
|
const canvasCenterY = (canvasBoundingBox.height - canvasBoundingBox.top) / 2
|
||||||
|
|
||||||
|
// console.log("position: ", TkMainWindow.)
|
||||||
if (UIFramework === FrameWorks.TKINTER){
|
if (UIFramework === FrameWorks.TKINTER){
|
||||||
canvasRef?.current?.createWidget(TkMainWindow)
|
|
||||||
|
const widgetCenterX = (TkMainWindow.initialSize.width - canvasBoundingBox.left) / 2
|
||||||
|
const widgetCenterY = (TkMainWindow.initialSize.height - canvasBoundingBox.top) / 2
|
||||||
|
|
||||||
|
canvasRef?.current?.createWidget(TkMainWindow, {x: canvasCenterX - widgetCenterX, y: canvasCenterY - widgetCenterY}, ({id, widgetRef}) => {
|
||||||
|
|
||||||
|
// center the widget when adding to canvas
|
||||||
|
if (!widgetRef.current){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetBoundingBox = widgetRef.current?.getBoundingRect()
|
||||||
|
const widgetCenterX = (widgetBoundingBox.width - widgetBoundingBox.left) / 2
|
||||||
|
const widgetCenterY = (widgetBoundingBox.height - widgetBoundingBox.top) / 2
|
||||||
|
|
||||||
|
|
||||||
|
// widgetRef.current?.setPos(canvasCenterX-widgetCenterX, canvasCenterY-widgetCenterY)
|
||||||
|
})
|
||||||
|
|
||||||
}else if (UIFramework === FrameWorks.CUSTOMTK){
|
}else if (UIFramework === FrameWorks.CUSTOMTK){
|
||||||
canvasRef?.current?.createWidget(CTkMainWindow)
|
|
||||||
|
const widgetCenterX = (TkMainWindow.initialSize.width - canvasBoundingBox.left) / 2
|
||||||
|
const widgetCenterY = (TkMainWindow.initialSize.height - canvasBoundingBox.top) / 2
|
||||||
|
|
||||||
|
|
||||||
|
canvasRef?.current?.createWidget(CTkMainWindow, {x: canvasCenterX - widgetCenterX, y: canvasCenterY - widgetCenterY}, ({id, widgetRef}) => {
|
||||||
|
|
||||||
|
// center the widget when adding to canvas
|
||||||
|
if (!widgetRef.current){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetBoundingBox = widgetRef.current?.getBoundingRect()
|
||||||
|
const widgetCenterX = (widgetBoundingBox.width - widgetBoundingBox.left) / 2
|
||||||
|
const widgetCenterY = (widgetBoundingBox.height - widgetBoundingBox.top) / 2
|
||||||
|
|
||||||
|
|
||||||
|
// widgetRef.current?.setPos(canvasCenterX-widgetCenterX, canvasCenterY-widgetCenterY)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}, [UIFramework])
|
}, [UIFramework, canvasRef])
|
||||||
|
|
||||||
// const handleDragStart = (event) => {
|
// const handleDragStart = (event) => {
|
||||||
// console.log("Drag start: ", event)
|
// console.log("Drag start: ", event)
|
||||||
@@ -207,17 +284,21 @@ function App() {
|
|||||||
onOk={handleOk} okType={okButtonType} onCancel={handleCancel}>
|
onOk={handleOk} okType={okButtonType} onCancel={handleCancel}>
|
||||||
<p>Are you sure you want to change the framework? This will clear the canvas.</p>
|
<p>Are you sure you want to change the framework? This will clear the canvas.</p>
|
||||||
</Modal> */}
|
</Modal> */}
|
||||||
|
|
||||||
|
|
||||||
|
<WidgetContextProvider>
|
||||||
<DragProvider>
|
<DragProvider>
|
||||||
<div className="tw-w-full tw-h-[94vh] tw-flex">
|
<div className="tw-w-full tw-h-[94vh] tw-flex">
|
||||||
<Sidebar tabs={sidebarTabs}/>
|
<Sidebar tabs={sidebarTabs}/>
|
||||||
|
|
||||||
{/* <ActiveWidgetProvider> */}
|
{/* <ActiveWidgetProvider> */}
|
||||||
<Canvas ref={canvasRef} widgets={canvasWidgets} onWidgetAdded={handleWidgetAddedToCanvas}/>
|
<Canvas ref={canvasRef} widgets={canvasWidgets}
|
||||||
|
/>
|
||||||
{/* </ActiveWidgetProvider> */}
|
{/* </ActiveWidgetProvider> */}
|
||||||
</div>
|
</div>
|
||||||
{/* dragOverlay (dnd-kit) helps move items from one container to another */}
|
{/* dragOverlay (dnd-kit) helps move items from one container to another */}
|
||||||
</DragProvider>
|
</DragProvider>
|
||||||
|
</WidgetContextProvider>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ some parts under a different non-commercial use license.
|
|||||||
|
|
||||||
-----------------------
|
-----------------------
|
||||||
License in short
|
License in short
|
||||||
* You are free fork, modify and redistribute it given the original or derived works is kept free and open-source and under the same licese.
|
* You are free fork, modify and redistribute it given the original or derived works is kept free and source-available and under the same license.
|
||||||
* Another restriction is you can't redistribute in binary or executable formats without asking me first.
|
* Another restriction is you can't redistribute in binary or executable formats without asking me first.
|
||||||
* If you plan on making money on this work or derived work you need permission first.
|
* If you plan on making money on this work or derived work you need permission first.
|
||||||
|
* No AI Training on this source code
|
||||||
|
|
||||||
License was planned to be kept under one AGPL or less restrictive license, but since its difficult for me to continue providing free and open-source software without proper funding. It's under this license.
|
License was planned to be kept under one AGPL or less restrictive license, but since its difficult for me to continue providing free and source-available software without proper funding. It's under this license.
|
||||||
|
|
||||||
Once there enough money to fund this project and my upcoming FOSS projects, The license will be brought back to a single AGPL or much lesser restrictive license. The fastest way to make this happen is by purchasing a one-time license or fund me.
|
|
||||||
|
|
||||||
Read the full license below.
|
Read the full license below.
|
||||||
--------------------
|
--------------------
|
||||||
@@ -24,7 +24,7 @@ Read the full license below.
|
|||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, publish, and distribute the source code, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, publish, and distribute the source code, subject to the following conditions:
|
||||||
|
|
||||||
2. Derived Work License
|
2. Derived Work License
|
||||||
Any modifications, derivative works, or copies of the Software must retain this license. All derivative works must be open-sourced under the same license terms. You are not permitted to relicense or sub-license any portion of the Software under a different license without explicit prior written permission from the original author.
|
Any modifications, derivative works, or copies of the Software must retain this license. All derivative works must be source-available under the same license terms. You are not permitted to relicense or sub-license any portion of the Software under a different license without explicit prior written permission from the original author.
|
||||||
|
|
||||||
3. Distribution Restriction on Executables
|
3. Distribution Restriction on Executables
|
||||||
You are not permitted to distribute the Software in compiled, executable, or binary form without prior written consent from the original author. This applies to both original and derivative works.
|
You are not permitted to distribute the Software in compiled, executable, or binary form without prior written consent from the original author. This applies to both original and derivative works.
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const Tools = {
|
|||||||
|
|
||||||
LAYOUT_MANAGER: "layout_manager",
|
LAYOUT_MANAGER: "layout_manager",
|
||||||
UPLOADED_LIST: "uploaded_list",
|
UPLOADED_LIST: "uploaded_list",
|
||||||
|
|
||||||
|
CUSTOM: "Custom"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
18
src/canvas/context/selectedWidgetContext.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React, { createContext, useContext, useState } from 'react';
|
||||||
|
|
||||||
|
const selectedWidgetContext = createContext()
|
||||||
|
|
||||||
|
export const useSelectedWidgetContext = () => useContext(selectedWidgetContext)
|
||||||
|
|
||||||
|
// NOTE: not in use
|
||||||
|
export const SelectedWidgetProvider = ({ children }) => {
|
||||||
|
const [selectedWidget, setSelectedWidget] = useState(null)
|
||||||
|
|
||||||
|
// const []
|
||||||
|
|
||||||
|
return (
|
||||||
|
<selectedWidgetContext.Provider value={{ selectedWidget, setSelectedWidget }}>
|
||||||
|
{children}
|
||||||
|
</selectedWidgetContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,35 +1,23 @@
|
|||||||
import React, { createContext, Component } from 'react'
|
import React, { createContext, useContext, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
const WidgetContext = createContext()
|
export const WidgetContext = createContext()
|
||||||
|
|
||||||
// NOTE: Don't use this context provider
|
export const useWidgetContext = () => useContext(WidgetContext)
|
||||||
|
|
||||||
class WidgetProvider extends Component {
|
|
||||||
state = {
|
|
||||||
activeWidget: null, // Keeps track of the active widget's data
|
|
||||||
widgetMethods: null, // Function to update active widget's state
|
|
||||||
}
|
|
||||||
|
|
||||||
setActiveWidget = (widgetData, widgetMethods) => {
|
export const WidgetContextProvider = ({ children }) => {
|
||||||
this.setState({
|
|
||||||
activeWidget: widgetData,
|
const [activeWidget, setActiveWidget] = useState(null)
|
||||||
widgetMethods: widgetMethods, // Store the update function of the active widget
|
const [widgets, setWidgets] = useState([]) // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass, children: [], parent: "", initialData: {}}]
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
// don't useState here because the refs are changing often
|
||||||
return (
|
const widgetRefs = useRef({}) // stores the actual refs to the widgets inside the canvas {id: ref, id2, ref2...}
|
||||||
<WidgetContext.Provider
|
|
||||||
value={{
|
|
||||||
activeWidget: this.state.activeWidget,
|
return (
|
||||||
setActiveWidget: this.setActiveWidget,
|
<WidgetContext.Provider value={{ widgets, setWidgets, widgetRefs,
|
||||||
widgetMethods: this.state.widgetMethods, // Expose the update function
|
activeWidget, setActiveWidget }}>
|
||||||
}}
|
{children}
|
||||||
>
|
</WidgetContext.Provider>
|
||||||
{this.props.children}
|
)
|
||||||
</WidgetContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { WidgetContext, WidgetProvider }
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const ResizeWidgetContainer = ({selectedWidget, onResize}) => {
|
|||||||
const [pos, setPos] = useState({x: 0, y: 0})
|
const [pos, setPos] = useState({x: 0, y: 0})
|
||||||
const [size, setSize] = useState({width: 0, height: 0})
|
const [size, setSize] = useState({width: 0, height: 0})
|
||||||
|
|
||||||
|
console.log("selected widget: ", selectedWidget)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
if (selectedWidget){
|
if (selectedWidget){
|
||||||
@@ -25,15 +27,15 @@ const ResizeWidgetContainer = ({selectedWidget, onResize}) => {
|
|||||||
|
|
||||||
console.log("selected widget resizable: ", selectedWidget)
|
console.log("selected widget resizable: ", selectedWidget)
|
||||||
|
|
||||||
}, [selectedWidget, selectedWidget?.getPos(), selectedWidget?.getSize()])
|
}, [selectedWidget])
|
||||||
|
|
||||||
|
|
||||||
|
console.log("pos: ", pos, size)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`tw-absolute tw-bg-transparent tw-top-[-20px] tw-left-[-20px] tw-opacity-100
|
<div className={`tw-absolute tw-opacity-100
|
||||||
tw-w-full tw-h-full tw-z-[-1] tw-border-2 tw-border-solid tw-border-blue-500`}
|
tw-w-full tw-h-full tw-z-[2] tw-border-2 tw-border-solid tw-border-blue-500 tw-bg-red-300`}
|
||||||
style={{
|
style={{
|
||||||
top: `${pos.y - 40}px`,
|
top: `${pos.y - 20}px`,
|
||||||
left: `${pos.x - 20}px`,
|
left: `${pos.x - 20}px`,
|
||||||
width: `${size.width + 40}px`,
|
width: `${size.width + 40}px`,
|
||||||
height: `${size.height + 40}px`,
|
height: `${size.height + 40}px`,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { memo, useEffect, useMemo, useState } from "react"
|
import { memo, useEffect, useMemo, useRef, useState } from "react"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Checkbox, ColorPicker, Input,
|
Checkbox, ColorPicker, Input,
|
||||||
@@ -12,26 +12,67 @@ import { Layouts } from "./constants/layouts.js"
|
|||||||
import { DynamicRadioInputList } from "../components/inputs.js"
|
import { DynamicRadioInputList } from "../components/inputs.js"
|
||||||
import { useFileUploadContext } from "../contexts/fileUploadContext.js"
|
import { useFileUploadContext } from "../contexts/fileUploadContext.js"
|
||||||
import { AudioOutlined, FileImageOutlined, FileTextOutlined, VideoCameraOutlined } from "@ant-design/icons"
|
import { AudioOutlined, FileImageOutlined, FileTextOutlined, VideoCameraOutlined } from "@ant-design/icons"
|
||||||
|
import { useWidgetContext } from "./context/widgetContext.js"
|
||||||
|
|
||||||
|
|
||||||
// FIXME: Maximum recursion error
|
|
||||||
// FIXME: Every time the parent attrs are changed a remount happens, which causes input cursor to go to the end
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {boolean} isOpen
|
* @param {boolean} isOpen
|
||||||
* @param {string} widgetType
|
* @param {string} widgetType
|
||||||
* @param {object} attrs - widget attributes
|
* @param {object} attrs - widget attributes
|
||||||
*/
|
*/
|
||||||
const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
const CanvasToolBar = memo(({ isOpen, widgetType, }) => {
|
||||||
|
|
||||||
// const { activeWidgetAttrs } = useActiveWidget()
|
// const { activeWidgetAttrs } = useActiveWidget()
|
||||||
|
|
||||||
|
const {activeWidget} = useWidgetContext()
|
||||||
|
|
||||||
// console.log("active widget context: ", activeWidgetAttrs)
|
// console.log("active widget context: ", activeWidgetAttrs)
|
||||||
const [toolbarOpen, setToolbarOpen] = useState(isOpen)
|
const [toolbarOpen, setToolbarOpen] = useState(isOpen)
|
||||||
const [toolbarAttrs, setToolbarAttrs] = useState(attrs)
|
const [toolbarAttrs, setToolbarAttrs] = useState({})
|
||||||
|
|
||||||
|
const focusedInputRef = useRef()
|
||||||
|
|
||||||
|
const [cursorPos, setCursorPos] = useState(0) // store cursor position for focused input so during remount if the cursor position goes to end we can use this
|
||||||
|
const [activeInputKey, setActiveInputKey] = useState(null)
|
||||||
|
|
||||||
const {uploadedAssets} = useFileUploadContext()
|
const {uploadedAssets} = useFileUploadContext()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const stateUpdatedCallback = () => {
|
||||||
|
setToolbarAttrs(activeWidget.getToolbarAttrs())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeWidget){
|
||||||
|
activeWidget.stateChangeSubscriberCallback(stateUpdatedCallback)
|
||||||
|
// console.log("sytate update: ", activeWidget.getToolbarAttrs())
|
||||||
|
setToolbarAttrs(activeWidget.getToolbarAttrs())
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}, [activeWidget, focusedInputRef, cursorPos]) // , activeWidget?.state
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setToolbarOpen(isOpen)
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (focusedInputRef.current?.input && activeInputKey) {
|
||||||
|
// this fixes the cursor going to the end issue during remount
|
||||||
|
focusedInputRef.current.setSelectionRange(cursorPos, cursorPos)
|
||||||
|
}
|
||||||
|
}, [toolbarAttrs, focusedInputRef, cursorPos])
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
|
||||||
|
// setToolbarAttrs(activeWidget.getToolbarAttrs())
|
||||||
|
|
||||||
|
// }, [])
|
||||||
|
|
||||||
const uploadItems = useMemo(() => {
|
const uploadItems = useMemo(() => {
|
||||||
|
|
||||||
const returnComponentBasedOnFileType = (file) => {
|
const returnComponentBasedOnFileType = (file) => {
|
||||||
@@ -81,19 +122,30 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
|
|
||||||
}, [uploadedAssets])
|
}, [uploadedAssets])
|
||||||
|
|
||||||
useEffect(() => {
|
const handleInputFocus = (key, event) => {
|
||||||
setToolbarOpen(isOpen)
|
|
||||||
}, [isOpen])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const {value, target} = event
|
||||||
setToolbarAttrs(attrs)
|
|
||||||
}, [attrs])
|
|
||||||
|
|
||||||
|
setActiveInputKey(key)
|
||||||
|
setCursorPos(target.selectionStart)
|
||||||
|
focusedInputRef.current = event.target
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInputBlur = () => {
|
||||||
|
setActiveInputKey(null);
|
||||||
|
focusedInputRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
const handleChange = (value, callback) => {
|
const handleChange = (value, callback) => {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(value)
|
callback(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (focusedInputRef.current?.input) {
|
||||||
|
setCursorPos(focusedInputRef.current.input.selectionStart)
|
||||||
|
}else{
|
||||||
|
setCursorPos(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUploadFileFromName(name){
|
function getUploadFileFromName(name){
|
||||||
@@ -119,6 +171,7 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
placeholder="select content"
|
placeholder="select content"
|
||||||
showSearch
|
showSearch
|
||||||
className="tw-w-full"
|
className="tw-w-full"
|
||||||
|
notFoundContent="Start uploading from uploads tab"
|
||||||
value={val.value?.name || ""}
|
value={val.value?.name || ""}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
|
||||||
@@ -165,7 +218,7 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
onChange={(value) => handleChange({ ...val.value, direction: value }, val.onChange)}
|
onChange={(value) => handleChange({ ...val.value, direction: value }, val.onChange)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-flex tw-flex-col tw-gap-1">
|
{/* <div className="tw-flex tw-flex-col tw-gap-1">
|
||||||
<span className="tw-text-sm">Align items</span>
|
<span className="tw-text-sm">Align items</span>
|
||||||
<Select
|
<Select
|
||||||
options={[
|
options={[
|
||||||
@@ -178,7 +231,7 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
placeholder={`${val.label}`}
|
placeholder={`${val.label}`}
|
||||||
onChange={(value) => handleChange({ ...val.value, align: value }, val.onChange)}
|
onChange={(value) => handleChange({ ...val.value, align: value }, val.onChange)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
<div className="tw-flex tw-flex-col">
|
<div className="tw-flex tw-flex-col">
|
||||||
<span className="tw-text-sm">Gap</span>
|
<span className="tw-text-sm">Gap</span>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
@@ -186,6 +239,9 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
min={1}
|
min={1}
|
||||||
value={val.value?.gap || 10}
|
value={val.value?.gap || 10}
|
||||||
size="small"
|
size="small"
|
||||||
|
onFocus={(e) => handleInputFocus(val.label, e)}
|
||||||
|
onBlur={handleInputBlur}
|
||||||
|
ref={activeInputKey === val.label ? focusedInputRef : null}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
handleChange({ ...val.value, gap: value }, val.onChange)
|
handleChange({ ...val.value, gap: value }, val.onChange)
|
||||||
}}
|
}}
|
||||||
@@ -235,6 +291,18 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const renderCustomTool = (val) => {
|
||||||
|
return (
|
||||||
|
// NOTE: custom components must accept value and onChange
|
||||||
|
<val.Component
|
||||||
|
{...val.toolProps}
|
||||||
|
value={val.value}
|
||||||
|
onChange={(value) => handleChange(value, val.onChange)}>
|
||||||
|
|
||||||
|
</val.Component>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const renderTool = (keyName, val) => {
|
const renderTool = (keyName, val) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -242,6 +310,10 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
{val.tool === Tools.INPUT && (
|
{val.tool === Tools.INPUT && (
|
||||||
<Input
|
<Input
|
||||||
{...val.toolProps}
|
{...val.toolProps}
|
||||||
|
// value={val.value}
|
||||||
|
onFocus={(event) => handleInputFocus(val.label, event)}
|
||||||
|
onBlur={handleInputBlur}
|
||||||
|
ref={activeInputKey === val.label ? focusedInputRef : null}
|
||||||
value={val.value}
|
value={val.value}
|
||||||
onChange={(e) => handleChange(e.target.value, val.onChange)}
|
onChange={(e) => handleChange(e.target.value, val.onChange)}
|
||||||
/>
|
/>
|
||||||
@@ -252,6 +324,9 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
{...val.toolProps}
|
{...val.toolProps}
|
||||||
value={val.value || 0}
|
value={val.value || 0}
|
||||||
size="small"
|
size="small"
|
||||||
|
onFocus={(event) => handleInputFocus(val.label, event)}
|
||||||
|
onBlur={handleInputBlur}
|
||||||
|
ref={activeInputKey === val.label ? focusedInputRef : null}
|
||||||
onChange={(value) => handleChange(value, val.onChange)}
|
onChange={(value) => handleChange(value, val.onChange)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -277,7 +352,7 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
showSearch
|
showSearch
|
||||||
value={val.value || ""}
|
value={val.value || ""}
|
||||||
placeholder={`${val.label}`}
|
placeholder={`${val.label}`}
|
||||||
className="tw-w-full"
|
className="tw-w-full tw-min-w-[80px]"
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||||
}
|
}
|
||||||
@@ -294,6 +369,7 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{val.tool === Tools.INPUT_RADIO_LIST && (
|
{val.tool === Tools.INPUT_RADIO_LIST && (
|
||||||
|
// FIXME: problem with maximum recursion error
|
||||||
<DynamicRadioInputList
|
<DynamicRadioInputList
|
||||||
defaultInputs={val.value.inputs}
|
defaultInputs={val.value.inputs}
|
||||||
defaultSelected={val.value.selectedRadio}
|
defaultSelected={val.value.selectedRadio}
|
||||||
@@ -312,6 +388,12 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
val.tool === Tools.CUSTOM && (
|
||||||
|
renderCustomTool(val)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -319,10 +401,12 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
|
|
||||||
|
|
||||||
const renderToolbar = (obj, parentKey = "", toolCount=0) => {
|
const renderToolbar = (obj, parentKey = "", toolCount=0) => {
|
||||||
|
// console.log("obj: ", obj)
|
||||||
const keys = []
|
const keys = []
|
||||||
|
|
||||||
return Object.entries(obj).map(([key, val], i) => {
|
return Object.entries(obj).map(([key, val], i) => {
|
||||||
const keyName = parentKey ? `${parentKey}.${key}` : key
|
const keyName = parentKey ? `${parentKey}.${key}` : key
|
||||||
|
// console.log("obj2: ", key, val)
|
||||||
|
|
||||||
// Highlight outer labels in blue for first-level keys
|
// Highlight outer labels in blue for first-level keys
|
||||||
const isFirstLevel = parentKey === ""
|
const isFirstLevel = parentKey === ""
|
||||||
@@ -331,6 +415,10 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
? "tw-text-sm tw-text-black tw-font-medium"
|
? "tw-text-sm tw-text-black tw-font-medium"
|
||||||
: "tw-text-sm"
|
: "tw-text-sm"
|
||||||
|
|
||||||
|
if (!val?.label){
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// Render tool widgets
|
// Render tool widgets
|
||||||
if (typeof val === "object" && val.tool) {
|
if (typeof val === "object" && val.tool) {
|
||||||
|
|
||||||
@@ -338,7 +426,7 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
|
|
||||||
if (isFirstLevel){
|
if (isFirstLevel){
|
||||||
return (
|
return (
|
||||||
<Collapse key={keyName} ghost defaultActiveKey={keys}>
|
<Collapse key={keyName} defaultActiveKey={keys}>
|
||||||
<Collapse.Panel header={val.label} key={keyName}>
|
<Collapse.Panel header={val.label} key={keyName}>
|
||||||
{renderTool(keyName, val)}
|
{renderTool(keyName, val)}
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
@@ -367,7 +455,7 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
|
|
||||||
if (isFirstLevel){
|
if (isFirstLevel){
|
||||||
return (
|
return (
|
||||||
<Collapse key={keyName} ghost defaultActiveKey={keys}>
|
<Collapse key={keyName} defaultActiveKey={keys}>
|
||||||
<Collapse.Panel header={val.label} key={keyName}>
|
<Collapse.Panel header={val.label} key={keyName}>
|
||||||
<div className={`${containerClass} tw-px-2`}>
|
<div className={`${containerClass} tw-px-2`}>
|
||||||
{renderToolbar(val, keyName, toolCount+1)}
|
{renderToolbar(val, keyName, toolCount+1)}
|
||||||
@@ -394,18 +482,20 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`tw-absolute tw-top-20 tw-right-5 tw-bg-white
|
className={`tw-absolute tw-top-10 tw-right-5 tw-bg-white
|
||||||
${toolbarOpen ? "tw-translate-x-0" : "tw-translate-x-full"}
|
${toolbarOpen ? "tw-translate-x-0" : "tw-translate-x-full"}
|
||||||
tw-w-[280px] tw-px-3 tw-p-2 tw-h-[600px] tw-rounded-md tw-z-[1000] tw-shadow-lg
|
tw-w-[280px] tw-px-3 tw-p-2 tw-h-[80vh] tw-rounded-md tw-z-[1000] tw-shadow-lg
|
||||||
tw-transition-transform tw-duration-[0.3s] tw-overflow-x-hidden
|
tw-transition-transform tw-duration-[0.3s] tw-overflow-x-hidden
|
||||||
tw-flex tw-flex-col tw-gap-2 tw-overflow-y-auto`}
|
tw-flex tw-flex-col tw-gap-2 tw-overflow-y-auto tw-border-[2px]
|
||||||
|
tw-border-solid tw-border-gray-300`}
|
||||||
|
|
||||||
style={{
|
style={{
|
||||||
transform: toolbarOpen ? "translateX(0)" : "translateX(calc(100% + 50px))"
|
transform: toolbarOpen ? "translateX(0)" : "translateX(calc(100% + 50px))"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h3 className="tw-text-lg tw-text-center">
|
<h3 className="tw-text-lg tw-text-center tw-bg-[#FAFAFA] tw-border-[1px] tw-border-solid tw-border-[#D9D9D9]
|
||||||
{capitalize(`${widgetType || ""}`).replace(/_/g, " ")}
|
tw-p-1 tw-px-2 tw-rounded-md tw-font-medium">
|
||||||
|
{capitalize(`${activeWidget?.getDisplayName() || ""}`).replace(/_/g, " ")}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="tw-flex tw-flex-col tw-gap-2">
|
<div className="tw-flex tw-flex-col tw-gap-2">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useDragWidgetContext } from "./draggableWidgetContext"
|
|||||||
import { useDragContext } from "../../components/draggable/draggableContext"
|
import { useDragContext } from "../../components/draggable/draggableContext"
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: not in use
|
||||||
// FIXME: sometimes even after drag end the showDroppable is visible
|
// FIXME: sometimes even after drag end the showDroppable is visible
|
||||||
/**
|
/**
|
||||||
* @param {} - widgetRef - the widget ref for your widget
|
* @param {} - widgetRef - the widget ref for your widget
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import { useEffect, useMemo, useRef } from "react"
|
import { memo, useEffect, useMemo, useRef, useState } from "react"
|
||||||
import Draggable from "./utils/draggableDnd"
|
import Draggable from "./utils/draggableDnd"
|
||||||
|
|
||||||
|
import { Button } from "antd"
|
||||||
import { GithubOutlined, GitlabOutlined, LinkOutlined,
|
import { GithubOutlined, GitlabOutlined, LinkOutlined,
|
||||||
AudioOutlined, FileTextOutlined,
|
AudioOutlined, FileTextOutlined,
|
||||||
DeleteFilled,
|
DeleteFilled,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
GlobalOutlined} from "@ant-design/icons"
|
GlobalOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
EyeInvisibleOutlined} from "@ant-design/icons"
|
||||||
|
|
||||||
import DraggableWrapper from "./draggable/draggable"
|
import DraggableWrapper from "./draggable/draggable"
|
||||||
import { Button } from "antd"
|
import { useWidgetContext } from "../canvas/context/widgetContext"
|
||||||
|
|
||||||
|
|
||||||
export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerRef}){
|
export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerRef}){
|
||||||
@@ -38,7 +42,7 @@ export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerR
|
|||||||
<div ref={innerRef} className="tw-select-none tw-h-[200px] tw-w-[230px] tw-flex tw-flex-col
|
<div ref={innerRef} className="tw-select-none tw-h-[200px] tw-w-[230px] tw-flex tw-flex-col
|
||||||
tw-rounded-md tw-overflow-hidden
|
tw-rounded-md tw-overflow-hidden
|
||||||
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px]
|
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px]
|
||||||
tw-border-blue-500 tw-shadow-md">
|
tw-border-gray-500 tw-shadow-md">
|
||||||
<div className="tw-h-[200px] tw-pointer-events-none tw-w-full tw-overflow-hidden">
|
<div className="tw-h-[200px] tw-pointer-events-none tw-w-full tw-overflow-hidden">
|
||||||
<img src={img} alt={name} className="tw-object-contain tw-h-full tw-w-full tw-select-none" />
|
<img src={img} alt={name} className="tw-object-contain tw-h-full tw-w-full tw-select-none" />
|
||||||
</div>
|
</div>
|
||||||
@@ -148,4 +152,50 @@ export function DraggableAssetCard({file, onDelete}){
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const TreeViewCard = memo(({widgetRef, title, isTopLevel}) => {
|
||||||
|
|
||||||
|
const [widgetVisible, setWidgetVisible] = useState(widgetRef.current.isWidgetVisible)
|
||||||
|
const {activeWidget} = useWidgetContext()
|
||||||
|
|
||||||
|
const onDelete = () => {
|
||||||
|
widgetRef.current.deleteWidget()
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleHideShowWidget = () => {
|
||||||
|
|
||||||
|
setWidgetVisible(!widgetRef.current.isWidgetVisible())
|
||||||
|
|
||||||
|
if (widgetRef.current.isWidgetVisible())
|
||||||
|
widgetRef.current.hideFromViewport()
|
||||||
|
else{
|
||||||
|
widgetRef.current.unHideFromViewport()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSingleClick = () => {
|
||||||
|
activeWidget?.deSelect()
|
||||||
|
widgetRef.current.select()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tw-flex tw-place-items-center tw-px-2 tw-p-1 tw-place-content-between
|
||||||
|
tw-gap-4 tw-w-full" style={{width: "100%"}}
|
||||||
|
onClick={handleSingleClick}
|
||||||
|
onDoubleClick={() => widgetRef?.current.panToWidget()}
|
||||||
|
>
|
||||||
|
<div className={`tw-text-sm ${isTopLevel ? "tw-font-medium" : ""}`}>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tw-ml-auto tw-flex tw-gap-1">
|
||||||
|
<Button color="danger" title="delete" onClick={onDelete} size="small" variant="text" danger
|
||||||
|
icon={<DeleteOutlined />}></Button>
|
||||||
|
<Button variant="text" type="text" title="hide" onClick={toggleHideShowWidget} size="small"
|
||||||
|
icon={widgetVisible ? <EyeOutlined /> : <EyeInvisibleOutlined/>}></Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
42
src/components/draggable/License.txt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
License held by paul
|
||||||
|
Github username: PaulleDemon
|
||||||
|
|
||||||
|
Below is the License for source code of PyUIBuilder. For license regarding source code generated by PyUIBuilder Refer Readme.md file (it has basically no restrictions).
|
||||||
|
|
||||||
|
The source code for PyUIBuilder is Dual licensed, most parts of the code comes under AGPL,
|
||||||
|
some parts under a different non-commercial use license.
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------
|
||||||
|
License in short
|
||||||
|
* You are free fork, modify and redistribute it given the original or derived works is kept free and source-available and under the same license.
|
||||||
|
* Another restriction is you can't redistribute in binary or executable formats without asking me first.
|
||||||
|
* If you plan on making money on this work or derived work you need permission first.
|
||||||
|
* No AI Training on this source code
|
||||||
|
|
||||||
|
License was planned to be kept under one AGPL or less restrictive license, but since its difficult for me to continue providing free and source-available software without proper funding. It's under this license.
|
||||||
|
|
||||||
|
|
||||||
|
Read the full license below.
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
1. License Grant
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, publish, and distribute the source code, subject to the following conditions:
|
||||||
|
|
||||||
|
2. Derived Work License
|
||||||
|
Any modifications, derivative works, or copies of the Software must retain this license. All derivative works must be source-available under the same license terms. You are not permitted to relicense or sub-license any portion of the Software under a different license without explicit prior written permission from the original author.
|
||||||
|
|
||||||
|
3. Distribution Restriction on Executables
|
||||||
|
You are not permitted to distribute the Software in compiled, executable, or binary form without prior written consent from the original author. This applies to both original and derivative works.
|
||||||
|
|
||||||
|
4. Commercial Use Restriction
|
||||||
|
The Software, in its original or modified form, cannot be used for any commercial purpose without prior written permission from the original author. This includes selling, licensing, or offering the Software as part of a service.
|
||||||
|
|
||||||
|
5. Non-Commercial Use
|
||||||
|
You may freely use, copy, modify, and distribute the source code of the Software for non-commercial purposes, provided that the same terms of this license are maintained in all copies or derivative works.
|
||||||
|
|
||||||
|
6. Changes to the License
|
||||||
|
The original author reserves the right to modify, amend, or update this license at any time. Any changes will apply only to future versions of the Software. Any prior versions of the Software, including derivative works, will continue to be governed by the version of the license in effect at the time the work was created.
|
||||||
|
|
||||||
|
7. Disclaimer
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -7,12 +7,25 @@ import { useDragContext } from "./draggableContext"
|
|||||||
* @param {string} - dragElementType - this will set the data-draggable-type which can be accessed on droppable to check if its allowed or not
|
* @param {string} - dragElementType - this will set the data-draggable-type which can be accessed on droppable to check if its allowed or not
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const DraggableWrapper = memo(({dragElementType, dragWidgetClass=null, className, children, ...props}) => {
|
const DraggableWrapper = memo(({dragElementType, dragWidgetClass=null, currentPos, className, children, ...props}) => {
|
||||||
|
|
||||||
const { onDragStart, onDragEnd } = useDragContext()
|
const { onDragStart, onDragEnd, setPosMetaData } = useDragContext()
|
||||||
|
|
||||||
const draggableRef = useRef(null)
|
const draggableRef = useRef(null)
|
||||||
|
|
||||||
|
const setInitialPos = (e) => {
|
||||||
|
const {clientX, clientY} = e
|
||||||
|
|
||||||
|
const draggableRect = draggableRef.current.getBoundingClientRect()
|
||||||
|
|
||||||
|
const posMetaData = {
|
||||||
|
dragStartCursorPos: {x: clientX, y: clientY},
|
||||||
|
initialPos: {x: draggableRect.left, y: draggableRect.top}
|
||||||
|
}
|
||||||
|
|
||||||
|
setPosMetaData(posMetaData)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {DragEvent} event
|
* @param {DragEvent} event
|
||||||
@@ -38,6 +51,8 @@ const DraggableWrapper = memo(({dragElementType, dragWidgetClass=null, className
|
|||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
ref={draggableRef}
|
ref={draggableRef}
|
||||||
|
onPointerDown={setInitialPos}
|
||||||
|
onMouseDown={setInitialPos}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ export const DragProvider = ({ children }) => {
|
|||||||
const [draggedElement, setDraggedElement] = useState(null)
|
const [draggedElement, setDraggedElement] = useState(null)
|
||||||
const [overElement, setOverElement] = useState(null) // the element the dragged items is over
|
const [overElement, setOverElement] = useState(null) // the element the dragged items is over
|
||||||
|
|
||||||
const [widgetClass, setWidgetClass] = useState(null) // helper to help pass the widget type from sidebar to canvas
|
const [posMetaData, setPosMetaData] = useState({dragStartCursorPos: {x: 0, y: 0},
|
||||||
|
initialPos: {x: 0, y: 0}})
|
||||||
|
|
||||||
|
|
||||||
|
const [widgetClass, setWidgetClass] = useState(null) // helper to help pass the widget type from sidebar to canvas
|
||||||
|
|
||||||
const onDragStart = (element, widgetClass=null) => {
|
const onDragStart = (element, widgetClass=null) => {
|
||||||
setDraggedElement(element)
|
setDraggedElement(element)
|
||||||
|
|
||||||
@@ -29,7 +33,8 @@ export const DragProvider = ({ children }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DragContext.Provider value={{ draggedElement, overElement, setOverElement,
|
<DragContext.Provider value={{ draggedElement, overElement, setOverElement,
|
||||||
widgetClass, onDragStart, onDragEnd }}>
|
widgetClass, onDragStart, onDragEnd, posMetaData,
|
||||||
|
setPosMetaData }}>
|
||||||
{children}
|
{children}
|
||||||
</DragContext.Provider>
|
</DragContext.Provider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useDragContext } from "./draggableContext"
|
|||||||
const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
|
const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
|
||||||
|
|
||||||
|
|
||||||
const { draggedElement, overElement, setOverElement, widgetClass } = useDragContext()
|
const { draggedElement, posMetaData, setOverElement, widgetClass } = useDragContext()
|
||||||
|
|
||||||
const [showDroppable, setShowDroppable] = useState({
|
const [showDroppable, setShowDroppable] = useState({
|
||||||
show: false,
|
show: false,
|
||||||
@@ -101,7 +101,7 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
|
|||||||
))
|
))
|
||||||
|
|
||||||
if(onDrop && allowDrop){
|
if(onDrop && allowDrop){
|
||||||
onDrop(e, draggedElement, widgetClass)
|
onDrop(e, draggedElement, widgetClass, posMetaData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
import { Select, Input, Button } from "antd"
|
import { Select, Input, Button } from "antd"
|
||||||
import { CrownFilled, DownloadOutlined, DownOutlined } from "@ant-design/icons"
|
import { CrownFilled, DownloadOutlined, DownOutlined, PlayCircleFilled, VideoCameraOutlined } from "@ant-design/icons"
|
||||||
import FrameWorks from "../constants/frameworks"
|
import FrameWorks from "../constants/frameworks"
|
||||||
import Premium from "../sidebar/utils/premium"
|
import Premium from "../sidebar/utils/premium"
|
||||||
|
import VideoPopUp from "./video-popup"
|
||||||
|
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
@@ -23,19 +24,36 @@ function Header({projectName, onProjectNameChange, framework, onFrameworkChange,
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`tw-w-full tw-bg-primaryBg tw-p-2 tw-flex tw-place-items-center
|
<div className={`tw-w-full tw-bg-primaryBg tw-gap-2 tw-overflow-x-auto tw-p-2 tw-flex tw-place-items-center
|
||||||
${className||''}`}>
|
${className||''}`}>
|
||||||
|
|
||||||
<Select
|
<div className="tw-flex tw-gap-2 tw-place-content-center">
|
||||||
// defaultValue={framework}
|
<Select
|
||||||
value={framework}
|
// defaultValue={framework}
|
||||||
options={items}
|
value={framework}
|
||||||
// onSelect={(key) => {console.log("value: ", key); onFrameworkChange(key); }}
|
options={items}
|
||||||
onChange={(key) => {onFrameworkChange(key)}}
|
// onSelect={(key) => {console.log("value: ", key); onFrameworkChange(key); }}
|
||||||
className="tw-min-w-[150px]"
|
onChange={(key) => {onFrameworkChange(key)}}
|
||||||
/>
|
className="tw-min-w-[150px]"
|
||||||
|
/>
|
||||||
|
<VideoPopUp>
|
||||||
|
<div className="tw-p-1 tw-w-full tw-outline-none tw-bg-transparent tw-border-[1px]
|
||||||
|
tw-border-gray-400 tw-rounded-md tw-no-underline tw-border-solid hover:tw-bg-[#9333EA]
|
||||||
|
hover:tw-text-white tw-duration-200 tw-flex tw-gap-1
|
||||||
|
tw-text-black tw-text-center tw-px-4 tw-text-sm tw-cursor-pointer">
|
||||||
|
Watch demo
|
||||||
|
<PlayCircleFilled className="tw-text-lg"/>
|
||||||
|
</div>
|
||||||
|
</VideoPopUp>
|
||||||
|
</div>
|
||||||
<div className="tw-ml-auto tw-flex tw-gap-2 tw-place-content-center">
|
<div className="tw-ml-auto tw-flex tw-gap-2 tw-place-content-center">
|
||||||
|
<button data-tally-open="mVDY7N" data-tally-layout="modal" data-tally-emoji-text="👋"
|
||||||
|
data-tally-emoji-animation="wave" className="tw-p-1 tw-w-full tw-outline-none tw-bg-transparent tw-border-[1px]
|
||||||
|
tw-border-gray-400 tw-rounded-md tw-no-underline tw-border-solid hover:tw-bg-[#9333EA]
|
||||||
|
hover:tw-text-white tw-duration-200
|
||||||
|
tw-text-black tw-text-center tw-px-4 tw-text-sm tw-cursor-pointer">
|
||||||
|
Get Updates
|
||||||
|
</button>
|
||||||
<Premium className="tw-text-2xl tw-bg-purple-600 tw-text-center
|
<Premium className="tw-text-2xl tw-bg-purple-600 tw-text-center
|
||||||
tw-w-[40px] tw-min-w-[40px] tw-h-[35px] tw-rounded-md
|
tw-w-[40px] tw-min-w-[40px] tw-h-[35px] tw-rounded-md
|
||||||
tw-cursor-pointer tw-text-white
|
tw-cursor-pointer tw-text-white
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState, useRef } from "react"
|
import React, { useEffect, useState, useRef } from "react"
|
||||||
import { Input, Button, Space, Radio } from "antd"
|
import { Input, Button, Space, Radio, InputNumber } from "antd"
|
||||||
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"
|
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"
|
||||||
import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons'
|
import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons'
|
||||||
|
|
||||||
@@ -87,19 +87,20 @@ export const DynamicInputList = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const DynamicRadioInputList = React.memo(({defaultInputs=[""], defaultSelected=null, onChange}) => {
|
export const DynamicRadioInputList = React.memo(({defaultInputs, defaultSelected=null, onChange}) => {
|
||||||
const [inputs, setInputs] = useState([""]) // Initialize with one input
|
const [inputs, setInputs] = useState(defaultInputs || [""]) // Initialize with one input
|
||||||
const [selectedRadio, setSelectedRadio] = useState(null) // Tracks selected radio button
|
const [selectedRadio, setSelectedRadio] = useState(defaultSelected) // Tracks selected radio button
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
setInputs(defaultInputs)
|
setInputs(defaultInputs || [""])
|
||||||
|
|
||||||
}, [defaultInputs])
|
}, [defaultInputs])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
setSelectedRadio(defaultSelected)
|
setSelectedRadio(defaultSelected)
|
||||||
|
|
||||||
}, [defaultSelected])
|
}, [defaultSelected])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -165,4 +166,95 @@ export const DynamicRadioInputList = React.memo(({defaultInputs=[""], defaultSel
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* defaultWeightMapping structure: {0: {gridNo: 0, weight: 0}}
|
||||||
|
*/
|
||||||
|
export const DynamicGridWeightInput = React.memo(({value, onChange, gridInputProps, weightInputProps}) => {
|
||||||
|
|
||||||
|
const [weightMapping, setWeightMapping] = useState(value) // Initialize with one input
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setWeightMapping(value || {})
|
||||||
|
}, [value])
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
if(onChange){
|
||||||
|
onChange(weightMapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [weightMapping])
|
||||||
|
|
||||||
|
// Add a new input
|
||||||
|
const addInput = () => {
|
||||||
|
|
||||||
|
const newObjectIndex = Object.keys(weightMapping).length
|
||||||
|
setWeightMapping({...weightMapping, [newObjectIndex]: {gridNo: 1, weight: 0}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove an input by index, but keep the first one
|
||||||
|
const removeInput = (index) => {
|
||||||
|
const newInputs = { ...weightMapping }; // Create a shallow copy
|
||||||
|
delete newInputs[index]; // Remove the entry by key
|
||||||
|
setWeightMapping(newInputs); // Update state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update input value
|
||||||
|
const handleGridNoChange = (index, gridNo, weight) => {
|
||||||
|
const newInputs = {...weightMapping}
|
||||||
|
newInputs[index] = {
|
||||||
|
weight,
|
||||||
|
gridNo
|
||||||
|
}
|
||||||
|
setWeightMapping(newInputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{weightMapping && Object.entries(weightMapping).map(([idx, {weight, gridNo}], index) => (
|
||||||
|
<Space key={index} style={{ display: "flex", marginBottom: 8 }} align="baseline">
|
||||||
|
|
||||||
|
<div className="tw-flex tw-flex-col tw-gap-2">
|
||||||
|
{ index === 0 &&
|
||||||
|
<span>Grid no</span>
|
||||||
|
}
|
||||||
|
<InputNumber
|
||||||
|
value={gridNo}
|
||||||
|
min={1}
|
||||||
|
onChange={(value) => handleGridNoChange(idx, value, weight)}
|
||||||
|
placeholder={`Input ${index + 1}`}
|
||||||
|
{...gridInputProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="tw-flex tw-flex-col tw-gap-2">
|
||||||
|
{ index === 0 &&
|
||||||
|
<span>Weight</span>
|
||||||
|
}
|
||||||
|
<InputNumber
|
||||||
|
min={0}
|
||||||
|
value={weight}
|
||||||
|
onChange={(value) => handleGridNoChange(idx, gridNo, value)}
|
||||||
|
placeholder={`Input ${index + 1}`}
|
||||||
|
{...weightInputProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* {index !== 0 && ( // Do not show delete button for the first input */}
|
||||||
|
<div>
|
||||||
|
<MinusCircleOutlined className="tw-text-xl tw-text-red-500"
|
||||||
|
onClick={() => removeInput(index)} />
|
||||||
|
</div>
|
||||||
|
{/* )} */}
|
||||||
|
</Space>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Button type="dashed" onClick={addInput} icon={<PlusOutlined />}>
|
||||||
|
Add Weight
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
47
src/components/video-popup.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Modal } from "antd"
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
|
||||||
|
const VideoPopUp = ({children, url}) => {
|
||||||
|
|
||||||
|
const [popUpOpen, setPopUpOpen] = useState(false)
|
||||||
|
|
||||||
|
|
||||||
|
const handleVideoOpen = () => {
|
||||||
|
setPopUpOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVideoClose = (event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
setPopUpOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div onClick={handleVideoOpen} >
|
||||||
|
{children}
|
||||||
|
|
||||||
|
<Modal title={"demo"}
|
||||||
|
style={{ zIndex: 14000, gap: '5px', placeContent: "center", }}
|
||||||
|
className="max-xl:tw-max-w-full max-lg:!tw-max-h-[450px]"
|
||||||
|
styles={{body: {height: "80vh", width: "100%"}, content: {width: "100%",}}}
|
||||||
|
onCancel={handleVideoClose}
|
||||||
|
centered
|
||||||
|
onOk={handleVideoClose}
|
||||||
|
footer={null}
|
||||||
|
width={'90%'}
|
||||||
|
height={1000}
|
||||||
|
open={popUpOpen}>
|
||||||
|
<div className="tw-mt-5 tw-text-lg tw-min-w-[350px] tw-rounded-md tw-overflow-hidden tw-h-full tw-w-full ">
|
||||||
|
<iframe src="https://www.youtube.com/embed/Lp2-ToDlqSk?si=dpUpg5ZBdUS8EVOw"
|
||||||
|
className="tw-w-full tw-h-full"
|
||||||
|
title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen>
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoPopUp
|
||||||
0
src/contexts/resizeContext.js
Normal file
42
src/frameworks/License.txt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
License held by paul
|
||||||
|
Github username: PaulleDemon
|
||||||
|
|
||||||
|
Below is the License for source code of PyUIBuilder. For license regarding source code generated by PyUIBuilder Refer Readme.md file (it has basically no restrictions).
|
||||||
|
|
||||||
|
The source code for PyUIBuilder is Dual licensed, most parts of the code comes under AGPL,
|
||||||
|
some parts under a different non-commercial use license.
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------
|
||||||
|
License in short
|
||||||
|
* You are free fork, modify and redistribute it given the original or derived works is kept free and source-available and under the same license.
|
||||||
|
* Another restriction is you can't redistribute in binary or executable formats without asking me first.
|
||||||
|
* If you plan on making money on this work or derived work you need permission first.
|
||||||
|
* No AI Training on this source code
|
||||||
|
|
||||||
|
License was planned to be kept under one AGPL or less restrictive license, but since its difficult for me to continue providing free and source-available software without proper funding. It's under this license.
|
||||||
|
|
||||||
|
|
||||||
|
Read the full license below.
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
1. License Grant
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, publish, and distribute the source code, subject to the following conditions:
|
||||||
|
|
||||||
|
2. Derived Work License
|
||||||
|
Any modifications, derivative works, or copies of the Software must retain this license. All derivative works must be source-available under the same license terms. You are not permitted to relicense or sub-license any portion of the Software under a different license without explicit prior written permission from the original author.
|
||||||
|
|
||||||
|
3. Distribution Restriction on Executables
|
||||||
|
You are not permitted to distribute the Software in compiled, executable, or binary form without prior written consent from the original author. This applies to both original and derivative works.
|
||||||
|
|
||||||
|
4. Commercial Use Restriction
|
||||||
|
The Software, in its original or modified form, cannot be used for any commercial purpose without prior written permission from the original author. This includes selling, licensing, or offering the Software as part of a service.
|
||||||
|
|
||||||
|
5. Non-Commercial Use
|
||||||
|
You may freely use, copy, modify, and distribute the source code of the Software for non-commercial purposes, provided that the same terms of this license are maintained in all copies or derivative works.
|
||||||
|
|
||||||
|
6. Changes to the License
|
||||||
|
The original author reserves the right to modify, amend, or update this license at any time. Any changes will apply only to future versions of the Software. Any prior versions of the Software, including derivative works, will continue to be governed by the version of the license in effect at the time the work was created.
|
||||||
|
|
||||||
|
7. Disclaimer
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
export const Tkinter_TO_WEB_CURSOR_MAPPING = {
|
export const Tkinter_TO_WEB_CURSOR_MAPPING = {
|
||||||
|
|
||||||
|
"": "",
|
||||||
"arrow": "default",
|
"arrow": "default",
|
||||||
"circle": "wait",
|
"circle": "wait",
|
||||||
"clock": "wait",
|
"clock": "wait",
|
||||||
|
|||||||
@@ -20,5 +20,25 @@ export const ANCHOR = [
|
|||||||
"s",
|
"s",
|
||||||
"e",
|
"e",
|
||||||
"w",
|
"w",
|
||||||
"center"
|
"center",
|
||||||
]
|
"ne",
|
||||||
|
"se",
|
||||||
|
"sw",
|
||||||
|
"nw",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
export const GRID_STICKY = {
|
||||||
|
N: "n",
|
||||||
|
S: "s",
|
||||||
|
E: "e",
|
||||||
|
W: "w",
|
||||||
|
WE: "we",
|
||||||
|
NS: "ns",
|
||||||
|
NW: "nw",
|
||||||
|
NE: "ne",
|
||||||
|
SW: "sw",
|
||||||
|
SE: "se",
|
||||||
|
NEWS: "news",
|
||||||
|
NONE: "",
|
||||||
|
}
|
||||||
@@ -111,13 +111,13 @@ async function generateCustomTkCode(projectName, widgetList=[], widgetRefs=[], a
|
|||||||
|
|
||||||
// widget - {id, widgetType: widgetComponentType, children: [], parent: "", initialData: {}}
|
// widget - {id, widgetType: widgetComponentType, children: [], parent: "", initialData: {}}
|
||||||
|
|
||||||
const generatedObject = generateCustomTkCodeList(filteredWidgetList, widgetRefs, "", "")
|
const generatedObject = generateCustomTkCodeList(filteredWidgetList, widgetRefs.current, "", "")
|
||||||
|
|
||||||
const {code: codeLines, imports, requirements, mainVariable} = generatedObject
|
const {code: codeLines, imports, requirements, mainVariable} = generatedObject
|
||||||
|
|
||||||
// TODO: avoid adding \n inside the list instead rewrite using code.join("\n")
|
// TODO: avoid adding \n inside the list instead rewrite using code.join("\n")
|
||||||
const code = [
|
const code = [
|
||||||
"# This code is generated by PyUIbuilder: https://github.com/PaulleDemon/PyUIBuilder",
|
"# This code is generated by PyUIbuilder: https://pyuibuilder.com",
|
||||||
"\n\n",
|
"\n\n",
|
||||||
...imports.flatMap((item, index) => index < imports.length - 1 ? [item, "\n"] : [item]), //add \n to every line
|
...imports.flatMap((item, index) => index < imports.length - 1 ? [item, "\n"] : [item]), //add \n to every line
|
||||||
"\n\n",
|
"\n\n",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const Themes = {
|
|||||||
class AnalogTimePicker extends CustomTkBase{
|
class AnalogTimePicker extends CustomTkBase{
|
||||||
|
|
||||||
static widgetType = "analog_timepicker"
|
static widgetType = "analog_timepicker"
|
||||||
|
static displayName = "Analog Timepicker"
|
||||||
|
|
||||||
static requiredImports = [
|
static requiredImports = [
|
||||||
...CustomTkBase.requiredImports,
|
...CustomTkBase.requiredImports,
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { CustomTkBase } from "../widgets/base"
|
|||||||
class MapView extends CustomTkBase{
|
class MapView extends CustomTkBase{
|
||||||
|
|
||||||
static widgetType = "map_view"
|
static widgetType = "map_view"
|
||||||
|
static displayName = "Map View"
|
||||||
|
|
||||||
|
|
||||||
static requiredImports = [
|
static requiredImports = [
|
||||||
...CustomTkBase.requiredImports,
|
...CustomTkBase.requiredImports,
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const ResizableTable = ({minRows=5, minCols=5}) => {
|
|||||||
class PandasTable extends CustomTkBase{
|
class PandasTable extends CustomTkBase{
|
||||||
|
|
||||||
static widgetType = "pandas_table"
|
static widgetType = "pandas_table"
|
||||||
|
static displayName = "Pandas Table"
|
||||||
|
|
||||||
static requiredImports = [
|
static requiredImports = [
|
||||||
...CustomTkBase.requiredImports,
|
...CustomTkBase.requiredImports,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
|||||||
class VideoPlayer extends CustomTkBase{
|
class VideoPlayer extends CustomTkBase{
|
||||||
|
|
||||||
static widgetType = "video_player"
|
static widgetType = "video_player"
|
||||||
|
static displayName = "Video Player"
|
||||||
|
|
||||||
static requiredImports = [
|
static requiredImports = [
|
||||||
...CustomTkBase.requiredImports,
|
...CustomTkBase.requiredImports,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Label from "./widgets/label"
|
|||||||
import Button from "./widgets/button"
|
import Button from "./widgets/button"
|
||||||
import OptionMenu from "./widgets/optionMenu"
|
import OptionMenu from "./widgets/optionMenu"
|
||||||
import Slider from "./widgets/slider"
|
import Slider from "./widgets/slider"
|
||||||
import { CheckBox, RadioButton } from "./widgets/ checkButton"
|
import { CheckBox, RadioButton } from "./widgets/checkButton"
|
||||||
import { Input, Text } from "./widgets/input"
|
import { Input, Text } from "./widgets/input"
|
||||||
import SpinBox from "./widgets/spinBox"
|
import SpinBox from "./widgets/spinBox"
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { CustomTkWidgetBase } from "./base"
|
|||||||
class Button extends CustomTkWidgetBase{
|
class Button extends CustomTkWidgetBase{
|
||||||
|
|
||||||
static widgetType = "button"
|
static widgetType = "button"
|
||||||
|
static displayName = "Button"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -69,6 +70,7 @@ class Button extends CustomTkWidgetBase{
|
|||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md
|
||||||
tw-border tw-border-solid tw-border-gray-400 tw-overflow-hidden">
|
tw-border tw-border-solid tw-border-gray-400 tw-overflow-hidden">
|
||||||
<div className="tw-p-2 tw-w-full tw-flex tw-place-content-center tw-place-items-center tw-h-full tw-text-center"
|
<div className="tw-p-2 tw-w-full tw-flex tw-place-content-center tw-place-items-center tw-h-full tw-text-center"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
{/* {this.props.children} */}
|
{/* {this.props.children} */}
|
||||||
<div className="tw-text-sm" style={{color: this.getAttrValue("styling.foregroundColor")}}>
|
<div className="tw-text-sm" style={{color: this.getAttrValue("styling.foregroundColor")}}>
|
||||||
|
|||||||
@@ -3,11 +3,15 @@ import Tools from "../../../canvas/constants/tools"
|
|||||||
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
|
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
|
||||||
import { CheckSquareFilled } from "@ant-design/icons"
|
import { CheckSquareFilled } from "@ant-design/icons"
|
||||||
import { CustomTkWidgetBase } from "./base"
|
import { CustomTkWidgetBase } from "./base"
|
||||||
|
import { Layouts } from "../../../canvas/constants/layouts"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
|
||||||
export class CheckBox extends CustomTkWidgetBase{
|
export class CheckBox extends CustomTkWidgetBase{
|
||||||
|
|
||||||
static widgetType = "check_button"
|
static widgetType = "check_button"
|
||||||
|
static displayName = "Check Box"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@@ -130,6 +134,8 @@ export class RadioButton extends CustomTkWidgetBase{
|
|||||||
|
|
||||||
this.minSize = {width: 50, height: 30}
|
this.minSize = {width: 50, height: 30}
|
||||||
|
|
||||||
|
this.firstDivRef = React.createRef()
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
size: { width: 80, height: 30 },
|
size: { width: 80, height: 30 },
|
||||||
@@ -178,7 +184,7 @@ export class RadioButton extends CustomTkWidgetBase{
|
|||||||
code.push(`\n`)
|
code.push(`\n`)
|
||||||
code.push(`${radioBtnVariable} = ctk.CTkRadioButton(master=${parent}, variable=${variableName}_var, text="${radio_text}", value=${idx})`)
|
code.push(`${radioBtnVariable} = ctk.CTkRadioButton(master=${parent}, variable=${variableName}_var, text="${radio_text}", value=${idx})`)
|
||||||
code.push(`${radioBtnVariable}.configure(${convertObjectToKeyValueString(config)})`)
|
code.push(`${radioBtnVariable}.configure(${convertObjectToKeyValueString(config)})`)
|
||||||
code.push(`${radioBtnVariable}.${this.getLayoutCode()}`)
|
code.push(`${radioBtnVariable}.${this.getLayoutCode({index: idx})}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
const defaultSelected = radios.selectedRadio
|
const defaultSelected = radios.selectedRadio
|
||||||
@@ -191,6 +197,39 @@ export class RadioButton extends CustomTkWidgetBase{
|
|||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* The index is required for pack as in pack every widget would be packed on top of each other in radio button
|
||||||
|
*/
|
||||||
|
getLayoutCode({index=0}){
|
||||||
|
let layoutManager = super.getLayoutCode()
|
||||||
|
|
||||||
|
const {layout: parentLayout, direction, gap, align="start"} = this.getParentLayout()
|
||||||
|
const absolutePositioning = this.getAttrValue("positioning")
|
||||||
|
|
||||||
|
|
||||||
|
if (parentLayout === Layouts.PLACE || absolutePositioning){
|
||||||
|
const config = {}
|
||||||
|
|
||||||
|
const elementRect = this.firstDivRef.current?.getBoundingClientRect()
|
||||||
|
|
||||||
|
config['x'] = Math.trunc(this.state.pos.x)
|
||||||
|
config['y'] = Math.trunc(this.state.pos.y)
|
||||||
|
|
||||||
|
if (elementRect?.height){
|
||||||
|
config['y'] = Math.trunc(config['y'] + (index * elementRect.height)) // the index is the radiobutton index
|
||||||
|
}
|
||||||
|
|
||||||
|
const configStr = convertObjectToKeyValueString(config)
|
||||||
|
|
||||||
|
layoutManager = `place(${configStr})`
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return layoutManager
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
getToolbarAttrs(){
|
getToolbarAttrs(){
|
||||||
|
|
||||||
const toolBarAttrs = super.getToolbarAttrs()
|
const toolBarAttrs = super.getToolbarAttrs()
|
||||||
@@ -211,13 +250,18 @@ export class RadioButton extends CustomTkWidgetBase{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
|
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}
|
style={this.getInnerRenderStyling()}
|
||||||
>
|
>
|
||||||
<div className="tw-flex tw-flex-col tw-gap-2 tw-w-fit tw-h-fit">
|
<div className="tw-flex tw-flex-col tw-gap-2 tw-w-fit tw-h-fit">
|
||||||
{
|
{
|
||||||
inputs.map((value, index) => {
|
inputs.map((value, index) => {
|
||||||
|
|
||||||
|
const ref = index === 0 ? this.firstDivRef : null
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index} className="tw-flex tw-gap-2 tw-w-full tw-h-full tw-place-items-center ">
|
<div key={index} ref={ref} className="tw-flex tw-gap-2 tw-w-full tw-h-full tw-place-items-center ">
|
||||||
<div className="tw-border-solid tw-border-[#D9D9D9] tw-border-2
|
<div className="tw-border-solid tw-border-[#D9D9D9] tw-border-2
|
||||||
tw-min-w-[20px] tw-min-h-[20px] tw-w-[20px] tw-h-[20px]
|
tw-min-w-[20px] tw-min-h-[20px] tw-w-[20px] tw-h-[20px]
|
||||||
tw-text-blue-600 tw-flex tw-items-center tw-justify-center
|
tw-text-blue-600 tw-flex tw-items-center tw-justify-center
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Layouts } from "../../../canvas/constants/layouts"
|
||||||
|
import Tools from "../../../canvas/constants/tools"
|
||||||
import Widget from "../../../canvas/widgets/base"
|
import Widget from "../../../canvas/widgets/base"
|
||||||
import { CustomTkBase } from "./base"
|
import { CustomTkBase } from "./base"
|
||||||
|
|
||||||
@@ -5,6 +7,7 @@ import { CustomTkBase } from "./base"
|
|||||||
class Frame extends CustomTkBase{
|
class Frame extends CustomTkBase{
|
||||||
|
|
||||||
static widgetType = "frame"
|
static widgetType = "frame"
|
||||||
|
static displayName = "Frame"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -16,7 +19,99 @@ class Frame extends CustomTkBase{
|
|||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
fitContent: {width: true, height: true},
|
fitContent: {width: true, height: true},
|
||||||
widgetName: "Frame"
|
widgetName: "Frame",
|
||||||
|
attrs: {
|
||||||
|
...this.state.attrs,
|
||||||
|
padding: {
|
||||||
|
label: "padding",
|
||||||
|
padX: {
|
||||||
|
label: "Pad X",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
// this.setWidgetInnerStyle("paddingLeft", `${value}px`)
|
||||||
|
// this.setWidgetInnerStyle("paddingRight", `${value}px`)
|
||||||
|
|
||||||
|
// const widgetStyle = {
|
||||||
|
|
||||||
|
// }
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
|
||||||
|
widgetInnerStyling: {
|
||||||
|
...prevState.widgetInnerStyling,
|
||||||
|
paddingLeft: `${value}px`,
|
||||||
|
paddingRight: `${value}px`
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
this.setAttrValue("padding.padX", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
padY: {
|
||||||
|
label: "Pad Y",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
|
||||||
|
widgetInnerStyling: {
|
||||||
|
...prevState.widgetInnerStyling,
|
||||||
|
paddingTop: `${value}px`,
|
||||||
|
paddingBottom: `${value}px`
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
// this.setState({
|
||||||
|
|
||||||
|
// widgetInnerStyling: widgetStyle
|
||||||
|
// })
|
||||||
|
this.setAttrValue("padding.padY", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
label: "Margin",
|
||||||
|
marginX: {
|
||||||
|
label: "Margin X",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
|
||||||
|
this.updateState((prev) => ({
|
||||||
|
widgetOuterStyling: {
|
||||||
|
...prev.widgetOuterStyling,
|
||||||
|
marginLeft: `${value}px`,
|
||||||
|
marginRight: `${value}px`
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
this.setAttrValue("margin.marginX", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
marginY: {
|
||||||
|
label: "Margin Y",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
this.updateState((prev) => ({
|
||||||
|
widgetOuterStyling: {
|
||||||
|
...prev.widgetOuterStyling,
|
||||||
|
marginTop: `${value}px`,
|
||||||
|
marginBottom: `${value}px`
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
this.setAttrValue("margin.marginY", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,6 +121,34 @@ class Frame extends CustomTkBase{
|
|||||||
this.setAttrValue("styling.backgroundColor", "#EDECEC")
|
this.setAttrValue("styling.backgroundColor", "#EDECEC")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getConfigCode(){
|
||||||
|
const bg = this.getAttrValue("styling.backgroundColor")
|
||||||
|
|
||||||
|
const fitWidth = this.state.fitContent.width
|
||||||
|
const fitHeight = this.state.fitContent.height
|
||||||
|
|
||||||
|
const {width, height} = this.getSize()
|
||||||
|
|
||||||
|
const {layout} = this.getParentLayout()
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
bg: `"${bg}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layout !== Layouts.PLACE){
|
||||||
|
if (!fitWidth){
|
||||||
|
config['width'] = width
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fitHeight){
|
||||||
|
config['height'] = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
generateCode(variableName, parent){
|
generateCode(variableName, parent){
|
||||||
|
|
||||||
const bg = this.getAttrValue("styling.backgroundColor")
|
const bg = this.getAttrValue("styling.backgroundColor")
|
||||||
@@ -33,20 +156,36 @@ class Frame extends CustomTkBase{
|
|||||||
return [
|
return [
|
||||||
`${variableName} = ctk.CTkFrame(master=${parent})`,
|
`${variableName} = ctk.CTkFrame(master=${parent})`,
|
||||||
`${variableName}.configure(fg_color="${bg}")`,
|
`${variableName}.configure(fg_color="${bg}")`,
|
||||||
`${variableName}.${this.getLayoutCode()}`
|
`${variableName}.${this.getLayoutCode()}`,
|
||||||
|
...this.getGridLayoutConfigurationCode(variableName)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getToolbarAttrs(){
|
||||||
|
const {layout, gridConfig, gridWeights, ...toolBarAttrs} = super.getToolbarAttrs()
|
||||||
|
|
||||||
|
// places layout at the end
|
||||||
|
return ({
|
||||||
|
id: this.__id,
|
||||||
|
...toolBarAttrs,
|
||||||
|
padding: this.state.attrs.padding,
|
||||||
|
margin: this.state.attrs.margin,
|
||||||
|
layout,
|
||||||
|
gridConfig,
|
||||||
|
gridWeights
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
renderContent(){
|
renderContent(){
|
||||||
// console.log("bounding rect: ", this.getBoundingRect())
|
// console.log("bounding rect: ", this.getBoundingRect())
|
||||||
|
|
||||||
// console.log("widget styling: ", this.state.widgetInnerStyling)
|
// console.log("widget styling: ", this.state.widgetInnerStyling)
|
||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-relative tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-relative tw-rounded-md tw-overflow-hidden">
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start" style={this.getInnerRenderStyling()}>
|
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start"
|
||||||
{this.props.children}
|
ref={this.styleAreaRef}
|
||||||
|
style={this.getInnerRenderStyling()}>
|
||||||
|
{this.renderTkinterLayout()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { CustomTkWidgetBase } from "./base"
|
|||||||
export class Input extends CustomTkWidgetBase{
|
export class Input extends CustomTkWidgetBase{
|
||||||
|
|
||||||
static widgetType = "entry"
|
static widgetType = "entry"
|
||||||
|
static displayName = "Entry"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -65,6 +66,7 @@ export class Input extends CustomTkWidgetBase{
|
|||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-items-center"
|
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-items-center"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
<div className="tw-text-sm tw-text-gray-300">
|
<div className="tw-text-sm tw-text-gray-300">
|
||||||
{this.getAttrValue("placeHolder")}
|
{this.getAttrValue("placeHolder")}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { CustomTkWidgetBase } from "./base"
|
|||||||
class Label extends CustomTkWidgetBase{
|
class Label extends CustomTkWidgetBase{
|
||||||
|
|
||||||
static widgetType = "label"
|
static widgetType = "label"
|
||||||
|
static displayName = "Label"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -116,6 +116,22 @@ class Label extends CustomTkWidgetBase{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAnchorStyle = (anchor) => {
|
||||||
|
const anchorStyles = {
|
||||||
|
n: { justifyContent: 'center', alignItems: 'flex-start' },
|
||||||
|
s: { justifyContent: 'center', alignItems: 'flex-end' },
|
||||||
|
e: { justifyContent: 'flex-end', alignItems: 'center' },
|
||||||
|
w: { justifyContent: 'flex-start', alignItems: 'center' },
|
||||||
|
ne: { justifyContent: 'flex-end', alignItems: 'flex-start' },
|
||||||
|
se: { justifyContent: 'flex-end', alignItems: 'flex-end' },
|
||||||
|
nw: { justifyContent: 'flex-start', alignItems: 'flex-start' },
|
||||||
|
sw: { justifyContent: 'flex-start', alignItems: 'flex-end' },
|
||||||
|
center: { justifyContent: 'center', alignItems: 'center' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchorStyles[anchor] || anchorStyles["w"];
|
||||||
|
}
|
||||||
|
|
||||||
renderContent(){
|
renderContent(){
|
||||||
|
|
||||||
const image = this.getAttrValue("imageUpload")
|
const image = this.getAttrValue("imageUpload")
|
||||||
@@ -129,6 +145,7 @@ class Label extends CustomTkWidgetBase{
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-content-center tw-place-items-center "
|
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-content-center tw-place-items-center "
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
{/* {this.props.children} */}
|
{/* {this.props.children} */}
|
||||||
{
|
{
|
||||||
@@ -136,7 +153,10 @@ class Label extends CustomTkWidgetBase{
|
|||||||
<img src={image.previewUrl} className="tw-bg-contain tw-w-full tw-h-full" />
|
<img src={image.previewUrl} className="tw-bg-contain tw-w-full tw-h-full" />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div className="" style={{color: this.getAttrValue("styling.foregroundColor")}}>
|
<div className={`tw-flex ${!image ? "tw-w-full tw-h-full" : ""}`} style={{
|
||||||
|
color: this.getAttrValue("styling.foregroundColor"),
|
||||||
|
...this.getAnchorStyle(this.getAttrValue("styling.anchor"))
|
||||||
|
}}>
|
||||||
{this.getAttrValue("labelWidget")}
|
{this.getAttrValue("labelWidget")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
import Widget from "../../../canvas/widgets/base"
|
import Widget from "../../../canvas/widgets/base"
|
||||||
import Tools from "../../../canvas/constants/tools"
|
import Tools from "../../../canvas/constants/tools"
|
||||||
import { CustomTkBase } from "./base"
|
import { CustomTkBase } from "./base"
|
||||||
|
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
||||||
|
|
||||||
|
|
||||||
class MainWindow extends CustomTkBase{
|
class MainWindow extends CustomTkBase{
|
||||||
|
|
||||||
static widgetType = "main_window"
|
static widgetType = "main_window"
|
||||||
|
static displayName = "Main Window"
|
||||||
|
|
||||||
|
|
||||||
|
static initialSize = {
|
||||||
|
width: 700,
|
||||||
|
height: 400
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -26,6 +35,13 @@ class MainWindow extends CustomTkBase{
|
|||||||
toolProps: {placeholder: "Window title", maxLength: 40},
|
toolProps: {placeholder: "Window title", maxLength: 40},
|
||||||
value: "Main Window",
|
value: "Main Window",
|
||||||
onChange: (value) => this.setAttrValue("title", value)
|
onChange: (value) => this.setAttrValue("title", value)
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
label: "Window Logo",
|
||||||
|
tool: Tools.UPLOADED_LIST,
|
||||||
|
toolProps: {filterOptions: ["image/jpg", "image/jpeg", "image/png"]},
|
||||||
|
value: "",
|
||||||
|
onChange: (value) => this.setAttrValue("logo", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -42,12 +58,48 @@ class MainWindow extends CustomTkBase{
|
|||||||
generateCode(variableName, parent){
|
generateCode(variableName, parent){
|
||||||
|
|
||||||
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
||||||
|
const logo = this.getAttrValue("logo")
|
||||||
|
|
||||||
return [
|
const {width, height} = this.getSize()
|
||||||
`${variableName} = ctk.CTk()`,
|
|
||||||
`${variableName}.configure(fg_color="${backgroundColor}")`,
|
const code = [
|
||||||
`${variableName}.title("${this.getAttrValue("title")}")`
|
`${variableName} = ctk.CTk()`,
|
||||||
]
|
`${variableName}.configure(fg_color="${backgroundColor}")`,
|
||||||
|
`${variableName}.title("${this.getAttrValue("title")}")`,
|
||||||
|
`${variableName}.geometry("${width}x${height}")`,
|
||||||
|
...this.getGridLayoutConfigurationCode(variableName)
|
||||||
|
]
|
||||||
|
|
||||||
|
if (logo?.name){
|
||||||
|
|
||||||
|
// code.push(`\n`)
|
||||||
|
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(logo.name, "image")})`)
|
||||||
|
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||||
|
code.push(`${variableName}.iconphoto(False, ${variableName}_img)`)
|
||||||
|
// code.push("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
getImports(){
|
||||||
|
const imports = super.getImports()
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
imports.push("import os", "from PIL import Image, ImageTk", )
|
||||||
|
|
||||||
|
return imports
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getRequirements(){
|
||||||
|
const requirements = super.getRequirements()
|
||||||
|
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
requirements.push("pillow")
|
||||||
|
|
||||||
|
return requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolbarAttrs(){
|
getToolbarAttrs(){
|
||||||
@@ -57,8 +109,8 @@ class MainWindow extends CustomTkBase{
|
|||||||
id: this.__id,
|
id: this.__id,
|
||||||
widgetName: toolBarAttrs.widgetName,
|
widgetName: toolBarAttrs.widgetName,
|
||||||
title: this.state.attrs.title,
|
title: this.state.attrs.title,
|
||||||
|
logo: this.state.attrs.logo,
|
||||||
size: toolBarAttrs.size,
|
size: toolBarAttrs.size,
|
||||||
|
|
||||||
...this.state.attrs,
|
...this.state.attrs,
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -80,8 +132,9 @@ class MainWindow extends CustomTkBase{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-p-2 tw-w-full tw-relative tw-h-full tw-overflow-hidden tw-content-start"
|
<div className="tw-p-2 tw-w-full tw-relative tw-h-full tw-overflow-hidden tw-content-start"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.state.widgetInnerStyling}>
|
style={this.state.widgetInnerStyling}>
|
||||||
{this.props.children}
|
{this.renderTkinterLayout()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../uti
|
|||||||
class OptionMenu extends CustomTkWidgetBase{
|
class OptionMenu extends CustomTkWidgetBase{
|
||||||
|
|
||||||
static widgetType = "option_menu"
|
static widgetType = "option_menu"
|
||||||
|
static displayName = "Option Menu"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -30,8 +31,8 @@ class OptionMenu extends CustomTkWidgetBase{
|
|||||||
label: "Default Value",
|
label: "Default Value",
|
||||||
tool: Tools.INPUT,
|
tool: Tools.INPUT,
|
||||||
value: "Select option",
|
value: "Select option",
|
||||||
onChange: ({inputs, selectedRadio}) => {
|
onChange: (value) => {
|
||||||
this.setAttrValue("options", {inputs, selectedRadio})
|
this.setAttrValue("defaultValue", value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
widgetOptions: {
|
widgetOptions: {
|
||||||
@@ -102,6 +103,7 @@ class OptionMenu extends CustomTkWidgetBase{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
|
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}
|
style={this.getInnerRenderStyling()}
|
||||||
onClick={this.toggleDropDownOpen}
|
onClick={this.toggleDropDownOpen}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { CustomTkWidgetBase } from "./base"
|
|||||||
class Slider extends CustomTkWidgetBase{
|
class Slider extends CustomTkWidgetBase{
|
||||||
|
|
||||||
static widgetType = "scale"
|
static widgetType = "scale"
|
||||||
|
static displayName = "Scale"
|
||||||
|
|
||||||
// FIXME: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use
|
// FIXME: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -141,7 +143,9 @@ class Slider extends CustomTkWidgetBase{
|
|||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||||
<div className="flex flex-col items-center justify-center h-screen
|
<div className="flex flex-col items-center justify-center h-screen
|
||||||
bg-gray-100" style={this.getInnerRenderStyling()}>
|
bg-gray-100"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
|
style={this.getInnerRenderStyling()}>
|
||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { CustomTkWidgetBase } from "./base"
|
|||||||
class SpinBox extends CustomTkWidgetBase{
|
class SpinBox extends CustomTkWidgetBase{
|
||||||
|
|
||||||
static widgetType = "spin_box"
|
static widgetType = "spin_box"
|
||||||
|
static displayName = "Spin Box"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -109,6 +110,7 @@ class SpinBox extends CustomTkWidgetBase{
|
|||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-items-center tw-justify-between"
|
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-items-center tw-justify-between"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
<div className="tw-text-sm ">
|
<div className="tw-text-sm ">
|
||||||
{this.getAttrValue("spinProps.default")}
|
{this.getAttrValue("spinProps.default")}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import Widget from "../../../canvas/widgets/base"
|
import Widget from "../../../canvas/widgets/base"
|
||||||
import Tools from "../../../canvas/constants/tools"
|
import Tools from "../../../canvas/constants/tools"
|
||||||
|
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
||||||
|
import { CustomTkBase } from "./base"
|
||||||
|
|
||||||
|
|
||||||
class TopLevel extends Widget{
|
class TopLevel extends CustomTkBase{
|
||||||
|
|
||||||
static widgetType = "toplevel"
|
static widgetType = "toplevel"
|
||||||
|
static displayName = "Top Level"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -26,6 +29,13 @@ class TopLevel extends Widget{
|
|||||||
toolProps: {placeholder: "Window title", maxLength: 40},
|
toolProps: {placeholder: "Window title", maxLength: 40},
|
||||||
value: "Top level",
|
value: "Top level",
|
||||||
onChange: (value) => this.setAttrValue("title", value)
|
onChange: (value) => this.setAttrValue("title", value)
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
label: "Toplevel Logo",
|
||||||
|
tool: Tools.UPLOADED_LIST,
|
||||||
|
toolProps: {filterOptions: ["image/jpg", "image/jpeg", "image/png"]},
|
||||||
|
value: "",
|
||||||
|
onChange: (value) => this.setAttrValue("logo", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -41,11 +51,49 @@ class TopLevel extends Widget{
|
|||||||
|
|
||||||
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
||||||
|
|
||||||
return [
|
const logo = this.getAttrValue("logo")
|
||||||
`${variableName} = ctk.CTkToplevel(master=${parent})`,
|
|
||||||
`${variableName}.configure(fg_color="${backgroundColor}")`,
|
const {width, height} = this.getSize()
|
||||||
`${variableName}.title("${this.getAttrValue("title")}")`
|
|
||||||
]
|
const code = [
|
||||||
|
`${variableName} = ctk.CTkToplevel(master=${parent})`,
|
||||||
|
`${variableName}.configure(fg_color="${backgroundColor}")`,
|
||||||
|
`${variableName}.title("${this.getAttrValue("title")}")`,
|
||||||
|
`${variableName}.geometry("${width}x${height}")`,
|
||||||
|
|
||||||
|
...this.getGridLayoutConfigurationCode(variableName)
|
||||||
|
]
|
||||||
|
|
||||||
|
if (logo?.name){
|
||||||
|
|
||||||
|
// code.push(`\n`)
|
||||||
|
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(logo.name, "image")})`)
|
||||||
|
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||||
|
code.push(`${variableName}.iconphoto(False, ${variableName}_img)`)
|
||||||
|
// code.push("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
getImports(){
|
||||||
|
const imports = super.getImports()
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
imports.push("import os", "from PIL import Image, ImageTk", )
|
||||||
|
|
||||||
|
return imports
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getRequirements(){
|
||||||
|
const requirements = super.getRequirements()
|
||||||
|
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
requirements.push("pillow")
|
||||||
|
|
||||||
|
return requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolbarAttrs(){
|
getToolbarAttrs(){
|
||||||
@@ -54,8 +102,8 @@ class TopLevel extends Widget{
|
|||||||
return ({
|
return ({
|
||||||
widgetName: toolBarAttrs.widgetName,
|
widgetName: toolBarAttrs.widgetName,
|
||||||
title: this.state.attrs.title,
|
title: this.state.attrs.title,
|
||||||
|
logo: this.state.attrs.logo,
|
||||||
size: toolBarAttrs.size,
|
size: toolBarAttrs.size,
|
||||||
|
|
||||||
...this.state.attrs,
|
...this.state.attrs,
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -76,8 +124,10 @@ class TopLevel extends Widget{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start" style={this.state.widgetInnerStyling}>
|
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start"
|
||||||
{this.props.children}
|
ref={this.styleAreaRef}
|
||||||
|
style={this.state.widgetInnerStyling}>
|
||||||
|
{this.renderTkinterLayout()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
export const Tkinter_TO_WEB_CURSOR_MAPPING = {
|
export const Tkinter_TO_WEB_CURSOR_MAPPING = {
|
||||||
|
|
||||||
|
"": "",
|
||||||
"arrow": "default",
|
"arrow": "default",
|
||||||
"circle": "wait",
|
"circle": "wait",
|
||||||
"clock": "wait",
|
"clock": "wait",
|
||||||
|
|||||||
@@ -20,5 +20,25 @@ export const ANCHOR = [
|
|||||||
"s",
|
"s",
|
||||||
"e",
|
"e",
|
||||||
"w",
|
"w",
|
||||||
"center"
|
"center",
|
||||||
]
|
"ne",
|
||||||
|
"se",
|
||||||
|
"sw",
|
||||||
|
"nw",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
export const GRID_STICKY = {
|
||||||
|
N: "n",
|
||||||
|
S: "s",
|
||||||
|
E: "e",
|
||||||
|
W: "w",
|
||||||
|
WE: "we",
|
||||||
|
NS: "ns",
|
||||||
|
NW: "nw",
|
||||||
|
NE: "ne",
|
||||||
|
SW: "sw",
|
||||||
|
SE: "se",
|
||||||
|
NEWS: "news",
|
||||||
|
NONE: "",
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import MainWindow from "../widgets/mainWindow"
|
|||||||
import { message } from "antd"
|
import { message } from "antd"
|
||||||
import TopLevel from "../widgets/toplevel"
|
import TopLevel from "../widgets/toplevel"
|
||||||
|
|
||||||
|
const pythonFiles = require.context("../pythonWidgets", false, /\.py$/)
|
||||||
|
|
||||||
|
|
||||||
// FIXME: if the toplevel comes first, before the MainWindow in widgetlist the root may become null
|
// FIXME: if the toplevel comes first, before the MainWindow in widgetlist the root may become null
|
||||||
// Recursive function to generate the code list, imports, requirements, and track mainVariable
|
// Recursive function to generate the code list, imports, requirements, and track mainVariable
|
||||||
@@ -13,6 +15,8 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
|
|||||||
let requirements = new Set([])
|
let requirements = new Set([])
|
||||||
let code = []
|
let code = []
|
||||||
|
|
||||||
|
let customPythonWidgets = new Set([])
|
||||||
|
|
||||||
for (let widget of widgetList) {
|
for (let widget of widgetList) {
|
||||||
const widgetRef = widgetRefs[widget.id].current
|
const widgetRef = widgetRefs[widget.id].current
|
||||||
let varName = widgetRef.getVariableName()
|
let varName = widgetRef.getVariableName()
|
||||||
@@ -20,6 +24,7 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
|
|||||||
// Add imports and requirements to sets
|
// Add imports and requirements to sets
|
||||||
widgetRef.getImports().forEach(importItem => imports.add(importItem))
|
widgetRef.getImports().forEach(importItem => imports.add(importItem))
|
||||||
widgetRef.getRequirements().forEach(requirementItem => requirements.add(requirementItem))
|
widgetRef.getRequirements().forEach(requirementItem => requirements.add(requirementItem))
|
||||||
|
widgetRef.getRequiredCustomPyFiles().forEach(customFile => customPythonWidgets.add(customFile))
|
||||||
|
|
||||||
// Set main variable if the widget is MainWindow
|
// Set main variable if the widget is MainWindow
|
||||||
if (widget.widgetType === MainWindow) {
|
if (widget.widgetType === MainWindow) {
|
||||||
@@ -68,6 +73,8 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
|
|||||||
// Merge child imports, requirements, and code
|
// Merge child imports, requirements, and code
|
||||||
imports = new Set([...imports, ...childResult.imports])
|
imports = new Set([...imports, ...childResult.imports])
|
||||||
requirements = new Set([...requirements, ...childResult.requirements])
|
requirements = new Set([...requirements, ...childResult.requirements])
|
||||||
|
customPythonWidgets = new Set([...customPythonWidgets, ...childResult.customPythonWidgets])
|
||||||
|
|
||||||
code.push(...childResult.code)
|
code.push(...childResult.code)
|
||||||
|
|
||||||
mainVariable = childResult.mainVariable || mainVariable // the main variable is the main window variable
|
mainVariable = childResult.mainVariable || mainVariable // the main variable is the main window variable
|
||||||
@@ -78,6 +85,7 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
|
|||||||
imports: Array.from(imports),
|
imports: Array.from(imports),
|
||||||
code: code,
|
code: code,
|
||||||
requirements: Array.from(requirements),
|
requirements: Array.from(requirements),
|
||||||
|
customPythonWidgets: Array.from(customPythonWidgets),
|
||||||
mainVariable
|
mainVariable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,13 +119,17 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], as
|
|||||||
|
|
||||||
// widget - {id, widgetType: widgetComponentType, children: [], parent: "", initialData: {}}
|
// widget - {id, widgetType: widgetComponentType, children: [], parent: "", initialData: {}}
|
||||||
|
|
||||||
const generatedObject = generateTkinterCodeList(filteredWidgetList, widgetRefs, "", "")
|
const generatedObject = generateTkinterCodeList(filteredWidgetList, widgetRefs.current, "", "")
|
||||||
|
|
||||||
const {code: codeLines, imports, requirements, mainVariable} = generatedObject
|
const {code: codeLines, imports, requirements, mainVariable, customPythonWidgets} = generatedObject
|
||||||
|
|
||||||
|
console.log("custom python widgets: ", customPythonWidgets)
|
||||||
|
|
||||||
// TODO: avoid adding \n inside the list instead rewrite using code.join("\n")
|
// TODO: avoid adding \n inside the list instead rewrite using code.join("\n")
|
||||||
|
|
||||||
|
// TODO: import customWidgets
|
||||||
const code = [
|
const code = [
|
||||||
"# This code is generated by PyUIbuilder: https://github.com/PaulleDemon/PyUIBuilder",
|
"# This code is generated by PyUIbuilder: https://pyuibuilder.com",
|
||||||
"\n\n",
|
"\n\n",
|
||||||
...imports.flatMap((item, index) => index < imports.length - 1 ? [item, "\n"] : [item]), //add \n to every line
|
...imports.flatMap((item, index) => index < imports.length - 1 ? [item, "\n"] : [item]), //add \n to every line
|
||||||
"\n\n",
|
"\n\n",
|
||||||
@@ -147,6 +159,32 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], as
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let customWidget of customPythonWidgets){
|
||||||
|
|
||||||
|
let [fileName, extension] = customWidget.split(".")
|
||||||
|
|
||||||
|
if (!extension){
|
||||||
|
fileName = `${fileName}.py`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const fileContent = pythonFiles(`./${fileName}`).default
|
||||||
|
|
||||||
|
createFileList.push({
|
||||||
|
fileData: new Blob([fileContent], { type: "text/plain" }),
|
||||||
|
fileName: fileName,
|
||||||
|
folder: "customWidgets"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customPythonWidgets.length > 0){
|
||||||
|
createFileList.push({
|
||||||
|
fileData: new Blob([''], { type: "text/plain" }),
|
||||||
|
fileName: '__init__.py',
|
||||||
|
folder: "customWidgets"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for (let asset of assetFiles){
|
for (let asset of assetFiles){
|
||||||
|
|
||||||
if (asset.fileType === "image"){
|
if (asset.fileType === "image"){
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const Themes = {
|
|||||||
class AnalogTimePicker extends TkinterBase{
|
class AnalogTimePicker extends TkinterBase{
|
||||||
|
|
||||||
static widgetType = "analog_timepicker"
|
static widgetType = "analog_timepicker"
|
||||||
|
static displayName = "Analog Timepicker"
|
||||||
|
|
||||||
static requiredImports = [
|
static requiredImports = [
|
||||||
...TkinterBase.requiredImports,
|
...TkinterBase.requiredImports,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { TkinterBase } from "../widgets/base"
|
|||||||
class MapView extends TkinterBase{
|
class MapView extends TkinterBase{
|
||||||
|
|
||||||
static widgetType = "map_view"
|
static widgetType = "map_view"
|
||||||
|
static displayName = "Map View"
|
||||||
|
|
||||||
static requiredImports = [
|
static requiredImports = [
|
||||||
...TkinterBase.requiredImports,
|
...TkinterBase.requiredImports,
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ const ResizableTable = ({minRows=5, minCols=5}) => {
|
|||||||
class PandasTable extends TkinterBase{
|
class PandasTable extends TkinterBase{
|
||||||
|
|
||||||
static widgetType = "pandas_table"
|
static widgetType = "pandas_table"
|
||||||
|
static displayName = "Pandas Table"
|
||||||
|
|
||||||
static requiredImports = [
|
static requiredImports = [
|
||||||
...TkinterBase.requiredImports,
|
...TkinterBase.requiredImports,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
|||||||
class VideoPlayer extends TkinterBase{
|
class VideoPlayer extends TkinterBase{
|
||||||
|
|
||||||
static widgetType = "video_player"
|
static widgetType = "video_player"
|
||||||
|
static displayName = "Video Player"
|
||||||
|
|
||||||
static requiredImports = [
|
static requiredImports = [
|
||||||
...TkinterBase.requiredImports,
|
...TkinterBase.requiredImports,
|
||||||
|
|||||||
92
src/frameworks/tkinter/pythonWidgets/imageLabel.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Author: Paul: https://github.com/PaulleDemon
|
||||||
|
# Made using PyUibuilder: https://pyuibuilder.com
|
||||||
|
# MIT License - keep the copy of this license
|
||||||
|
|
||||||
|
# By default Label grows to fit the image, which isn't ideal for many use cases (image should grow to fit/cover the image instead)
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from PIL import Image, ImageTk
|
||||||
|
|
||||||
|
|
||||||
|
class ImageLabel(tk.Label):
|
||||||
|
def __init__(self, master, image_path=None, mode="cover", width=100, height=100, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
mode:
|
||||||
|
- "fit" -> Keeps aspect ratio, fits inside label
|
||||||
|
- "cover" -> Covers label fully, cropping excess
|
||||||
|
"""
|
||||||
|
super().__init__(master, width=width, height=height, *args, **kwargs)
|
||||||
|
self.parent = master
|
||||||
|
self.image_path = image_path
|
||||||
|
self.mode = mode
|
||||||
|
self.original_image = None
|
||||||
|
self.photo = None
|
||||||
|
self.resize_job = None # Debounce job reference
|
||||||
|
|
||||||
|
if mode not in ['fit', 'cover']:
|
||||||
|
raise Exception("Mode can only be fit or cover.")
|
||||||
|
|
||||||
|
if image_path:
|
||||||
|
try:
|
||||||
|
self.original_image = Image.open(image_path)
|
||||||
|
self.photo = ImageTk.PhotoImage(self.original_image)
|
||||||
|
self.config(image=self.photo)
|
||||||
|
|
||||||
|
self.force_resize()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading image: {e}")
|
||||||
|
|
||||||
|
self.after(100, self.init_events)
|
||||||
|
|
||||||
|
def init_events(self):
|
||||||
|
self.parent.bind("<Configure>", self.on_resize)
|
||||||
|
|
||||||
|
def on_resize(self, event=None):
|
||||||
|
"""Debounce resizing to prevent rapid execution."""
|
||||||
|
if self.resize_job:
|
||||||
|
self.after_cancel(self.resize_job)
|
||||||
|
self.resize_job = self.after(1, self.force_resize) # Debounce
|
||||||
|
|
||||||
|
def force_resize(self):
|
||||||
|
"""Resize image using actual widget size."""
|
||||||
|
|
||||||
|
if self.original_image is None:
|
||||||
|
return # Do nothing if no image is loaded
|
||||||
|
|
||||||
|
width = self.winfo_width()
|
||||||
|
height = self.winfo_height()
|
||||||
|
|
||||||
|
if width < 5 or height < 5:
|
||||||
|
return
|
||||||
|
|
||||||
|
aspect_ratio = self.original_image.width / self.original_image.height
|
||||||
|
|
||||||
|
if self.mode == "fit":
|
||||||
|
if width / height > aspect_ratio:
|
||||||
|
new_width = int(height * aspect_ratio)
|
||||||
|
new_height = height
|
||||||
|
else:
|
||||||
|
new_width = width
|
||||||
|
new_height = int(width / aspect_ratio)
|
||||||
|
resized = self.original_image.resize((new_width, new_height), Image.LANCZOS)
|
||||||
|
|
||||||
|
elif self.mode == "cover":
|
||||||
|
if width / height > aspect_ratio:
|
||||||
|
new_width = width
|
||||||
|
new_height = int(width / aspect_ratio)
|
||||||
|
else:
|
||||||
|
new_width = int(height * aspect_ratio)
|
||||||
|
new_height = height
|
||||||
|
|
||||||
|
resized = self.original_image.resize((new_width, new_height), Image.LANCZOS)
|
||||||
|
|
||||||
|
# Crop excess
|
||||||
|
left = (new_width - width) // 2
|
||||||
|
top = (new_height - height) // 2
|
||||||
|
right = left + width
|
||||||
|
bottom = top + height
|
||||||
|
resized = resized.crop((left, top, right, bottom))
|
||||||
|
|
||||||
|
# Update image
|
||||||
|
self.photo = ImageTk.PhotoImage(resized)
|
||||||
|
self.config(image=self.photo)
|
||||||
1
src/frameworks/tkinter/pythonWidgets/readme.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# contains python coded custom widgets
|
||||||
@@ -7,7 +7,7 @@ import Label from "./widgets/label"
|
|||||||
import Button from "./widgets/button"
|
import Button from "./widgets/button"
|
||||||
import OptionMenu from "./widgets/optionMenu"
|
import OptionMenu from "./widgets/optionMenu"
|
||||||
import Slider from "./widgets/slider"
|
import Slider from "./widgets/slider"
|
||||||
import { CheckBox, RadioButton } from "./widgets/ checkButton"
|
import { CheckBox, RadioButton } from "./widgets/checkButton"
|
||||||
import { Input, Text } from "./widgets/input"
|
import { Input, Text } from "./widgets/input"
|
||||||
import SpinBox from "./widgets/spinBox"
|
import SpinBox from "./widgets/spinBox"
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { TkinterWidgetBase } from "./base"
|
|||||||
class Button extends TkinterWidgetBase{
|
class Button extends TkinterWidgetBase{
|
||||||
|
|
||||||
static widgetType = "button"
|
static widgetType = "button"
|
||||||
|
static displayName = "Button"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -68,6 +69,7 @@ class Button extends TkinterWidgetBase{
|
|||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md
|
||||||
tw-border tw-border-solid tw-border-gray-400 tw-overflow-hidden">
|
tw-border tw-border-solid tw-border-gray-400 tw-overflow-hidden">
|
||||||
<div className="tw-p-2 tw-w-full tw-flex tw-place-content-center tw-place-items-center tw-h-full tw-text-center"
|
<div className="tw-p-2 tw-w-full tw-flex tw-place-content-center tw-place-items-center tw-h-full tw-text-center"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
{/* {this.props.children} */}
|
{/* {this.props.children} */}
|
||||||
<div className="tw-text-sm" style={{color: this.getAttrValue("styling.foregroundColor")}}>
|
<div className="tw-text-sm" style={{color: this.getAttrValue("styling.foregroundColor")}}>
|
||||||
|
|||||||
@@ -3,11 +3,15 @@ import Tools from "../../../canvas/constants/tools"
|
|||||||
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
|
import { convertObjectToKeyValueString, removeKeyFromObject } from "../../../utils/common"
|
||||||
import { CheckSquareFilled } from "@ant-design/icons"
|
import { CheckSquareFilled } from "@ant-design/icons"
|
||||||
import { TkinterWidgetBase } from "./base"
|
import { TkinterWidgetBase } from "./base"
|
||||||
|
import { Layouts } from "../../../canvas/constants/layouts"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
|
||||||
export class CheckBox extends TkinterWidgetBase{
|
export class CheckBox extends TkinterWidgetBase{
|
||||||
|
|
||||||
static widgetType = "check_button"
|
static widgetType = "check_button"
|
||||||
|
static displayName = "Check Box"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@@ -96,6 +100,7 @@ export class CheckBox extends TkinterWidgetBase{
|
|||||||
return (
|
return (
|
||||||
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
|
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
|
||||||
style={this.getInnerRenderStyling()}
|
style={this.getInnerRenderStyling()}
|
||||||
|
ref={this.styleAreaRef}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div className="tw-flex tw-gap-2 tw-w-full tw-h-full tw-place-items-center tw-place-content-center">
|
<div className="tw-flex tw-gap-2 tw-w-full tw-h-full tw-place-items-center tw-place-content-center">
|
||||||
@@ -124,25 +129,31 @@ export class RadioButton extends TkinterWidgetBase{
|
|||||||
// FIXME: the radio buttons are not visible because of the default heigh provided
|
// FIXME: the radio buttons are not visible because of the default heigh provided
|
||||||
|
|
||||||
static widgetType = "radio_button"
|
static widgetType = "radio_button"
|
||||||
|
static displayName = "Radio Button"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.minSize = {width: 50, height: 30}
|
this.minSize = {width: 50, height: 30}
|
||||||
|
|
||||||
|
let newAttrs = removeKeyFromObject("layout", this.state.attrs)
|
||||||
|
|
||||||
|
this.firstDivRef = React.createRef()
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
size: { width: 80, height: 30 },
|
size: { width: 80, height: 30 },
|
||||||
fitContent: { width: true, height: true },
|
fitContent: { width: true, height: true },
|
||||||
widgetName: "Radio button",
|
widgetName: "Radio button",
|
||||||
attrs: {
|
attrs: {
|
||||||
...this.state.attrs,
|
...newAttrs,
|
||||||
radios: {
|
radios: {
|
||||||
label: "Radio Group",
|
label: "Radio Group",
|
||||||
tool: Tools.INPUT_RADIO_LIST,
|
tool: Tools.INPUT_RADIO_LIST,
|
||||||
value: {inputs: ["default"], selectedRadio: -1},
|
value: {inputs: ["default"], selectedRadio: -1},
|
||||||
onChange: ({inputs, selectedRadio}) => {
|
onChange: ({inputs, selectedRadio}) => {
|
||||||
this.setAttrValue("radios", {inputs, selectedRadio})
|
this.setAttrValue("radios", {inputs, selectedRadio}, () => {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +167,49 @@ export class RadioButton extends TkinterWidgetBase{
|
|||||||
this.setWidgetInnerStyle("backgroundColor", "#fff0")
|
this.setWidgetInnerStyle("backgroundColor", "#fff0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* The index is required for pack as in pack every widget would be packed on top of each other in radio button
|
||||||
|
*/
|
||||||
|
getLayoutCode({index=0}){
|
||||||
|
let layoutManager = super.getLayoutCode()
|
||||||
|
|
||||||
|
const {layout: parentLayout, direction, gap, align="start"} = this.getParentLayout()
|
||||||
|
const absolutePositioning = this.getAttrValue("positioning")
|
||||||
|
|
||||||
|
|
||||||
|
if (parentLayout === Layouts.PLACE || absolutePositioning){
|
||||||
|
const config = {}
|
||||||
|
|
||||||
|
const elementRect = this.firstDivRef.current?.getBoundingClientRect()
|
||||||
|
|
||||||
|
config['x'] = Math.trunc(this.state.pos.x)
|
||||||
|
config['y'] = Math.trunc(this.state.pos.y)
|
||||||
|
|
||||||
|
if (elementRect?.height){
|
||||||
|
config['y'] = Math.trunc(config['y'] + (index * elementRect.height)) // the index is the radiobutton index
|
||||||
|
}
|
||||||
|
|
||||||
|
// config["width"] = Math.trunc(this.state.size.width)
|
||||||
|
// config["height"] = Math.trunc(this.state.size.height)
|
||||||
|
|
||||||
|
if (!this.state.fitContent.width){
|
||||||
|
config["width"] = Math.trunc(this.state.size.width)
|
||||||
|
}
|
||||||
|
if (!this.state.fitContent.height){
|
||||||
|
config["height"] = Math.trunc(this.state.size.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configStr = convertObjectToKeyValueString(config)
|
||||||
|
|
||||||
|
layoutManager = `place(${configStr})`
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return layoutManager
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
generateCode(variableName, parent){
|
generateCode(variableName, parent){
|
||||||
|
|
||||||
const config = convertObjectToKeyValueString(this.getConfigCode())
|
const config = convertObjectToKeyValueString(this.getConfigCode())
|
||||||
@@ -170,9 +224,10 @@ export class RadioButton extends TkinterWidgetBase{
|
|||||||
|
|
||||||
const radioBtnVariable = `${variableName}_${idx}`
|
const radioBtnVariable = `${variableName}_${idx}`
|
||||||
code.push(`\n`)
|
code.push(`\n`)
|
||||||
|
// TODO increment the next widget with the height of the previous
|
||||||
code.push(`${radioBtnVariable} = tk.Radiobutton(master=${parent}, variable=${variableName}_var, text="${radio_text}")`)
|
code.push(`${radioBtnVariable} = tk.Radiobutton(master=${parent}, variable=${variableName}_var, text="${radio_text}")`)
|
||||||
code.push(`${radioBtnVariable}.config(${config}, value=${idx})`)
|
code.push(`${radioBtnVariable}.config(${config}, value=${idx})`)
|
||||||
code.push(`${radioBtnVariable}.${this.getLayoutCode()}`)
|
code.push(`${radioBtnVariable}.${this.getLayoutCode({index: idx})}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
const defaultSelected = radios.selectedRadio
|
const defaultSelected = radios.selectedRadio
|
||||||
@@ -210,8 +265,13 @@ export class RadioButton extends TkinterWidgetBase{
|
|||||||
<div className="tw-flex tw-flex-col tw-gap-2 tw-w-fit tw-h-fit">
|
<div className="tw-flex tw-flex-col tw-gap-2 tw-w-fit tw-h-fit">
|
||||||
{
|
{
|
||||||
inputs.map((value, index) => {
|
inputs.map((value, index) => {
|
||||||
|
|
||||||
|
const ref = index === 0 ? this.firstDivRef : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index} className="tw-flex tw-gap-2 tw-w-full tw-h-full tw-place-items-center ">
|
<div key={index}
|
||||||
|
ref={ref}
|
||||||
|
className="tw-flex tw-gap-2 tw-w-full tw-h-full tw-place-items-center ">
|
||||||
<div className="tw-border-solid tw-border-[#D9D9D9] tw-border-2
|
<div className="tw-border-solid tw-border-[#D9D9D9] tw-border-2
|
||||||
tw-min-w-[20px] tw-min-h-[20px] tw-w-[20px] tw-h-[20px]
|
tw-min-w-[20px] tw-min-h-[20px] tw-w-[20px] tw-h-[20px]
|
||||||
tw-text-blue-600 tw-flex tw-items-center tw-justify-center
|
tw-text-blue-600 tw-flex tw-items-center tw-justify-center
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
|
import { Layouts } from "../../../canvas/constants/layouts"
|
||||||
|
import Tools from "../../../canvas/constants/tools"
|
||||||
import Widget from "../../../canvas/widgets/base"
|
import Widget from "../../../canvas/widgets/base"
|
||||||
|
import { convertObjectToKeyValueString } from "../../../utils/common"
|
||||||
import {TkinterBase} from "./base"
|
import {TkinterBase} from "./base"
|
||||||
|
|
||||||
|
|
||||||
class Frame extends TkinterBase{
|
class Frame extends TkinterBase{
|
||||||
|
|
||||||
static widgetType = "frame"
|
static widgetType = "frame"
|
||||||
|
static displayName = "Frame"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -16,7 +20,99 @@ class Frame extends TkinterBase{
|
|||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
fitContent: {width: true, height: true},
|
fitContent: {width: true, height: true},
|
||||||
widgetName: "Frame"
|
widgetName: "Frame",
|
||||||
|
attrs: {
|
||||||
|
...this.state.attrs,
|
||||||
|
padding: {
|
||||||
|
label: "padding",
|
||||||
|
padX: {
|
||||||
|
label: "Pad X",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
// this.setWidgetInnerStyle("paddingLeft", `${value}px`)
|
||||||
|
// this.setWidgetInnerStyle("paddingRight", `${value}px`)
|
||||||
|
|
||||||
|
// const widgetStyle = {
|
||||||
|
|
||||||
|
// }
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
|
||||||
|
widgetInnerStyling: {
|
||||||
|
...prevState.widgetInnerStyling,
|
||||||
|
paddingLeft: `${value}px`,
|
||||||
|
paddingRight: `${value}px`
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
this.setAttrValue("padding.padX", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
padY: {
|
||||||
|
label: "Pad Y",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
|
||||||
|
widgetInnerStyling: {
|
||||||
|
...prevState.widgetInnerStyling,
|
||||||
|
paddingTop: `${value}px`,
|
||||||
|
paddingBottom: `${value}px`
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
// this.setState({
|
||||||
|
|
||||||
|
// widgetInnerStyling: widgetStyle
|
||||||
|
// })
|
||||||
|
this.setAttrValue("padding.padY", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
label: "Margin",
|
||||||
|
marginX: {
|
||||||
|
label: "Margin X",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
|
||||||
|
this.updateState((prev) => ({
|
||||||
|
widgetOuterStyling: {
|
||||||
|
...prev.widgetOuterStyling,
|
||||||
|
marginLeft: `${value}px`,
|
||||||
|
marginRight: `${value}px`
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
this.setAttrValue("margin.marginX", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
marginY: {
|
||||||
|
label: "Margin Y",
|
||||||
|
tool: Tools.NUMBER_INPUT,
|
||||||
|
toolProps: {min: 0, max: 140},
|
||||||
|
value: null,
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
this.updateState((prev) => ({
|
||||||
|
widgetOuterStyling: {
|
||||||
|
...prev.widgetOuterStyling,
|
||||||
|
marginTop: `${value}px`,
|
||||||
|
marginBottom: `${value}px`
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
this.setAttrValue("margin.marginY", value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,18 +122,59 @@ class Frame extends TkinterBase{
|
|||||||
this.setAttrValue("styling.backgroundColor", "#EDECEC")
|
this.setAttrValue("styling.backgroundColor", "#EDECEC")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getConfigCode(){
|
||||||
|
const bg = this.getAttrValue("styling.backgroundColor")
|
||||||
|
|
||||||
|
const fitWidth = this.state.fitContent.width
|
||||||
|
const fitHeight = this.state.fitContent.height
|
||||||
|
|
||||||
|
const {width, height} = this.getSize()
|
||||||
|
|
||||||
|
const {layout} = this.getParentLayout()
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
bg: `"${bg}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layout !== Layouts.PLACE){
|
||||||
|
if (!fitWidth){
|
||||||
|
config['width'] = width
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fitHeight){
|
||||||
|
config['height'] = height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
generateCode(variableName, parent){
|
generateCode(variableName, parent){
|
||||||
|
|
||||||
const bg = this.getAttrValue("styling.backgroundColor")
|
const config = convertObjectToKeyValueString(this.getConfigCode())
|
||||||
|
|
||||||
return [
|
return [
|
||||||
`${variableName} = tk.Frame(master=${parent})`,
|
`${variableName} = tk.Frame(master=${parent})`,
|
||||||
`${variableName}.config(bg="${bg}")`,
|
`${variableName}.config(${config})`,
|
||||||
`${variableName}.${this.getLayoutCode()}`
|
`${variableName}.${this.getLayoutCode()}`,
|
||||||
|
...this.getGridLayoutConfigurationCode(variableName)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getToolbarAttrs(){
|
||||||
|
const {layout, gridConfig, gridWeights, ...toolBarAttrs} = super.getToolbarAttrs()
|
||||||
|
|
||||||
|
// places layout at the end
|
||||||
|
return ({
|
||||||
|
id: this.__id,
|
||||||
|
...toolBarAttrs,
|
||||||
|
padding: this.state.attrs.padding,
|
||||||
|
margin: this.state.attrs.margin,
|
||||||
|
layout,
|
||||||
|
gridConfig,
|
||||||
|
gridWeights
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
renderContent(){
|
renderContent(){
|
||||||
// console.log("bounding rect: ", this.getBoundingRect())
|
// console.log("bounding rect: ", this.getBoundingRect())
|
||||||
@@ -45,8 +182,10 @@ class Frame extends TkinterBase{
|
|||||||
// console.log("widget styling: ", this.state.widgetInnerStyling)
|
// console.log("widget styling: ", this.state.widgetInnerStyling)
|
||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-relative tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-relative tw-rounded-md tw-overflow-hidden">
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start" style={this.getInnerRenderStyling()}>
|
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start"
|
||||||
{this.props.children}
|
ref={this.styleAreaRef}
|
||||||
|
style={this.getInnerRenderStyling()}>
|
||||||
|
{this.renderTkinterLayout()} {/*This is required for pack layouts, so if your widget accepts child widgets, ensure to add this */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { TkinterWidgetBase } from "./base"
|
|||||||
export class Input extends TkinterWidgetBase{
|
export class Input extends TkinterWidgetBase{
|
||||||
|
|
||||||
static widgetType = "entry"
|
static widgetType = "entry"
|
||||||
|
static displayName = "Entry"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -40,7 +41,7 @@ export class Input extends TkinterWidgetBase{
|
|||||||
const config = convertObjectToKeyValueString(this.getConfigCode())
|
const config = convertObjectToKeyValueString(this.getConfigCode())
|
||||||
|
|
||||||
return [
|
return [
|
||||||
`${variableName} = tk.Entry(master=${parent}, text="${placeHolderText}")`,
|
`${variableName} = tk.Entry(master=${parent})`,
|
||||||
`${variableName}.config(${config})`,
|
`${variableName}.config(${config})`,
|
||||||
`${variableName}.${this.getLayoutCode()}`
|
`${variableName}.${this.getLayoutCode()}`
|
||||||
]
|
]
|
||||||
@@ -65,6 +66,7 @@ export class Input extends TkinterWidgetBase{
|
|||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-items-center"
|
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-items-center"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
<div className="tw-text-sm tw-text-gray-300">
|
<div className="tw-text-sm tw-text-gray-300">
|
||||||
{this.getAttrValue("placeHolder")}
|
{this.getAttrValue("placeHolder")}
|
||||||
@@ -138,6 +140,7 @@ export class Text extends TkinterWidgetBase{
|
|||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start "
|
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start "
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
<div className="tw-text-sm tw-text-gray-300">
|
<div className="tw-text-sm tw-text-gray-300">
|
||||||
{this.getAttrValue("placeHolder")}
|
{this.getAttrValue("placeHolder")}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { Layouts } from "../../../canvas/constants/layouts"
|
||||||
import Tools from "../../../canvas/constants/tools"
|
import Tools from "../../../canvas/constants/tools"
|
||||||
import { convertObjectToKeyValueString } from "../../../utils/common"
|
import { convertObjectToKeyValueString } from "../../../utils/common"
|
||||||
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
||||||
|
import { ANCHOR } from "../constants/styling"
|
||||||
import { TkinterWidgetBase } from "./base"
|
import { TkinterWidgetBase } from "./base"
|
||||||
|
|
||||||
|
|
||||||
class Label extends TkinterWidgetBase{
|
class Label extends TkinterWidgetBase{
|
||||||
|
|
||||||
static widgetType = "label"
|
static widgetType = "label"
|
||||||
|
static displayName = "Label"
|
||||||
|
|
||||||
|
// static requiredCustomPyFiles = ["imageLabel"]
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -19,6 +24,26 @@ class Label extends TkinterWidgetBase{
|
|||||||
size: { width: 80, height: 40 },
|
size: { width: 80, height: 40 },
|
||||||
attrs: {
|
attrs: {
|
||||||
...this.state.attrs,
|
...this.state.attrs,
|
||||||
|
styling: {
|
||||||
|
...this.state.attrs.styling,
|
||||||
|
anchor: {
|
||||||
|
label: "Text align",
|
||||||
|
tool: Tools.SELECT_DROPDOWN,
|
||||||
|
options: ANCHOR.map((val) => ({value: val, label: val})),
|
||||||
|
value: "",
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
this.setAttrValue("styling.anchor", value)
|
||||||
|
|
||||||
|
this.setState((prevState) => ({
|
||||||
|
widgetInnerStyle: {
|
||||||
|
...prevState,
|
||||||
|
...this.getAnchorStyle(value)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
labelWidget: {
|
labelWidget: {
|
||||||
label: "Text",
|
label: "Text",
|
||||||
tool: Tools.INPUT,
|
tool: Tools.INPUT,
|
||||||
@@ -33,6 +58,34 @@ class Label extends TkinterWidgetBase{
|
|||||||
value: "",
|
value: "",
|
||||||
onChange: (value) => this.setAttrValue("imageUpload", value)
|
onChange: (value) => this.setAttrValue("imageUpload", value)
|
||||||
},
|
},
|
||||||
|
imageSize: {
|
||||||
|
label: "Image size",
|
||||||
|
display: "horizontal",
|
||||||
|
// width: {
|
||||||
|
// label: "Width",
|
||||||
|
// tool: Tools.NUMBER_INPUT, // the tool to display, can be either HTML ELement or a constant string
|
||||||
|
// toolProps: { placeholder: "width", max: 3000, min: 1 },
|
||||||
|
// value: 150,
|
||||||
|
// onChange: (value) => this.setWidgetSize(value, null)
|
||||||
|
// },
|
||||||
|
// height: {
|
||||||
|
// label: "Height",
|
||||||
|
// tool: Tools.NUMBER_INPUT,
|
||||||
|
// toolProps: { placeholder: "height", max: 3000, min: 1 },
|
||||||
|
// value: 150,
|
||||||
|
// onChange: (value) => this.setWidgetSize(null, value)
|
||||||
|
// },
|
||||||
|
mode: {
|
||||||
|
label: "Image mode",
|
||||||
|
tool: Tools.SELECT_DROPDOWN,
|
||||||
|
options: ["fit", "cover"].map((val) => ({value: val, label: val})),
|
||||||
|
value: "cover",
|
||||||
|
onChange: (value) => {
|
||||||
|
|
||||||
|
this.setAttrValue("imageSize.mode", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,11 +104,20 @@ class Label extends TkinterWidgetBase{
|
|||||||
const imports = super.getImports()
|
const imports = super.getImports()
|
||||||
|
|
||||||
if (this.getAttrValue("imageUpload"))
|
if (this.getAttrValue("imageUpload"))
|
||||||
imports.push("import os", "from PIL import Image, ImageTk", )
|
imports.push("import os", "from PIL import Image, ImageTk", "from customWidgets.imageLabel import ImageLabel")
|
||||||
|
|
||||||
return imports
|
return imports
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRequiredCustomPyFiles(){
|
||||||
|
const requiredCustomFiles = super.getRequiredCustomPyFiles()
|
||||||
|
|
||||||
|
if (this.getAttrValue("imageUpload"))
|
||||||
|
requiredCustomFiles.push("imageLabel")
|
||||||
|
|
||||||
|
return requiredCustomFiles
|
||||||
|
}
|
||||||
|
|
||||||
getRequirements(){
|
getRequirements(){
|
||||||
const requirements = super.getRequirements()
|
const requirements = super.getRequirements()
|
||||||
|
|
||||||
@@ -66,10 +128,40 @@ class Label extends TkinterWidgetBase{
|
|||||||
return requirements
|
return requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getConfigCode(){
|
||||||
|
const config = super.getConfigCode()
|
||||||
|
|
||||||
|
const anchor = this.getAttrValue("styling.anchor")
|
||||||
|
const fitWidth = this.state.fitContent.width
|
||||||
|
const fitHeight = this.state.fitContent.height
|
||||||
|
|
||||||
|
const {width, height} = this.getSize()
|
||||||
|
|
||||||
|
const {layout} = this.getParentLayout()
|
||||||
|
|
||||||
|
if (anchor)
|
||||||
|
config['anchor'] = `"${anchor}"`
|
||||||
|
|
||||||
|
// LABEL width and height are not pixel based instead its character based
|
||||||
|
// if (layout !== Layouts.PLACE){
|
||||||
|
// if (!fitWidth){
|
||||||
|
// config['width'] = width
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!fitHeight){
|
||||||
|
// config['height'] = height
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
generateCode(variableName, parent){
|
generateCode(variableName, parent){
|
||||||
|
|
||||||
|
|
||||||
const labelText = this.getAttrValue("labelWidget")
|
const labelText = this.getAttrValue("labelWidget")
|
||||||
|
|
||||||
const config = convertObjectToKeyValueString(this.getConfigCode())
|
const config = convertObjectToKeyValueString(this.getConfigCode())
|
||||||
const image = this.getAttrValue("imageUpload")
|
const image = this.getAttrValue("imageUpload")
|
||||||
|
|
||||||
@@ -78,10 +170,10 @@ class Label extends TkinterWidgetBase{
|
|||||||
const code = []
|
const code = []
|
||||||
|
|
||||||
if (image?.name){
|
if (image?.name){
|
||||||
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(image.name, "image")})`)
|
// code.push(`${variableName}_img = Image.open(${getPythonAssetPath(image.name, "image")})`)
|
||||||
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
// code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||||
// code.push("\n")
|
// code.push("\n")
|
||||||
labelInitialization = `${variableName} = tk.Label(master=${parent}, image=${variableName}_img, text="${labelText}", compound=tk.TOP)`
|
labelInitialization = `${variableName} = ImageLabel(master=${parent}, image_path=${getPythonAssetPath(image.name, "image")}, text="${labelText}", compound=tk.TOP, mode="${this.getAttrValue("imageSize.mode")}")`
|
||||||
}
|
}
|
||||||
|
|
||||||
// code.push("\n")
|
// code.push("\n")
|
||||||
@@ -108,29 +200,66 @@ class Label extends TkinterWidgetBase{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent(){
|
getAnchorStyle = (anchor) => {
|
||||||
|
const anchorStyles = {
|
||||||
|
n: { justifyContent: 'center', alignItems: 'flex-start' },
|
||||||
|
s: { justifyContent: 'center', alignItems: 'flex-end' },
|
||||||
|
e: { justifyContent: 'flex-end', alignItems: 'center' },
|
||||||
|
w: { justifyContent: 'flex-start', alignItems: 'center' },
|
||||||
|
ne: { justifyContent: 'flex-end', alignItems: 'flex-start' },
|
||||||
|
se: { justifyContent: 'flex-end', alignItems: 'flex-end' },
|
||||||
|
nw: { justifyContent: 'flex-start', alignItems: 'flex-start' },
|
||||||
|
sw: { justifyContent: 'flex-start', alignItems: 'flex-end' },
|
||||||
|
center: { justifyContent: 'center', alignItems: 'center' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return anchorStyles[anchor] || anchorStyles["center"];
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent(){
|
||||||
|
//FIXME: label image causing issues
|
||||||
const image = this.getAttrValue("imageUpload")
|
const image = this.getAttrValue("imageUpload")
|
||||||
|
const imageMode = this.getAttrValue("imageSize.mode") || "cover"
|
||||||
|
|
||||||
|
const imgClassName = imageMode === "fit" ? "tw-object-contain" : (imageMode === "cover" ? "tw-object-cover" : "")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-content-start tw-h-full tw-rounded-md tw-overflow-hidden"
|
<div className="tw-flex tw-flex-col tw-w-full tw-relative tw-content-start tw-h-full tw-rounded-md tw-overflow-hidden"
|
||||||
style={{
|
// style={{
|
||||||
flexGrow: 1, // Ensure the content grows to fill the parent
|
// flexGrow: 1, // Ensure the content grows to fill the parent
|
||||||
minWidth: '100%', // Force the width to 100% of the parent
|
// minWidth: '100%', // Force the width to 100% of the parent
|
||||||
minHeight: '100%', // Force the height to 100% of the parent
|
// minHeight: '100%', // Force the height to 100% of the parent
|
||||||
}}
|
// }}
|
||||||
>
|
>
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-content-center tw-place-items-center "
|
<div className="tw-p-2 tw-w-full tw-h-full tw-overflow-hidden tw-flex tw-place-content-center tw-place-items-center"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
{/* {this.props.children} */}
|
{/* {this.props.children} */}
|
||||||
{
|
{
|
||||||
image && (
|
image ? (
|
||||||
<img src={image.previewUrl} className="tw-bg-contain tw-w-full tw-h-full" />
|
<div className="tw-relative tw-w-full tw-h-full tw-overflow-hidden">
|
||||||
)
|
<img src={image.previewUrl}
|
||||||
}
|
className={`${imgClassName}`}
|
||||||
<div className="" style={{color: this.getAttrValue("styling.foregroundColor")}}>
|
alt={this.getAttrValue("widgetName")}
|
||||||
{this.getAttrValue("labelWidget")}
|
style={{
|
||||||
</div>
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
position: "absolute",
|
||||||
|
top: '0px',
|
||||||
|
left: '0px',
|
||||||
|
|
||||||
|
}}
|
||||||
|
// className="tw-object-cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
<div className={`tw-flex ${!image ? "tw-w-full tw-h-full" : ""}`} style={{
|
||||||
|
color: this.getAttrValue("styling.foregroundColor"),
|
||||||
|
...this.getAnchorStyle(this.getAttrValue("styling.anchor"))
|
||||||
|
}}>
|
||||||
|
{this.getAttrValue("labelWidget")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -139,4 +268,44 @@ class Label extends TkinterWidgetBase{
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function LabelImage({imageSrc, alt, styleAreaRef}){
|
||||||
|
|
||||||
|
const [size, setSize] = useState({
|
||||||
|
width: 0,
|
||||||
|
height: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!styleAreaRef.current) return;
|
||||||
|
|
||||||
|
// Function to update size
|
||||||
|
const updateSize = () => {
|
||||||
|
const boundingBox = styleAreaRef.current.getBoundingClientRect();
|
||||||
|
setSize({ width: boundingBox.width, height: boundingBox.height });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial size update
|
||||||
|
updateSize();
|
||||||
|
|
||||||
|
// Observe size changes
|
||||||
|
const resizeObserver = new ResizeObserver(updateSize);
|
||||||
|
resizeObserver.observe(styleAreaRef.current);
|
||||||
|
|
||||||
|
return () => resizeObserver.disconnect();
|
||||||
|
}, [styleAreaRef])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img src={imageSrc} alt={alt} className="tw-object-cover"
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: '0px',
|
||||||
|
left: '0px',
|
||||||
|
width: "100%", // Prevent overflow
|
||||||
|
height: "100%", // Prevent overflow
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default Label
|
export default Label
|
||||||
@@ -1,11 +1,19 @@
|
|||||||
import Widget from "../../../canvas/widgets/base"
|
import Widget from "../../../canvas/widgets/base"
|
||||||
import Tools from "../../../canvas/constants/tools"
|
import Tools from "../../../canvas/constants/tools"
|
||||||
import { TkinterBase } from "./base"
|
import { TkinterBase } from "./base"
|
||||||
|
import { removeKeyFromObject } from "../../../utils/common"
|
||||||
|
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
||||||
|
|
||||||
|
|
||||||
class MainWindow extends TkinterBase{
|
class MainWindow extends TkinterBase{
|
||||||
|
|
||||||
static widgetType = "main_window"
|
static widgetType = "main_window"
|
||||||
|
static displayName = "Main Window"
|
||||||
|
|
||||||
|
static initialSize = {
|
||||||
|
width: 700,
|
||||||
|
height: 400
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -14,18 +22,27 @@ class MainWindow extends TkinterBase{
|
|||||||
exclude: ["image", "video", "media", "main_window", "toplevel"]
|
exclude: ["image", "video", "media", "main_window", "toplevel"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newAttrs = removeKeyFromObject("margin", this.state.attrs)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
size: { width: 700, height: 400 },
|
size: { width: MainWindow.initialSize.width, height: MainWindow.initialSize.height },
|
||||||
widgetName: "main",
|
widgetName: "main",
|
||||||
attrs: {
|
attrs: {
|
||||||
...this.state.attrs,
|
...newAttrs,
|
||||||
title: {
|
title: {
|
||||||
label: "Window Title",
|
label: "Window Title",
|
||||||
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
|
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
|
||||||
toolProps: {placeholder: "Window title", maxLength: 40},
|
toolProps: {placeholder: "Window title", maxLength: 40},
|
||||||
value: "Main Window",
|
value: "Main Window",
|
||||||
onChange: (value) => this.setAttrValue("title", value)
|
onChange: (value) => this.setAttrValue("title", value)
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
label: "Window Logo",
|
||||||
|
tool: Tools.UPLOADED_LIST,
|
||||||
|
toolProps: {filterOptions: ["image/jpg", "image/jpeg", "image/png"]},
|
||||||
|
value: "",
|
||||||
|
onChange: (value) => this.setAttrValue("logo", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -33,29 +50,58 @@ class MainWindow extends TkinterBase{
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// componentDidMount(){
|
// componentDidMount(){
|
||||||
|
// // this.setAttrValue("styling.backgroundColor", "#E4E2E2")
|
||||||
// super.componentDidMount()
|
// super.componentDidMount()
|
||||||
|
|
||||||
// //this.setAttrValue("styling", { backgroundColor: "#E4E2E2" }) refactor to something like this?
|
|
||||||
// this.setAttrValue("styling.backgroundColor", "#E4E2E2")
|
|
||||||
// console.log("mounted: ", this.state)
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
componentDidMount(){
|
|
||||||
this.setAttrValue("styling.backgroundColor", "#E4E2E2")
|
|
||||||
super.componentDidMount()
|
|
||||||
}
|
|
||||||
|
|
||||||
generateCode(variableName, parent){
|
generateCode(variableName, parent){
|
||||||
|
|
||||||
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
||||||
|
|
||||||
return [
|
const logo = this.getAttrValue("logo")
|
||||||
`${variableName} = tk.Tk()`,
|
|
||||||
`${variableName}.config(bg="${backgroundColor}")`,
|
const {width, height} = this.getSize()
|
||||||
`${variableName}.title("${this.getAttrValue("title")}")`
|
|
||||||
]
|
const code = [
|
||||||
|
`${variableName} = tk.Tk()`,
|
||||||
|
`${variableName}.config(bg="${backgroundColor}")`,
|
||||||
|
`${variableName}.title("${this.getAttrValue("title")}")`,
|
||||||
|
`${variableName}.geometry("${width}x${height}")`,
|
||||||
|
...this.getGridLayoutConfigurationCode(variableName)
|
||||||
|
]
|
||||||
|
|
||||||
|
if (logo?.name){
|
||||||
|
|
||||||
|
// code.push(`\n`)
|
||||||
|
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(logo.name, "image")})`)
|
||||||
|
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||||
|
code.push(`${variableName}.iconphoto(False, ${variableName}_img)`)
|
||||||
|
// code.push("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
getImports(){
|
||||||
|
const imports = super.getImports()
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
imports.push("import os", "from PIL import Image, ImageTk", )
|
||||||
|
|
||||||
|
return imports
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequirements(){
|
||||||
|
const requirements = super.getRequirements()
|
||||||
|
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
requirements.push("pillow")
|
||||||
|
|
||||||
|
return requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolbarAttrs(){
|
getToolbarAttrs(){
|
||||||
@@ -65,18 +111,28 @@ class MainWindow extends TkinterBase{
|
|||||||
id: this.__id,
|
id: this.__id,
|
||||||
widgetName: toolBarAttrs.widgetName,
|
widgetName: toolBarAttrs.widgetName,
|
||||||
title: this.state.attrs.title,
|
title: this.state.attrs.title,
|
||||||
|
logo: this.state.attrs.logo,
|
||||||
size: toolBarAttrs.size,
|
size: toolBarAttrs.size,
|
||||||
|
|
||||||
...this.state.attrs,
|
...this.state.attrs,
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent(){
|
renderContent(){
|
||||||
|
|
||||||
|
const logo = this.getAttrValue("logo")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||||
|
|
||||||
<div className="tw-flex tw-w-full tw-h-[25px] tw-bg-[#c7c7c7] tw-p-1
|
<div className="tw-flex tw-w-full tw-h-[25px] tw-bg-[#c7c7c7] tw-p-1
|
||||||
tw-overflow-hidden tw-shadow-xl tw-place-items-center">
|
tw-overflow-hidden tw-shadow-xl tw-place-items-center tw-gap-1">
|
||||||
|
{
|
||||||
|
logo && (
|
||||||
|
<img src={logo.previewUrl} alt={logo.name}
|
||||||
|
className="tw-bg-contain tw-w-[15px] tw-h-[15px] tw-rounded-sm" />
|
||||||
|
)
|
||||||
|
}
|
||||||
<div className="tw-text-sm">{this.getAttrValue("title")}</div>
|
<div className="tw-text-sm">{this.getAttrValue("title")}</div>
|
||||||
<div className="tw-ml-auto tw-flex tw-gap-1 tw-place-items-center">
|
<div className="tw-ml-auto tw-flex tw-gap-1 tw-place-items-center">
|
||||||
<div className="tw-bg-yellow-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
|
<div className="tw-bg-yellow-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
|
||||||
@@ -88,8 +144,10 @@ class MainWindow extends TkinterBase{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-p-2 tw-w-full tw-relative tw-h-full tw-overflow-hidden tw-content-start"
|
<div className="tw-p-2 tw-w-full tw-relative tw-h-full tw-overflow-hidden tw-content-start"
|
||||||
style={this.getInnerRenderStyling()}>
|
ref={this.styleAreaRef}
|
||||||
{this.props.children}
|
style={{...this.getInnerRenderStyling(), width: "100%", height: "calc(100% - 25px)"}}>
|
||||||
|
{/* {this.props.children} */}
|
||||||
|
{this.renderTkinterLayout()} {/* This is required for pack layouts, so if your widget accepts child widgets, ensure to add this */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { convertObjectToKeyValueString } from "../../../utils/common"
|
|||||||
class OptionMenu extends TkinterWidgetBase{
|
class OptionMenu extends TkinterWidgetBase{
|
||||||
|
|
||||||
static widgetType = "option_menu"
|
static widgetType = "option_menu"
|
||||||
|
static displayName = "Option Menu"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -27,8 +28,8 @@ class OptionMenu extends TkinterWidgetBase{
|
|||||||
label: "Default Value",
|
label: "Default Value",
|
||||||
tool: Tools.INPUT,
|
tool: Tools.INPUT,
|
||||||
value: "Select option",
|
value: "Select option",
|
||||||
onChange: ({inputs, selectedRadio}) => {
|
onChange: (value) => {
|
||||||
this.setAttrValue("options", {inputs, selectedRadio})
|
this.setAttrValue("defaultValue", value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
widgetOptions: {
|
widgetOptions: {
|
||||||
@@ -100,6 +101,7 @@ class OptionMenu extends TkinterWidgetBase{
|
|||||||
return (
|
return (
|
||||||
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
|
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
|
||||||
style={this.getInnerRenderStyling()}
|
style={this.getInnerRenderStyling()}
|
||||||
|
ref={this.styleAreaRef}
|
||||||
onClick={this.toggleDropDownOpen}
|
onClick={this.toggleDropDownOpen}
|
||||||
>
|
>
|
||||||
<div className="tw-flex tw-justify-between tw-gap-1">
|
<div className="tw-flex tw-justify-between tw-gap-1">
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import {TkinterBase, TkinterWidgetBase} from "./base"
|
|||||||
class Slider extends TkinterWidgetBase{
|
class Slider extends TkinterWidgetBase{
|
||||||
|
|
||||||
static widgetType = "scale"
|
static widgetType = "scale"
|
||||||
|
static displayName = "Scale"
|
||||||
|
|
||||||
// FIXME: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use
|
// FIXME: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -136,7 +138,9 @@ class Slider extends TkinterWidgetBase{
|
|||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||||
<div className="flex flex-col items-center justify-center h-screen
|
<div className="flex flex-col items-center justify-center h-screen
|
||||||
bg-gray-100" style={this.getInnerRenderStyling()}>
|
bg-gray-100"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
|
style={this.getInnerRenderStyling()}>
|
||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {TkinterBase, TkinterWidgetBase} from "./base"
|
|||||||
class SpinBox extends TkinterWidgetBase{
|
class SpinBox extends TkinterWidgetBase{
|
||||||
|
|
||||||
static widgetType = "spin_box"
|
static widgetType = "spin_box"
|
||||||
|
static displayName = "Spin Box"
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -109,6 +110,7 @@ class SpinBox extends TkinterWidgetBase{
|
|||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-items-center tw-justify-between"
|
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-items-center tw-justify-between"
|
||||||
|
ref={this.styleAreaRef}
|
||||||
style={this.getInnerRenderStyling()}>
|
style={this.getInnerRenderStyling()}>
|
||||||
<div className="tw-text-sm ">
|
<div className="tw-text-sm ">
|
||||||
{this.getAttrValue("spinProps.default")}
|
{this.getAttrValue("spinProps.default")}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import Widget from "../../../canvas/widgets/base"
|
import Widget from "../../../canvas/widgets/base"
|
||||||
import Tools from "../../../canvas/constants/tools"
|
import Tools from "../../../canvas/constants/tools"
|
||||||
|
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
||||||
|
import { TkinterBase } from "./base"
|
||||||
|
|
||||||
|
|
||||||
class TopLevel extends Widget{
|
class TopLevel extends TkinterBase{
|
||||||
|
|
||||||
static widgetType = "toplevel"
|
static widgetType = "toplevel"
|
||||||
|
static displayName = "Top Level"
|
||||||
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -26,6 +30,13 @@ class TopLevel extends Widget{
|
|||||||
toolProps: {placeholder: "Window title", maxLength: 40},
|
toolProps: {placeholder: "Window title", maxLength: 40},
|
||||||
value: "Top level",
|
value: "Top level",
|
||||||
onChange: (value) => this.setAttrValue("title", value)
|
onChange: (value) => this.setAttrValue("title", value)
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
label: "Toplevel Logo",
|
||||||
|
tool: Tools.UPLOADED_LIST,
|
||||||
|
toolProps: {filterOptions: ["image/jpg", "image/jpeg", "image/png"]},
|
||||||
|
value: "",
|
||||||
|
onChange: (value) => this.setAttrValue("logo", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -37,15 +48,54 @@ class TopLevel extends Widget{
|
|||||||
super.componentDidMount()
|
super.componentDidMount()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
generateCode(variableName, parent){
|
generateCode(variableName, parent){
|
||||||
|
|
||||||
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
const backgroundColor = this.getAttrValue("styling.backgroundColor")
|
||||||
|
|
||||||
return [
|
const logo = this.getAttrValue("logo")
|
||||||
`${variableName} = tk.Toplevel(master=${parent})`,
|
|
||||||
`${variableName}.config(bg="${backgroundColor}")`,
|
const {width, height} = this.getSize()
|
||||||
`${variableName}.title("${this.getAttrValue("title")}")`
|
|
||||||
]
|
const code = [
|
||||||
|
`${variableName} = tk.Toplevel(master=${parent})`,
|
||||||
|
`${variableName}.config(bg="${backgroundColor}")`,
|
||||||
|
`${variableName}.title("${this.getAttrValue("title")}")`,
|
||||||
|
`${variableName}.geometry("${width}x${height}")`,
|
||||||
|
|
||||||
|
...this.getGridLayoutConfigurationCode(variableName)
|
||||||
|
]
|
||||||
|
|
||||||
|
if (logo?.name){
|
||||||
|
|
||||||
|
// code.push(`\n`)
|
||||||
|
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(logo.name, "image")})`)
|
||||||
|
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||||
|
code.push(`${variableName}.iconphoto(False, ${variableName}_img)`)
|
||||||
|
// code.push("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
getImports(){
|
||||||
|
const imports = super.getImports()
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
imports.push("import os", "from PIL import Image, ImageTk", )
|
||||||
|
|
||||||
|
return imports
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequirements(){
|
||||||
|
const requirements = super.getRequirements()
|
||||||
|
|
||||||
|
|
||||||
|
if (this.getAttrValue("logo"))
|
||||||
|
requirements.push("pillow")
|
||||||
|
|
||||||
|
return requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolbarAttrs(){
|
getToolbarAttrs(){
|
||||||
@@ -54,18 +104,26 @@ class TopLevel extends Widget{
|
|||||||
return ({
|
return ({
|
||||||
widgetName: toolBarAttrs.widgetName,
|
widgetName: toolBarAttrs.widgetName,
|
||||||
title: this.state.attrs.title,
|
title: this.state.attrs.title,
|
||||||
|
logo: this.state.attrs.logo,
|
||||||
size: toolBarAttrs.size,
|
size: toolBarAttrs.size,
|
||||||
|
|
||||||
...this.state.attrs,
|
...this.state.attrs,
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent(){
|
renderContent(){
|
||||||
|
const logo = this.getAttrValue("logo")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
|
||||||
<div className="tw-flex tw-w-full tw-h-[25px] tw-bg-[#c7c7c7] tw-p-1
|
<div className="tw-flex tw-w-full tw-h-[25px] tw-bg-[#c7c7c7] tw-p-1 tw-gap-1
|
||||||
tw-overflow-hidden tw-shadow-xl tw-place-items-center">
|
tw-overflow-hidden tw-shadow-xl tw-place-items-center">
|
||||||
|
{
|
||||||
|
logo && (
|
||||||
|
<img src={logo.previewUrl} alt={logo.name}
|
||||||
|
className="tw-bg-contain tw-w-[15px] tw-h-[15px] tw-rounded-sm" />
|
||||||
|
)
|
||||||
|
}
|
||||||
<div className="tw-text-sm">{this.getAttrValue("title")}</div>
|
<div className="tw-text-sm">{this.getAttrValue("title")}</div>
|
||||||
<div className="tw-ml-auto tw-flex tw-gap-1 tw-place-items-center">
|
<div className="tw-ml-auto tw-flex tw-gap-1 tw-place-items-center">
|
||||||
<div className="tw-bg-yellow-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
|
<div className="tw-bg-yellow-400 tw-rounded-full tw-w-[15px] tw-h-[15px]">
|
||||||
@@ -76,8 +134,10 @@ class TopLevel extends Widget{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start" style={this.state.widgetInnerStyling}>
|
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start"
|
||||||
{this.props.children}
|
ref={this.styleAreaRef}
|
||||||
|
style={this.state.widgetInnerStyling}>
|
||||||
|
{this.renderTkinterLayout()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,8 +11,12 @@ import { QueryClient, QueryClientProvider } from "react-query";
|
|||||||
|
|
||||||
import "./styles/tailwind.css";
|
import "./styles/tailwind.css";
|
||||||
import "./styles/index.css";
|
import "./styles/index.css";
|
||||||
|
|
||||||
import { FileUploadProvider } from "./contexts/fileUploadContext";
|
import { FileUploadProvider } from "./contexts/fileUploadContext";
|
||||||
|
|
||||||
|
window.React = React
|
||||||
|
|
||||||
|
|
||||||
const originalSetItem = localStorage.setItem;
|
const originalSetItem = localStorage.setItem;
|
||||||
// triggers itemsChaned event whenever the item in localstorage is chanegd.
|
// triggers itemsChaned event whenever the item in localstorage is chanegd.
|
||||||
localStorage.setItem = function (key, value) {
|
localStorage.setItem = function (key, value) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import ButtonWidget from "../assets/widgets/button.png"
|
|||||||
import { filterObjectListStartingWith } from "../utils/filter"
|
import { filterObjectListStartingWith } from "../utils/filter"
|
||||||
import Widget from "../canvas/widgets/base"
|
import Widget from "../canvas/widgets/base"
|
||||||
import { SearchComponent } from "../components/inputs"
|
import { SearchComponent } from "../components/inputs"
|
||||||
|
import { Button } from "antd"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,6 +73,13 @@ function PluginsContainer({sidebarContent, onWidgetsUpdate}){
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Button href="https://github.com/PaulleDemon/PyUIBuilder/issues"
|
||||||
|
target="_blank" rel="noopener noreferrer"
|
||||||
|
className="tw-flex tw-mt-6 btn"
|
||||||
|
>
|
||||||
|
Request new plugin
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
import { useEffect, useRef, useMemo, useState } from "react";
|
import { useEffect, useRef, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { BookOutlined, CloseCircleFilled, CrownFilled, GithubFilled, ShareAltOutlined } from "@ant-design/icons";
|
import { BookOutlined, CloseCircleFilled, CrownFilled, DiscordFilled, GithubFilled, InfoCircleOutlined, ShareAltOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
import KO_FI from "../assets/logo/ko-fi.png"
|
import KO_FI from "../assets/logo/ko-fi.png"
|
||||||
import Premium from "./utils/premium";
|
import Premium from "./utils/premium";
|
||||||
import Share from "./utils/share";
|
import Share from "./utils/share";
|
||||||
|
import { Tooltip } from "antd";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ function Sidebar({tabs}){
|
|||||||
>
|
>
|
||||||
|
|
||||||
|
|
||||||
<div className="tw-min-w-[80px] tw-w-[80px] tw-h-full tw-flex tw-flex-col tw-gap-4 tw-p-3 tw-place-items-center">
|
<div className="tw-min-w-[80px] tw-w-[80px] tw-bg-gray-100 tw-h-full tw-flex tw-flex-col tw-gap-4 tw-p-3 tw-place-items-center">
|
||||||
{
|
{
|
||||||
sidebarTabs.map((tab, index) => {
|
sidebarTabs.map((tab, index) => {
|
||||||
return (
|
return (
|
||||||
@@ -88,6 +89,21 @@ function Sidebar({tabs}){
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div className="tw-flex tw-flex-col tw-place-content-items tw-place-items-center tw-gap-3 tw-mt-auto">
|
<div className="tw-flex tw-flex-col tw-place-content-items tw-place-items-center tw-gap-3 tw-mt-auto">
|
||||||
|
|
||||||
|
<Tooltip title="About">
|
||||||
|
<a href="https://about.pyuibuilder.com" target="_blank"
|
||||||
|
title="about"
|
||||||
|
rel="noopener noreferrer" className="tw-text-2xl tw-text-black tw-cursor-pointer">
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Discord invite">
|
||||||
|
<a href="https://discord.gg/dHXjrrCA7G" target="_blank"
|
||||||
|
title="discord invite"
|
||||||
|
rel="noopener noreferrer" className="tw-text-3xl tw-cursor-pointer tw-text-[#5562EA]">
|
||||||
|
<DiscordFilled />
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
<Premium className="tw-text-2xl tw-bg-purple-700 tw-text-center
|
<Premium className="tw-text-2xl tw-bg-purple-700 tw-text-center
|
||||||
tw-w-[35px] tw-h-[35px] tw-rounded-md
|
tw-w-[35px] tw-h-[35px] tw-rounded-md
|
||||||
tw-cursor-pointer tw-text-white
|
tw-cursor-pointer tw-text-white
|
||||||
@@ -95,25 +111,33 @@ function Sidebar({tabs}){
|
|||||||
hover:tw-scale-[1.2]">
|
hover:tw-scale-[1.2]">
|
||||||
<CrownFilled />
|
<CrownFilled />
|
||||||
</Premium>
|
</Premium>
|
||||||
|
|
||||||
<Share className="tw-cursor-pointer tw-text-xl">
|
<Share className="tw-cursor-pointer tw-text-xl">
|
||||||
<ShareAltOutlined />
|
<Tooltip title="Share">
|
||||||
|
<ShareAltOutlined />
|
||||||
|
</Tooltip>
|
||||||
</Share>
|
</Share>
|
||||||
<a className="tw-cursor-pointer tw-text-xl tw-text-gray-700"
|
|
||||||
href="https://pyuibuilder-docs.pages.dev/"
|
<Tooltip title="Docs">
|
||||||
target="_blank" rel="noopener noreferrer">
|
<a className="tw-cursor-pointer tw-text-xl tw-text-gray-700"
|
||||||
<i className="bi bi-book-half"></i>
|
href="https://docs.pyuibuilder.com/"
|
||||||
</a>
|
target="_blank" rel="noopener noreferrer">
|
||||||
|
<i className="bi bi-book-half"></i>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
<a href="https://github.com/PaulleDemon/PyUIBuilder" target="_blank"
|
<a href="https://github.com/PaulleDemon/PyUIBuilder" target="_blank"
|
||||||
rel="noopener noreferrer" className="tw-text-2xl tw-cursor-pointer tw-text-black">
|
rel="noopener noreferrer" className="tw-text-2xl tw-cursor-pointer tw-text-black">
|
||||||
<GithubFilled />
|
<GithubFilled />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://ko-fi.com/artpaul" className="tw-cursor-pointer ">
|
{/* <a href="https://ko-fi.com/artpaul" className="tw-cursor-pointer ">
|
||||||
<img src={KO_FI} alt="ko-fi" className="tw-w-[30px] tw-h-[30px]"/>
|
<img src={KO_FI} alt="ko-fi" className="tw-w-[30px] tw-h-[30px]"/>
|
||||||
</a>
|
</a> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="tw-w-full tw-h-full tw-bg-inherit tw-flex tw-flex-col tw-overflow-x-hidden" ref={sideBarExtraRef}>
|
<div className="tw-w-full tw-h-full tw-bg-inherit tw-rounded-lg
|
||||||
|
tw-shadow-[rgb(207,207,207)] tw-shadow-[rgb(207,207,207,0.5)_-3px_0px_10px_] tw-flex tw-flex-col tw-overflow-x-hidden"
|
||||||
|
ref={sideBarExtraRef}>
|
||||||
<div className="tw-w-full tw-h-[50px] tw-flex tw-place-content-end tw-p-1">
|
<div className="tw-w-full tw-h-[50px] tw-flex tw-place-content-end tw-p-1">
|
||||||
<button className="tw-outline-none tw-bg-transparent tw-border-none tw-text-gray-600 tw-cursor-pointer tw-text-xl"
|
<button className="tw-outline-none tw-bg-transparent tw-border-none tw-text-gray-600 tw-cursor-pointer tw-text-xl"
|
||||||
onClick={closeSidebar}
|
onClick={closeSidebar}
|
||||||
|
|||||||
@@ -50,10 +50,13 @@ function TemplatesContainer(){
|
|||||||
|
|
||||||
<div className="tw-flex tw-flex-col tw-mt-6 tw-gap-2">
|
<div className="tw-flex tw-flex-col tw-mt-6 tw-gap-2">
|
||||||
<div>Want to be notified of the release?</div>
|
<div>Want to be notified of the release?</div>
|
||||||
<a href="https://paulfreeman.substack.com/subscribe?utm_source=pyGUIBuilder_web"
|
<button data-tally-open="mVDY7N" data-tally-layout="modal" data-tally-emoji-text="👋"
|
||||||
|
data-tally-emoji-animation="wave" className="tw-p-2 tw-w-full tw-outline-none tw-bg-blue-500 tw-rounded-md tw-no-underline
|
||||||
|
tw-text-white tw-text-center tw-py-3 tw-border-none tw-text-base tw-shadow-md tw-cursor-pointer">Join the waitlist</button>
|
||||||
|
{/* <a href="https://paulfreeman.substack.com/subscribe?utm_source=pyGUIBuilder_web"
|
||||||
className="tw-p-2 tw-w-full tw-bg-blue-500 tw-rounded-md tw-no-underline
|
className="tw-p-2 tw-w-full tw-bg-blue-500 tw-rounded-md tw-no-underline
|
||||||
tw-text-white tw-text-center tw-py-3"
|
tw-text-white tw-text-center tw-py-3"
|
||||||
target="_blank" rel="noreferrer noopener">Subscribe to newsletter</a>
|
target="_blank" rel="noreferrer noopener">Join the waitlist</a> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
76
src/sidebar/treeviewContainer.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { useEffect, useMemo, useState } from "react"
|
||||||
|
|
||||||
|
import { CloseCircleFilled, SearchOutlined } from "@ant-design/icons"
|
||||||
|
|
||||||
|
import { SidebarWidgetCard, TreeViewCard } from "../components/cards"
|
||||||
|
|
||||||
|
|
||||||
|
import { Tree } from "antd"
|
||||||
|
import { useWidgetContext } from "../canvas/context/widgetContext"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function transformWidgets(widgets, widgetRefs, isTopLevel=true) {
|
||||||
|
// console.log("Wdiegts refs: ", widgetRefs)
|
||||||
|
return widgets.map(widget => ({
|
||||||
|
title: widgetRefs.current[widget.id].current.getDisplayName(), // Assuming widgetType is a class
|
||||||
|
key: widget.id,
|
||||||
|
isTopLevel: isTopLevel,
|
||||||
|
widgetRef: widgetRefs.current[widget.id],
|
||||||
|
children: widget.children.length > 0 ? transformWidgets(widget.children, widgetRefs, false) : []
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {function} onWidgetsUpdate - this is a callback that will be called once the sidebar is populated with widgets
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function TreeviewContainer() {
|
||||||
|
|
||||||
|
|
||||||
|
const {widgets, widgetRefs} = useWidgetContext()
|
||||||
|
|
||||||
|
const transformedContent = useMemo(() => {
|
||||||
|
return (transformWidgets(widgets, widgetRefs))
|
||||||
|
}, [widgets, widgetRefs])
|
||||||
|
|
||||||
|
const topLevelKeys = transformedContent.filter(cont => cont.isTopLevel).map(cont => cont.key)
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col tw-overflow-x-hidden">
|
||||||
|
|
||||||
|
{/* <SearchComponent onSearch={onSearch} searchValue={searchValue}
|
||||||
|
onClear={() => setSearchValue("")} /> */}
|
||||||
|
<div className="tw-flex tw-flex-col tw-gap-2 tw-w-full tw-h-full tw-p-1">
|
||||||
|
|
||||||
|
<Tree showLine
|
||||||
|
treeData={transformedContent}
|
||||||
|
titleRender={(nodeData) =>
|
||||||
|
|
||||||
|
<TreeViewCard widgetId={nodeData.id} title={nodeData.title}
|
||||||
|
widgetRef={nodeData.widgetRef}
|
||||||
|
isTopLevel={nodeData.isTopLevel}/>
|
||||||
|
}
|
||||||
|
defaultExpandedKeys={topLevelKeys}
|
||||||
|
>
|
||||||
|
|
||||||
|
</Tree>
|
||||||
|
|
||||||
|
{
|
||||||
|
Object.keys(transformedContent || {}).length === 0 &&
|
||||||
|
<div className="tw-text-sm tw-place-content-center">
|
||||||
|
Start adding widgets to view it in tree view
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default TreeviewContainer
|
||||||
@@ -33,31 +33,37 @@ function Premium({ children, className = "" }) {
|
|||||||
open={premiumModalOpen}
|
open={premiumModalOpen}
|
||||||
>
|
>
|
||||||
<div className="tw-mt-5 tw-text-lg tw-max-w-[850px] tw-w-full ">
|
<div className="tw-mt-5 tw-text-lg tw-max-w-[850px] tw-w-full ">
|
||||||
I am Paul, a indie open-source dev.
|
|
||||||
|
I am Paul, an indie open-source dev.
|
||||||
If you find this tool useful and want to fund and support it's development, consider buying a <b>one time license</b>.
|
If you find this tool useful and want to fund and support it's development, consider buying a <b>one time license</b>.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
By buying pre-order license, you get advance features, priority support, early access, upcoming features, and
|
By pre-ordering license, you get discounted price, advance features, priority support, early access, upcoming features, and
|
||||||
<a
|
<a
|
||||||
href="https://github.com/PaulleDemon/tab=readme-ov-file"
|
href="https://github.com/PaulleDemon/PyUIBuilder/blob/main/roadmap.md"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
className="!tw-text-blue-500"
|
className="!tw-text-blue-500"
|
||||||
>
|
>
|
||||||
more.
|
more.
|
||||||
</a>
|
</a>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
Premium features will start rolling out phase wise towards end of April, after which there would be a price increase.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section className="tw-mt-1 tw-flex tw-w-full tw-flex-col tw-place-items-center tw-p-[2%] max-lg:tw-p-2" id="pricing">
|
<section className="tw-mt-1 tw-flex tw-w-full tw-flex-col tw-place-items-center tw-p-[2%] max-lg:tw-p-2" id="pricing">
|
||||||
<h3 className="tw-text-2xl tw-font-medium max-md:tw-text-2xl">Choose your plan</h3>
|
<h3 className="tw-text-2xl tw-font-medium max-md:tw-text-2xl">Choose your plan</h3>
|
||||||
<div className="tw-mt-10 tw-flex tw-place-content-center tw-gap-8 max-lg:tw-flex-col">
|
<div className="tw-mt-6 tw-flex tw-place-content-center tw-gap-8 max-lg:tw-flex-col">
|
||||||
{/* Free Plan */}
|
{/* Free Plan */}
|
||||||
<div className="tw-flex tw-w-[380px] tw-border-[1px] tw-border-gray-500 tw-border-solid tw-flex-col tw-place-items-center tw-gap-2 tw-rounded-lg tw-p-8 tw-shadow-xl max-lg:tw-w-[340px]">
|
<div className="tw-flex tw-w-[380px] tw-border-[1px] tw-border-gray-500 tw-border-solid tw-flex-col tw-place-items-center tw-gap-2 tw-rounded-lg tw-p-8 tw-shadow-xl max-lg:tw-w-[340px]">
|
||||||
<h3>
|
<h3>
|
||||||
<span className="tw-text-5xl tw-font-semibold">$0</span>
|
<span className="tw-text-5xl tw-font-semibold">$0</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="tw-mt-3 tw-text-base tw-text-center tw-text-gray-600">
|
<p className="tw-mt-3 tw-text-base tw-text-center tw-text-gray-600">
|
||||||
Free to use forever, but for added features and to support open-source development, consider buying a lifetime license.
|
Free to use forever, but for added features and to support development, consider buying a lifetime license.
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
<ul className="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
<ul className="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
||||||
@@ -69,6 +75,20 @@ function Premium({ children, className = "" }) {
|
|||||||
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Commercial use</span>
|
<span>Commercial use</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i className="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
||||||
|
<span>Premium widgets
|
||||||
|
<span className="tw-text-sm">(eg: tab widget, file upload, multi-page support, 3rd party widget library support etc)</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i class="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
||||||
|
<span>Event handlers and code editor</span>
|
||||||
|
</li>
|
||||||
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i className="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
||||||
|
<span>Premium Templates</span>
|
||||||
|
</li>
|
||||||
<li className="tw-flex tw-place-items-center tw-gap-2">
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
<i className="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
<i className="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
||||||
<span>Downloadable UI builder exe for local development</span>
|
<span>Downloadable UI builder exe for local development</span>
|
||||||
@@ -113,7 +133,7 @@ function Premium({ children, className = "" }) {
|
|||||||
<div className="tw-flex tw-w-[380px] tw-flex-col tw-place-items-center tw-gap-2 tw-rounded-lg tw-border-2 tw-border-solid
|
<div className="tw-flex tw-w-[380px] tw-flex-col tw-place-items-center tw-gap-2 tw-rounded-lg tw-border-2 tw-border-solid
|
||||||
tw-border-blue-500 tw-p-8 tw-shadow-xl max-lg:tw-w-[340px]">
|
tw-border-blue-500 tw-p-8 tw-shadow-xl max-lg:tw-w-[340px]">
|
||||||
<div className="tw-text-white tw-p-1 tw-px-3 tw-bg-blue-500 tw-rounded-full">
|
<div className="tw-text-white tw-p-1 tw-px-3 tw-bg-blue-500 tw-rounded-full">
|
||||||
Limited time offer
|
Pre-order offer
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-text-white tw-font-medium tw-p-1 tw-px-3 tw-bg-green-700 tw-rounded-full">
|
<div className="tw-text-white tw-font-medium tw-p-1 tw-px-3 tw-bg-green-700 tw-rounded-full">
|
||||||
Hobby
|
Hobby
|
||||||
@@ -126,10 +146,10 @@ function Premium({ children, className = "" }) {
|
|||||||
<span className="tw-text-2xl tw-text-gray-600">Forever</span>
|
<span className="tw-text-2xl tw-text-gray-600">Forever</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="tw-mt-3 tw-text-center tw-text-gray-600">
|
<p className="tw-mt-3 tw-text-center tw-text-gray-600">
|
||||||
Support open-source development 🚀. Plus, get added benefits.
|
Best for hobby users and people who want to learn basic UI development without PySide.
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
<ul className="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
<ul className="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-black">
|
||||||
<li className="tw-flex tw-place-items-center tw-gap-2">
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Access to web-based editor</span>
|
<span>Access to web-based editor</span>
|
||||||
@@ -138,6 +158,20 @@ function Premium({ children, className = "" }) {
|
|||||||
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Downloadable UI builder exe for local development</span>
|
<span>Downloadable UI builder exe for local development</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
|
<span>Premium widgets
|
||||||
|
<span className="tw-text-sm">(eg: tab widget, file upload, multi-page support, 3rd party widget library support etc)</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
|
<span>Premium Templates</span>
|
||||||
|
</li>
|
||||||
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
|
<span>Event handlers and code editor</span>
|
||||||
|
</li>
|
||||||
<li className="tw-flex tw-place-items-center tw-gap-2">
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Preview live</span>
|
<span>Preview live</span>
|
||||||
@@ -192,7 +226,7 @@ function Premium({ children, className = "" }) {
|
|||||||
<div className="tw-flex tw-w-[380px] tw-flex-col tw-place-items-center tw-gap-2 tw-rounded-lg tw-border-3 tw-border-solid
|
<div className="tw-flex tw-w-[380px] tw-flex-col tw-place-items-center tw-gap-2 tw-rounded-lg tw-border-3 tw-border-solid
|
||||||
tw-border-green-600 tw-p-8 tw-shadow-xl max-lg:tw-w-[340px]">
|
tw-border-green-600 tw-p-8 tw-shadow-xl max-lg:tw-w-[340px]">
|
||||||
<div className="tw-text-white tw-p-1 tw-px-3 tw-bg-blue-500 tw-rounded-full">
|
<div className="tw-text-white tw-p-1 tw-px-3 tw-bg-blue-500 tw-rounded-full">
|
||||||
Limited time offer
|
Pre-order offer
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-text-white tw-font-medium tw-p-1 tw-px-3 tw-bg-green-700 tw-rounded-full">
|
<div className="tw-text-white tw-font-medium tw-p-1 tw-px-3 tw-bg-green-700 tw-rounded-full">
|
||||||
Commercial
|
Commercial
|
||||||
@@ -205,10 +239,10 @@ function Premium({ children, className = "" }) {
|
|||||||
<span className="tw-text-2xl tw-text-gray-600">Forever</span>
|
<span className="tw-text-2xl tw-text-gray-600">Forever</span>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="tw-mt-3 tw-text-center tw-text-gray-600">
|
<p className="tw-mt-3 tw-text-center tw-text-gray-600">
|
||||||
Support open-source development 🚀. Plus, get added benefits.
|
Best for startups and teams and people who want to use internally or serves a commercial purpose and want support for PySide
|
||||||
</p>
|
</p>
|
||||||
<hr />
|
<hr />
|
||||||
<ul className="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl tw-text-gray-600">
|
<ul className="tw-mt-4 tw-flex tw-flex-col tw-gap-3 tw-text-xl">
|
||||||
<li className="tw-flex tw-place-items-center tw-gap-2">
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Access to web-based editor</span>
|
<span>Access to web-based editor</span>
|
||||||
@@ -217,6 +251,20 @@ function Premium({ children, className = "" }) {
|
|||||||
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Downloadable UI builder exe for local development</span>
|
<span>Downloadable UI builder exe for local development</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
|
<span>Premium widgets
|
||||||
|
<span className="tw-text-sm">(eg: tab widget, file upload, multi-page support, 3rd party widget library support etc)</span>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
|
<span>Premium Templates</span>
|
||||||
|
</li>
|
||||||
|
<li class="tw-flex tw-place-items-center tw-gap-2">
|
||||||
|
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
|
<span>Event handlers and code editor</span>
|
||||||
|
</li>
|
||||||
<li className="tw-flex tw-place-items-center tw-gap-2">
|
<li className="tw-flex tw-place-items-center tw-gap-2">
|
||||||
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
<i className="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||||
<span>Preview live</span>
|
<span>Preview live</span>
|
||||||
@@ -267,6 +315,11 @@ function Premium({ children, className = "" }) {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="tw-text-sm tw-mt-4">
|
||||||
|
Or you could support development by sharing this tool on your socials and you'll be eligible for a
|
||||||
|
<a href="https://tally.so/r/mJM22X" target="_blank" rel="noreferrer noopener">free license</a>.
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ function Share({children, className=""}){
|
|||||||
footer={null}
|
footer={null}
|
||||||
open={shareModalOpen}>
|
open={shareModalOpen}>
|
||||||
|
|
||||||
|
<div className="tw-text-lg">
|
||||||
|
Share and help speed up development
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="tw-mt-5 tw-flex tw-place-content-center tw-w-full tw-place-items-center">
|
<div className="tw-mt-5 tw-flex tw-place-content-center tw-w-full tw-place-items-center">
|
||||||
<a onClick={onCopy}
|
<a onClick={onCopy}
|
||||||
className="hover:!tw-bg-gray-100 hover:!tw-color-black !tw-text-4xl"
|
className="hover:!tw-bg-gray-100 hover:!tw-color-black !tw-text-4xl"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ body {
|
|||||||
|
|
||||||
/* width */
|
/* width */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Track */
|
/* Track */
|
||||||
|
|||||||
@@ -69,4 +69,29 @@ export function convertObjectToKeyValueString(obj){
|
|||||||
return Object.entries(obj)
|
return Object.entries(obj)
|
||||||
.map(([key, value]) => `${key}=${value}`)
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} widget
|
||||||
|
* @param {HTMLElement} gridContainer
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getGridPosition = (widget, gridContainer) => {
|
||||||
|
if (!widget || !gridContainer) return null;
|
||||||
|
|
||||||
|
const widgets = Array.from(gridContainer.children); // Get all grid items
|
||||||
|
// const index = widgets.indexOf(widget);
|
||||||
|
const widgetIndex = widgets.indexOf(widget);
|
||||||
|
|
||||||
|
if (widgetIndex === -1) return null; // Widget not found
|
||||||
|
|
||||||
|
const gridStyles = getComputedStyle(gridContainer);
|
||||||
|
const columnCount = gridStyles.gridTemplateColumns.split(' ').length;
|
||||||
|
|
||||||
|
const row = Math.floor(widgetIndex / columnCount) + 1;
|
||||||
|
const column = (widgetIndex % columnCount) + 1;
|
||||||
|
|
||||||
|
return { row, column };
|
||||||
}
|
}
|
||||||
36
src/utils/hooks.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { useLayoutEffect, useCallback, useState } from 'react';
|
||||||
|
|
||||||
|
export const useRect = (ref) => {
|
||||||
|
|
||||||
|
const [rect, setRect] = useState({ width: 0, height: 0, top: 0, left: 0, right: 0, bottom: 0 });
|
||||||
|
|
||||||
|
const updateRect = useCallback(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
setRect(ref.current.getBoundingClientRect());
|
||||||
|
}
|
||||||
|
}, [ref]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!ref.current) return;
|
||||||
|
|
||||||
|
const timeout = setTimeout(updateRect, 0); // Delay to next event loop
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(updateRect);
|
||||||
|
observer.observe(ref.current);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect()
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
}, [updateRect, ref]);
|
||||||
|
|
||||||
|
return rect;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Higher order component to make use of the useRect
|
||||||
|
*/
|
||||||
|
export function WithRect({ forwardedRef, children }) {
|
||||||
|
const rect = useRect(forwardedRef)
|
||||||
|
return children(rect)
|
||||||
|
}
|
||||||