Compare commits
21 Commits
code-edito
...
landing-pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eff608b9f5 | ||
|
|
9efcd8a9c6 | ||
|
|
6af1d1f6e5 | ||
|
|
07f1320f6d | ||
|
|
5211faf140 | ||
|
|
1f709a10be | ||
|
|
382675c1ba | ||
|
|
495083b416 | ||
|
|
9dbfdacd9f | ||
|
|
95321ced95 | ||
|
|
d03c3de36b | ||
|
|
fd78229570 | ||
|
|
e06a105a68 | ||
|
|
a82960ca15 | ||
|
|
20d21c210e | ||
|
|
f1eddf1821 | ||
|
|
fff10097fd | ||
|
|
d10928bb5a | ||
|
|
0243b3b9e8 | ||
|
|
66d7dfc45f | ||
|
|
a38cd90c16 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
|
||||
.env-cmdrc.json
|
||||
build
|
||||
dist
|
||||
|
||||
python-tests/
|
||||
|
||||
|
||||
12
CONTRIBUTING.md
Normal file
12
CONTRIBUTING.md
Normal file
@@ -0,0 +1,12 @@
|
||||
## Contributing
|
||||
|
||||
Currently if you find bugs or typos, I request you to create an issue first, instead of a Pull request.
|
||||
That is the best way to contribute to this repo in it's current state.
|
||||
|
||||
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 ways to contribute.
|
||||
1. Creating tutorials and helping others on [discord server](https://discord.gg/dHXjrrCA7G)
|
||||
2. Sharing the tool with others.
|
||||
13
README.md
13
README.md
@@ -27,7 +27,7 @@ Build Python GUI's with ease of Drag and drop builders.
|
||||
|
||||
https://github.com/user-attachments/assets/ac91aa98-843d-4578-b646-88e66bc113de
|
||||
|
||||
<sub>**Don't like background music? fell free to mute it**</sub>
|
||||
<sub>**Don't like background music? feel free to mute it**</sub>
|
||||
|
||||
## Try PyUIBuilder
|
||||
Try [PyUIBuilder](https://pyuibuilder.com)
|
||||
@@ -51,6 +51,9 @@ Try [PyUIBuilder](https://pyuibuilder.com)
|
||||
- [Author](#author)
|
||||
|
||||
|
||||
## Tutorials
|
||||
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)
|
||||
|
||||
## Docs - Getting started
|
||||
Read the docs on the [Docs page](https://docs.pyuibuilder.com/)
|
||||
@@ -123,7 +126,6 @@ While there are a lot of features, here are few you need to know.
|
||||
|
||||
## Roadmap
|
||||
Here are some of the upcoming features.
|
||||
* Treeview on the sidebar
|
||||
* Support for Event Handlers
|
||||
* Kivy Framework support
|
||||
* Pyqt/PySide Support
|
||||
@@ -131,13 +133,13 @@ Here are some of the upcoming features.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@@ -148,6 +150,7 @@ The discount's will be available for limited time only on pre-orders.
|
||||
| **Lifetime license** (one-time purchase) | 👍️ | ✅ | ✅ |
|
||||
| **Early access** to upcoming features | ❌ | ✅ | ✅ |
|
||||
| **Downloadable Electron App** (upcoming) | ❌ | ✅ | ✅ |
|
||||
| **Premium widgets**(tabbed widget, scroll widget etc) (upcoming) | ❌ | ✅ | ✅ |
|
||||
| **Run Preview live**(upcoming) | ❌ | ✅ | ✅ |
|
||||
| **Save and Load UI files** (upcoming) | ❌ | ✅ | ✅ |
|
||||
| **Load 3rd party plugins locally** | ❌ | ✅ | ✅ |
|
||||
|
||||
BIN
landingpages/landingpage/assets/images/home/benefits/1.png
Normal file
BIN
landingpages/landingpage/assets/images/home/benefits/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
BIN
landingpages/landingpage/assets/images/home/benefits/2.png
Normal file
BIN
landingpages/landingpage/assets/images/home/benefits/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
BIN
landingpages/landingpage/assets/images/home/benefits/3.png
Normal file
BIN
landingpages/landingpage/assets/images/home/benefits/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
BIN
landingpages/landingpage/assets/images/home/benefits/4.png
Normal file
BIN
landingpages/landingpage/assets/images/home/benefits/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
BIN
landingpages/landingpage/assets/images/home/benefits/5.png
Normal file
BIN
landingpages/landingpage/assets/images/home/benefits/5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
BIN
landingpages/landingpage/assets/images/home/benefits/6.png
Normal file
BIN
landingpages/landingpage/assets/images/home/benefits/6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
@@ -3,8 +3,8 @@
|
||||
|
||||
--bg-color: #fff;
|
||||
|
||||
--btn-color: #0F1727;/* button color*/
|
||||
--btn-bg: #f1efef;/* button bg color*/
|
||||
--btn-color: #ffffff;/* button color*/
|
||||
--btn-bg: #0F1727;/* button bg color*/
|
||||
|
||||
--primary-text-color: #000;
|
||||
--header-link-hover: #000;
|
||||
@@ -181,7 +181,7 @@ header > .collapsible-header{
|
||||
}
|
||||
|
||||
.footer-link:hover{
|
||||
color: #fff;
|
||||
color: #0b0b0b;
|
||||
}
|
||||
|
||||
/* 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" />
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href="./assets/logo/icon48.png"
|
||||
href="./assets/logo/logo.png"
|
||||
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: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:url" content="https://github.com/PaulleDemon/PyUIBuilder" />
|
||||
<meta property="og:url" content="https://about.pyuibuilder.com" />
|
||||
<!--Replace with the current website url-->
|
||||
<meta property="og:image" content="" />
|
||||
|
||||
<link rel="stylesheet" href="../tailwind/tailwind-runtime.css" />
|
||||
<!-- <link rel="stylesheet" href="./css/tailwind-build.css"> -->
|
||||
<!-- <link rel="stylesheet" href="../tailwind/tailwind-runtime.css" /> -->
|
||||
<link rel="stylesheet" href="./css/tailwind-build.css">
|
||||
<link rel="stylesheet" href="css/index.css" />
|
||||
|
||||
<link
|
||||
@@ -66,14 +66,17 @@
|
||||
<a class="header-links" href=""> About </a>
|
||||
<a class="header-links" href="#pricing"> Pricing </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
|
||||
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
|
||||
href="#pricing"
|
||||
href="https://pyuibuilder.com"
|
||||
aria-label="start"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
class="tw-rounded-md tw-border-[1px] tw-border-black
|
||||
tw-bg-[#0F1727] tw-px-3 tw-py-2 tw-text-white
|
||||
tw-duration-[0.3s]
|
||||
@@ -130,11 +133,11 @@
|
||||
class="tw-flex tw-flex-col tw-place-content-center tw-items-center"
|
||||
>
|
||||
<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-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>
|
||||
<br />
|
||||
<span class=" tw-px-3 tw-text-5xl"> Drag and Drop editor </span>
|
||||
@@ -146,7 +149,7 @@
|
||||
<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"
|
||||
>
|
||||
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
|
||||
@@ -154,7 +157,9 @@
|
||||
>
|
||||
<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]"
|
||||
href="#pricing"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://pyuibuilder.com"
|
||||
>
|
||||
Start for free
|
||||
</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">
|
||||
<!-- <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"
|
||||
alt="font tester gif" src="./assets/images/home/promo.gif" >
|
||||
alt="Pyui gif" src="./assets/images/home/promo.gif" >
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</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">
|
||||
<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 mid of April phase wise. This is your last chance to get it before price increase from mid 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-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]">
|
||||
@@ -214,6 +357,10 @@
|
||||
<i class="bi bi-x-circle-fill tw-text-red-600 tw-text-base"></i>
|
||||
<span>Downloadable UI builder exe for local development</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>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>Support for PySlide/PyQt</span>
|
||||
@@ -287,6 +434,10 @@
|
||||
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||
<span>Downloadable UI builder exe for local development</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>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>Preview live</span>
|
||||
@@ -338,7 +489,7 @@
|
||||
</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
|
||||
!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">
|
||||
Limited time offer
|
||||
</div>
|
||||
@@ -365,6 +516,10 @@
|
||||
<i class="bi bi-check-circle-fill tw-text-green-600 tw-text-base"></i>
|
||||
<span>Downloadable UI builder exe for local development</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>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>Preview live</span>
|
||||
@@ -483,25 +638,82 @@
|
||||
<div
|
||||
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>
|
||||
</div>
|
||||
<div class="content">
|
||||
You can find the upcoming features on the
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/PaulleDemon/font-tester-chrome/blob/main/roadmap.md"
|
||||
class="tw-underline"
|
||||
>Roadmap</a
|
||||
>
|
||||
Premium features will start rolling out from mid of April in phase wise.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="faq tw-w-full"
|
||||
>
|
||||
<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">read more</a>
|
||||
</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
|
||||
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
|
||||
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 +761,7 @@
|
||||
<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="#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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -557,12 +769,43 @@
|
||||
<div class="tw-flex tw-flex-col tw-gap-3 max-md:tw-text-sm">
|
||||
<a href="" class="footer-link">About</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/font-tester-chrome" class="footer-link">Report issue</a>
|
||||
<a href="https://github.com/PaulleDemon/PyUIBuilder" class="footer-link">Github</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>
|
||||
</footer>
|
||||
</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
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.0/gsap.min.js"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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 */
|
||||
|
||||
[hidden] {
|
||||
[hidden]:where(:not([hidden="until-found"])) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -619,6 +619,20 @@ video {
|
||||
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 {
|
||||
margin-left: auto;
|
||||
}
|
||||
@@ -643,6 +657,10 @@ video {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.tw-mt-6 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.tw-mt-8 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
@@ -651,14 +669,14 @@ video {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.\!tw-mt-auto {
|
||||
margin-top: auto !important;
|
||||
}
|
||||
|
||||
.tw-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tw-grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.\!tw-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -683,10 +701,18 @@ video {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.tw-h-\[200px\] {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.tw-h-\[40px\] {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.tw-h-\[500px\] {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.tw-h-\[50px\] {
|
||||
height: 50px;
|
||||
}
|
||||
@@ -707,6 +733,14 @@ video {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tw-h-\[230px\] {
|
||||
height: 230px;
|
||||
}
|
||||
|
||||
.tw-h-\[360px\] {
|
||||
height: 360px;
|
||||
}
|
||||
|
||||
.tw-max-h-\[120px\] {
|
||||
max-height: 120px;
|
||||
}
|
||||
@@ -727,6 +761,10 @@ video {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.tw-min-h-\[200px\] {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.tw-min-h-\[300px\] {
|
||||
min-height: 300px;
|
||||
}
|
||||
@@ -739,22 +777,18 @@ video {
|
||||
min-height: 550px;
|
||||
}
|
||||
|
||||
.tw-min-h-\[60vh\] {
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.tw-min-h-\[70vh\] {
|
||||
min-height: 70vh;
|
||||
}
|
||||
|
||||
.tw-min-h-full {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.tw-min-h-\[80vh\] {
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
.tw-min-h-full {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.tw-w-8 {
|
||||
width: 2rem;
|
||||
}
|
||||
@@ -767,6 +801,10 @@ video {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.tw-w-\[350px\] {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.tw-w-\[380px\] {
|
||||
width: 380px;
|
||||
}
|
||||
@@ -791,10 +829,18 @@ video {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.tw-min-w-\[350px\] {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
.tw-max-w-\[100vw\] {
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
.tw-max-w-\[1150px\] {
|
||||
max-width: 1150px;
|
||||
}
|
||||
|
||||
.tw-max-w-\[120px\] {
|
||||
max-width: 120px;
|
||||
}
|
||||
@@ -803,6 +849,10 @@ video {
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
.tw-max-w-\[650px\] {
|
||||
max-width: 650px;
|
||||
}
|
||||
|
||||
.tw-max-w-\[80vw\] {
|
||||
max-width: 80vw;
|
||||
}
|
||||
@@ -811,6 +861,10 @@ video {
|
||||
max-width: 850px;
|
||||
}
|
||||
|
||||
.tw-max-w-\[350px\] {
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.tw--translate-x-1\/2 {
|
||||
--tw-translate-x: -50%;
|
||||
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));
|
||||
@@ -856,6 +910,14 @@ video {
|
||||
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 {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -920,6 +982,10 @@ video {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.tw-gap-\[5\%\] {
|
||||
gap: 5%;
|
||||
}
|
||||
|
||||
.tw-overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -940,6 +1006,10 @@ video {
|
||||
text-wrap: wrap;
|
||||
}
|
||||
|
||||
.tw-rounded-3xl {
|
||||
border-radius: 1.5rem;
|
||||
}
|
||||
|
||||
.tw-rounded-full {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
@@ -968,10 +1038,6 @@ video {
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.tw-border-4 {
|
||||
border-width: 4px;
|
||||
}
|
||||
|
||||
.tw-border-\[3px\] {
|
||||
border-width: 3px;
|
||||
}
|
||||
@@ -984,65 +1050,65 @@ video {
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.\!tw-border-white {
|
||||
--tw-border-opacity: 1 !important;
|
||||
border-color: rgb(255 255 255 / var(--tw-border-opacity)) !important;
|
||||
}
|
||||
|
||||
.tw-border-black {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(0 0 0 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.tw-border-blue-500 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(59 130 246 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.tw-border-gray-300 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.tw-border-green-600 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(22 163 74 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.tw-border-white {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(255 255 255 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.\!tw-border-\[\#0F1727\] {
|
||||
--tw-border-opacity: 1 !important;
|
||||
border-color: rgb(15 23 39 / var(--tw-border-opacity)) !important;
|
||||
border-color: rgb(15 23 39 / var(--tw-border-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.\!tw-border-green-600 {
|
||||
--tw-border-opacity: 1 !important;
|
||||
border-color: rgb(22 163 74 / var(--tw-border-opacity)) !important;
|
||||
border-color: rgb(22 163 74 / var(--tw-border-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.\!tw-bg-\[\#7E22CE\] {
|
||||
.tw-border-black {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(0 0 0 / var(--tw-border-opacity, 1));
|
||||
}
|
||||
|
||||
.tw-border-gray-300 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
|
||||
}
|
||||
|
||||
.tw-border-white {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
|
||||
}
|
||||
|
||||
.\!tw-border-blue-600 {
|
||||
--tw-border-opacity: 1 !important;
|
||||
border-color: rgb(37 99 235 / var(--tw-border-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.\!tw-bg-\[\#0F1727\] {
|
||||
--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-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-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 {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.tw-bg-\[\#0F1727\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(15 23 39 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.tw-bg-\[\#eeeeee\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(238 238 238 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.tw-bg-\[\#f0f0f0ef\] {
|
||||
background-color: #f0f0f0ef;
|
||||
}
|
||||
@@ -1053,49 +1119,37 @@ video {
|
||||
|
||||
.tw-bg-black {
|
||||
--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-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-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-green-700 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.tw-bg-transparent {
|
||||
background-color: transparent;
|
||||
background-color: rgb(21 128 61 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.tw-bg-white {
|
||||
--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-\[\#f2f2f2\] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(15 23 39 / var(--tw-bg-opacity));
|
||||
background-color: rgb(242 242 242 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.\!tw-bg-\[\#\#0F1727\] {
|
||||
background-color: ##0F1727 !important;
|
||||
}
|
||||
|
||||
.tw-bg-\[\#\#0F1727\] {
|
||||
background-color: ##0F1727;
|
||||
.tw-bg-gray-800 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.tw-bg-opacity-0 {
|
||||
@@ -1110,6 +1164,10 @@ video {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.tw-p-10 {
|
||||
padding: 2.5rem;
|
||||
}
|
||||
|
||||
.tw-p-2 {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
@@ -1118,6 +1176,10 @@ video {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.tw-p-4 {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.tw-p-6 {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
@@ -1138,6 +1200,10 @@ video {
|
||||
padding: 5%;
|
||||
}
|
||||
|
||||
.tw-p-\[3\%\] {
|
||||
padding: 3%;
|
||||
}
|
||||
|
||||
.tw-px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
@@ -1167,6 +1233,10 @@ video {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tw-align-middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.\!tw-text-4xl {
|
||||
font-size: 2.25rem !important;
|
||||
line-height: 2.5rem !important;
|
||||
@@ -1216,6 +1286,11 @@ video {
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.tw-text-sm {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.tw-text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
@@ -1233,10 +1308,6 @@ video {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.\!tw-font-medium {
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
.tw-uppercase {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@@ -1250,84 +1321,73 @@ video {
|
||||
line-height: 90px;
|
||||
}
|
||||
|
||||
.\!tw-text-black {
|
||||
--tw-text-opacity: 1 !important;
|
||||
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-leading-normal {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.\!tw-text-\[\#0F1727\] {
|
||||
--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-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-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 {
|
||||
@@ -1364,16 +1424,17 @@ video {
|
||||
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-color: #4d395e;
|
||||
--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 {
|
||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
@@ -1392,12 +1453,6 @@ video {
|
||||
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 {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
@@ -1410,9 +1465,8 @@ video {
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
.hover\:tw-translate-x-1:hover {
|
||||
--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));
|
||||
.tw-duration-75 {
|
||||
transition-duration: 75ms;
|
||||
}
|
||||
|
||||
.hover\:tw-translate-x-2:hover {
|
||||
@@ -1432,6 +1486,18 @@ 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));
|
||||
}
|
||||
|
||||
.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-\[1\.1\]:hover {
|
||||
--tw-scale-x: 1.1;
|
||||
--tw-scale-y: 1.1;
|
||||
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 {
|
||||
--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));
|
||||
@@ -1439,47 +1505,22 @@ video {
|
||||
|
||||
.hover\:\!tw-bg-gray-100:hover {
|
||||
--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 {
|
||||
--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 {
|
||||
--tw-bg-opacity: 1 !important;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity)) !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));
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.hover\:\!tw-text-black:hover {
|
||||
--tw-text-opacity: 1 !important;
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity)) !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));
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity, 1)) !important;
|
||||
}
|
||||
|
||||
.hover\:tw-transition-transform:hover {
|
||||
@@ -1488,9 +1529,14 @@ video {
|
||||
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 *) {
|
||||
--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 *) {
|
||||
@@ -1498,6 +1544,14 @@ video {
|
||||
}
|
||||
|
||||
@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 {
|
||||
place-items: center;
|
||||
}
|
||||
@@ -1520,6 +1574,10 @@ video {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.max-lg\:tw-h-fit {
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.max-lg\:tw-min-h-\[250px\] {
|
||||
min-height: 250px;
|
||||
}
|
||||
@@ -1528,10 +1586,6 @@ video {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.max-lg\:tw-min-h-\[60vh\] {
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.max-lg\:tw-min-h-\[80vh\] {
|
||||
min-height: 80vh;
|
||||
}
|
||||
@@ -1556,6 +1610,10 @@ video {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.max-lg\:tw-place-content-center {
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
.max-lg\:tw-place-items-end {
|
||||
place-items: end;
|
||||
}
|
||||
|
||||
15683
package-lock.json
generated
15683
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@@ -5,6 +5,10 @@
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"overrides": {
|
||||
"picomatch": "^3.0.0",
|
||||
"postcss-selector-parser": "7.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.4.0",
|
||||
"@dnd-kit/core": "^6.1.0",
|
||||
@@ -35,9 +39,9 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "GENERATE_SOURCEMAP=false react-scripts build",
|
||||
"build:local": "GENERATE_SOURCEMAP=false env-cmd -e production react-scripts build",
|
||||
"start": "env-cmd -e development cross-env NODE_ENV=development webpack serve",
|
||||
"build": "NODE_ENV=production webpack",
|
||||
"build:local": "env-cmd -e production cross-env NODE_ENV=production webpack",
|
||||
"test": "react-scripts test",
|
||||
"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",
|
||||
@@ -63,8 +67,29 @@
|
||||
]
|
||||
},
|
||||
"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",
|
||||
"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,31 +2,31 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
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
|
||||
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" />
|
||||
|
||||
<script async src="https://tally.so/widgets/embed.js"></script>
|
||||
|
||||
<!-- 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>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', '%REACT_APP_ANALYTICS_SCRIPT_ID%');
|
||||
gtag('config', '<%= REACT_APP_ANALYTICS_SCRIPT_ID %>');
|
||||
</script>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
|
||||
@@ -19,7 +19,8 @@ import { removeDuplicateObjects } from "../utils/common"
|
||||
|
||||
|
||||
// import DotsBackground from "../assets/background/dots.svg"
|
||||
import { ReactComponent as DotsBackground } from "../assets/background/dots.svg"
|
||||
// import { ReactComponent as DotsBackground } from "../assets/background/dots.svg"
|
||||
import DotsBackground from "../assets/background/dots.svg";
|
||||
|
||||
import DroppableWrapper from "../components/draggable/droppable"
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ class Widget extends React.Component {
|
||||
static requirements = [] // requirements for the widgets (libraries) eg: tkvideoplayer, tktimepicker
|
||||
static requiredImports = [] // import statements
|
||||
|
||||
static requiredCustomPyFiles = [] // custom widgets inside of pythonWidgets don't add .py at the end
|
||||
|
||||
// static contextType = ActiveWidgetContext
|
||||
|
||||
constructor(props) {
|
||||
@@ -185,6 +187,7 @@ class Widget extends React.Component {
|
||||
|
||||
this.getImports = this.getImports.bind(this)
|
||||
this.getRequirements = this.getRequirements.bind(this)
|
||||
this.getRequiredCustomPyFiles = this.getRequiredCustomPyFiles.bind(this)
|
||||
|
||||
// this.openRenaming = this.openRenaming.bind(this)
|
||||
|
||||
@@ -214,8 +217,6 @@ class Widget extends React.Component {
|
||||
|
||||
this.stateUpdateCallback = null // allowing other components such as toolbar to subscribe to changes in this widget
|
||||
this.resizeObserver = null
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -428,6 +429,10 @@ class Widget extends React.Component {
|
||||
return this.constructor.requiredImports
|
||||
}
|
||||
|
||||
getRequiredCustomPyFiles(){
|
||||
return this.constructor.requiredCustomPyFiles
|
||||
}
|
||||
|
||||
generateCode(){
|
||||
throw new NotImplementedError("generateCode() must be implemented by the subclass")
|
||||
}
|
||||
@@ -918,6 +923,8 @@ class Widget extends React.Component {
|
||||
pos
|
||||
}
|
||||
|
||||
const {layout} = attrs
|
||||
|
||||
this.setState(newData, () => {
|
||||
// Updates attrs
|
||||
let newAttrs = { ...this.state.attrs }
|
||||
@@ -949,6 +956,10 @@ class Widget extends React.Component {
|
||||
|
||||
if (selected){
|
||||
this.select()
|
||||
}
|
||||
|
||||
if (layout){
|
||||
this.setLayout(layout)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -58,13 +58,35 @@ export class CustomTkBase extends Widget {
|
||||
const absolutePositioning = this.getAttrValue("positioning")
|
||||
|
||||
let layoutManager = `pack()`
|
||||
const marginX = this.getAttrValue("margin.marginX")
|
||||
const marginY = this.getAttrValue("margin.marginY")
|
||||
|
||||
const paddingX = this.getAttrValue("padding.padX")
|
||||
const paddingY = this.getAttrValue("padding.padY")
|
||||
|
||||
let config = {}
|
||||
|
||||
if (marginX){
|
||||
config["padx"] = marginX
|
||||
}
|
||||
|
||||
if (marginY){
|
||||
config["pady"] = marginY
|
||||
}
|
||||
|
||||
if (paddingX){
|
||||
config["ipadx"] = paddingX
|
||||
}
|
||||
|
||||
if (paddingY){
|
||||
config["ipady"] = paddingY
|
||||
}
|
||||
|
||||
if (parentLayout === Layouts.PLACE || absolutePositioning){
|
||||
|
||||
const config = {
|
||||
x: Math.trunc(this.state.pos.x),
|
||||
y: Math.trunc(this.state.pos.y),
|
||||
}
|
||||
|
||||
config['x'] = Math.trunc(this.state.pos.x)
|
||||
config['y'] = Math.trunc(this.state.pos.y)
|
||||
|
||||
config["width"] = Math.trunc(this.state.size.width)
|
||||
config["height"] = Math.trunc(this.state.size.height)
|
||||
@@ -84,12 +106,6 @@ export class CustomTkBase extends Widget {
|
||||
|
||||
const packSide = this.getAttrValue("flexManager.side")
|
||||
|
||||
const marginX = this.getAttrValue("margin.marginX")
|
||||
const marginY = this.getAttrValue("margin.marginY")
|
||||
|
||||
const paddingX = this.getAttrValue("padding.padX")
|
||||
const paddingY = this.getAttrValue("padding.padY")
|
||||
|
||||
const config = {}
|
||||
|
||||
if (packSide === "" || packSide === "top"){
|
||||
@@ -113,21 +129,6 @@ export class CustomTkBase extends Widget {
|
||||
// config["pady"] = gap
|
||||
// }
|
||||
|
||||
if (marginX){
|
||||
config["padx"] = marginX
|
||||
}
|
||||
|
||||
if (marginY){
|
||||
config["pady"] = marginY
|
||||
}
|
||||
|
||||
if (paddingX){
|
||||
config["ipadx"] = paddingX
|
||||
}
|
||||
|
||||
if (paddingY){
|
||||
config["ipady"] = paddingY
|
||||
}
|
||||
|
||||
// if (align === "start"){
|
||||
// config["anchor"] = "'nw'"
|
||||
@@ -167,10 +168,8 @@ export class CustomTkBase extends Widget {
|
||||
|
||||
const sticky = this.getAttrValue("gridManager.sticky")
|
||||
|
||||
const config = {
|
||||
row: row-1, // unlike css grid tkinter grid starts from 0
|
||||
column: column-1, // unlike css grid tkinter grid starts from 0
|
||||
}
|
||||
config['row'] = row - 1
|
||||
config['column'] = column - 1
|
||||
|
||||
if (rowSpan > 1){
|
||||
config['rowspan'] = rowSpan
|
||||
@@ -574,11 +573,13 @@ export class CustomTkBase extends Widget {
|
||||
|
||||
let expandValue = expand ? (isSameSide ? previousExpandValue : widgets.length - index) : 1;
|
||||
|
||||
if ((expand && previousExpandValue === 0) || (expand && expandValue === 0)){
|
||||
if (expand && expandValue === 0){
|
||||
expandValue = 1 // if there is expand it should expand
|
||||
}
|
||||
|
||||
if (expand && !isSameSide) previousExpandValue = expandValue;
|
||||
|
||||
if ((expand && !isSameSide) || (expand && previousExpandValue === 0)){
|
||||
previousExpandValue = expandValue;
|
||||
}
|
||||
|
||||
lastSide = side; // Update last side for recursion
|
||||
|
||||
@@ -1219,23 +1220,23 @@ export class CustomTkWidgetBase extends CustomTkBase{
|
||||
if (this.getAttrValue("cursor"))
|
||||
config["cursor"] = `"${this.getAttrValue("cursor")}"`
|
||||
|
||||
if (this.getAttrValue("padding.padX")){
|
||||
// inner padding
|
||||
config["ipadx"] = this.getAttrValue("padding.padX")
|
||||
}
|
||||
// if (this.getAttrValue("padding.padX")){
|
||||
// // inner padding
|
||||
// config["ipadx"] = this.getAttrValue("padding.padX")
|
||||
// }
|
||||
|
||||
if (this.getAttrValue("padding.padY")){
|
||||
config["ipady"] = this.getAttrValue("padding.padY")
|
||||
}
|
||||
// if (this.getAttrValue("padding.padY")){
|
||||
// config["ipady"] = this.getAttrValue("padding.padY")
|
||||
// }
|
||||
|
||||
|
||||
if (this.getAttrValue("margin.marginX")){
|
||||
config["padx"] = this.getAttrValue("margin.marginX")
|
||||
}
|
||||
// if (this.getAttrValue("margin.marginX")){
|
||||
// config["padx"] = this.getAttrValue("margin.marginX")
|
||||
// }
|
||||
|
||||
if (this.getAttrValue("margin.marginY")){
|
||||
config["pady"] = this.getAttrValue("margin.marginY")
|
||||
}
|
||||
// if (this.getAttrValue("margin.marginY")){
|
||||
// config["pady"] = this.getAttrValue("margin.marginY")
|
||||
// }
|
||||
|
||||
// FIXME: add width and height, the scales may not be correct as the width and height are based on characters in pack and grid not pixels
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import MainWindow from "../widgets/mainWindow"
|
||||
import { message } from "antd"
|
||||
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
|
||||
// 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 code = []
|
||||
|
||||
let customPythonWidgets = new Set([])
|
||||
|
||||
for (let widget of widgetList) {
|
||||
const widgetRef = widgetRefs[widget.id].current
|
||||
let varName = widgetRef.getVariableName()
|
||||
@@ -20,6 +24,7 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
|
||||
// Add imports and requirements to sets
|
||||
widgetRef.getImports().forEach(importItem => imports.add(importItem))
|
||||
widgetRef.getRequirements().forEach(requirementItem => requirements.add(requirementItem))
|
||||
widgetRef.getRequiredCustomPyFiles().forEach(customFile => customPythonWidgets.add(customFile))
|
||||
|
||||
// Set main variable if the widget is MainWindow
|
||||
if (widget.widgetType === MainWindow) {
|
||||
@@ -68,6 +73,8 @@ function generateTkinterCodeList(widgetList = [], widgetRefs = [], parentVariabl
|
||||
// Merge child imports, requirements, and code
|
||||
imports = new Set([...imports, ...childResult.imports])
|
||||
requirements = new Set([...requirements, ...childResult.requirements])
|
||||
customPythonWidgets = new Set([...customPythonWidgets, ...childResult.customPythonWidgets])
|
||||
|
||||
code.push(...childResult.code)
|
||||
|
||||
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),
|
||||
code: code,
|
||||
requirements: Array.from(requirements),
|
||||
customPythonWidgets: Array.from(customPythonWidgets),
|
||||
mainVariable
|
||||
}
|
||||
}
|
||||
@@ -113,9 +121,13 @@ async function generateTkinterCode(projectName, widgetList=[], widgetRefs=[], as
|
||||
|
||||
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: import customWidgets
|
||||
const code = [
|
||||
"# This code is generated by PyUIbuilder: https://pyuibuilder.com",
|
||||
"\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){
|
||||
|
||||
if (asset.fileType === "image"){
|
||||
|
||||
92
src/frameworks/tkinter/pythonWidgets/imageLabel.py
Normal file
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
1
src/frameworks/tkinter/pythonWidgets/readme.md
Normal file
@@ -0,0 +1 @@
|
||||
# contains python coded custom widgets
|
||||
@@ -59,13 +59,34 @@ export class TkinterBase extends Widget {
|
||||
const absolutePositioning = this.getAttrValue("positioning")
|
||||
|
||||
let layoutManager = `pack()`
|
||||
const marginX = this.getAttrValue("margin.marginX")
|
||||
const marginY = this.getAttrValue("margin.marginY")
|
||||
|
||||
const paddingX = this.getAttrValue("padding.padX")
|
||||
const paddingY = this.getAttrValue("padding.padY")
|
||||
|
||||
let config = {}
|
||||
|
||||
if (marginX){
|
||||
config["padx"] = marginX
|
||||
}
|
||||
|
||||
if (marginY){
|
||||
config["pady"] = marginY
|
||||
}
|
||||
|
||||
if (paddingX){
|
||||
config["ipadx"] = paddingX
|
||||
}
|
||||
|
||||
if (paddingY){
|
||||
config["ipady"] = paddingY
|
||||
}
|
||||
|
||||
if (parentLayout === Layouts.PLACE || absolutePositioning){
|
||||
|
||||
const config = {
|
||||
x: Math.trunc(this.state.pos.x),
|
||||
y: Math.trunc(this.state.pos.y),
|
||||
}
|
||||
config['x'] = Math.trunc(this.state.pos.x)
|
||||
config['y'] = Math.trunc(this.state.pos.y)
|
||||
|
||||
config["width"] = Math.trunc(this.state.size.width)
|
||||
config["height"] = Math.trunc(this.state.size.height)
|
||||
@@ -85,14 +106,6 @@ export class TkinterBase extends Widget {
|
||||
|
||||
const packSide = this.getAttrValue("flexManager.side")
|
||||
|
||||
const marginX = this.getAttrValue("margin.marginX")
|
||||
const marginY = this.getAttrValue("margin.marginY")
|
||||
|
||||
const paddingX = this.getAttrValue("padding.padX")
|
||||
const paddingY = this.getAttrValue("padding.padY")
|
||||
|
||||
const config = {}
|
||||
|
||||
if (packSide === "" || packSide === "top"){
|
||||
|
||||
config['side'] = `tk.TOP`
|
||||
@@ -114,21 +127,6 @@ export class TkinterBase extends Widget {
|
||||
// config["pady"] = gap
|
||||
// }
|
||||
|
||||
if (marginX){
|
||||
config["padx"] = marginX
|
||||
}
|
||||
|
||||
if (marginY){
|
||||
config["pady"] = marginY
|
||||
}
|
||||
|
||||
if (paddingX){
|
||||
config["ipadx"] = paddingX
|
||||
}
|
||||
|
||||
if (paddingY){
|
||||
config["ipady"] = paddingY
|
||||
}
|
||||
|
||||
// if (align === "start"){
|
||||
// config["anchor"] = "'nw'"
|
||||
@@ -168,10 +166,8 @@ export class TkinterBase extends Widget {
|
||||
|
||||
const sticky = this.getAttrValue("gridManager.sticky")
|
||||
|
||||
const config = {
|
||||
row: row-1, // unlike css grid tkinter grid starts from 0
|
||||
column: column-1, // unlike css grid tkinter grid starts from 0
|
||||
}
|
||||
config['row'] = row - 1 // unlike css grid tkinter grid starts from 0
|
||||
config['column'] = column - 1 // unlike css grid tkinter grid starts from 0
|
||||
|
||||
if (rowSpan > 1){
|
||||
config['rowspan'] = rowSpan
|
||||
@@ -561,7 +557,8 @@ export class TkinterBase extends Widget {
|
||||
const { side = "top", expand = false, anchor } = widgetRef.getPackAttrs() || {};
|
||||
|
||||
// console.log("rerendering:", side, expand);
|
||||
|
||||
|
||||
|
||||
const directionMap = {
|
||||
top: "column",
|
||||
bottom: "column-reverse",
|
||||
@@ -570,17 +567,29 @@ export class TkinterBase extends Widget {
|
||||
}
|
||||
|
||||
const currentWidgetDirection = directionMap[side] || "column"; // Default to "column"
|
||||
const isSameSide = lastSide === side;
|
||||
const isVertical = ["top", "bottom"].includes(side);
|
||||
|
||||
let expandValue = expand ? (isSameSide ? previousExpandValue : widgets.length - index) : 1;
|
||||
|
||||
if ((expand && previousExpandValue === 0) || (expand && expandValue === 0)){
|
||||
const isSameSide = lastSide === side
|
||||
const isOppositeSide = ((lastSide === "top" && side === "bottom") || (lastSide === "left" && side === "right"))
|
||||
|
||||
const isDiagonal = (!isSameSide && !isOppositeSide && lastSide !== "") // bottom and right, top and left
|
||||
|
||||
let expandValue = expand ? ((isSameSide || isOppositeSide) ? previousExpandValue : (widgets.length - index) + 1) : (previousExpandValue > 0) ? 0 : 1
|
||||
|
||||
if (expand && expandValue === 0){
|
||||
expandValue = 1 // if there is expand it should expand
|
||||
}
|
||||
|
||||
if (expand && !isSameSide) previousExpandValue = expandValue;
|
||||
|
||||
if (expand && isDiagonal){
|
||||
expandValue = 1
|
||||
}
|
||||
|
||||
// TODO: if the child widget as fillx or y use flex grow
|
||||
|
||||
if ((expand && !isSameSide) || (expand && previousExpandValue === 0)){
|
||||
previousExpandValue = expandValue;
|
||||
}
|
||||
|
||||
lastSide = side; // Update last side for recursion
|
||||
|
||||
const anchorStyles = {
|
||||
@@ -598,7 +607,10 @@ export class TkinterBase extends Widget {
|
||||
|
||||
|
||||
const stretchClass = isVertical ? "tw-flex-grow" : "tw-h-full"; // Allow only horizontal growth for top/bottom
|
||||
|
||||
// TODO: if previous expand value is greater than 0 and current doesn't have expand then it should be 0
|
||||
|
||||
// const fill = this.getAttrValue("flexManager.fillX") || this.getAttrValue("flexManager.fillY")
|
||||
|
||||
if (isSameSide) {
|
||||
return (
|
||||
<>
|
||||
@@ -627,11 +639,12 @@ export class TkinterBase extends Widget {
|
||||
return (
|
||||
<div
|
||||
data-pack-container={side}
|
||||
className={`tw-flex ${isVertical ? "!tw-h-full" : "!tw-w-full"}`}
|
||||
className={`tw-flex tw-flex-auto`}
|
||||
// className={`tw-flex ${isVertical ? "!tw-h-full" : "!tw-w-full"}`}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: currentWidgetDirection,
|
||||
flexGrow: expand ? expandValue : 1, //((widgets.length - 1) === index) ? 1 : 0, // last index will always have flex-grow 1
|
||||
flexGrow: expandValue, //((widgets.length - 1) === index) ? 1 : 0, // last index will always have flex-grow 1
|
||||
flexShrink: expand ? 0 : 1,
|
||||
flexBasis: "auto",
|
||||
minWidth: isVertical ? "0" : "auto",
|
||||
@@ -1212,23 +1225,23 @@ export class TkinterWidgetBase extends TkinterBase{
|
||||
if (this.getAttrValue("cursor"))
|
||||
config["cursor"] = `"${this.getAttrValue("cursor")}"`
|
||||
|
||||
if (this.getAttrValue("padding.padX")){
|
||||
// inner padding
|
||||
config["ipadx"] = this.getAttrValue("padding.padX")
|
||||
}
|
||||
// if (this.getAttrValue("padding.padX")){
|
||||
// // inner padding
|
||||
// config["ipadx"] = this.getAttrValue("padding.padX")
|
||||
// }
|
||||
|
||||
if (this.getAttrValue("padding.padY")){
|
||||
config["ipady"] = this.getAttrValue("padding.padY")
|
||||
}
|
||||
// if (this.getAttrValue("padding.padY")){
|
||||
// config["ipady"] = this.getAttrValue("padding.padY")
|
||||
// }
|
||||
|
||||
|
||||
if (this.getAttrValue("margin.marginX")){
|
||||
config["padx"] = this.getAttrValue("margin.marginX")
|
||||
}
|
||||
// if (this.getAttrValue("margin.marginX")){
|
||||
// config["padx"] = this.getAttrValue("margin.marginX")
|
||||
// }
|
||||
|
||||
if (this.getAttrValue("margin.marginY")){
|
||||
config["pady"] = this.getAttrValue("margin.marginY")
|
||||
}
|
||||
// if (this.getAttrValue("margin.marginY")){
|
||||
// config["pady"] = this.getAttrValue("margin.marginY")
|
||||
// }
|
||||
|
||||
// FIXME: add width and height, the scales may not be correct as the width and height are based on characters in label and grid not pixels
|
||||
// if (!this.state.fitContent.width){
|
||||
|
||||
@@ -41,7 +41,7 @@ export class Input extends TkinterWidgetBase{
|
||||
const config = convertObjectToKeyValueString(this.getConfigCode())
|
||||
|
||||
return [
|
||||
`${variableName} = tk.Entry(master=${parent}, text="${placeHolderText}")`,
|
||||
`${variableName} = tk.Entry(master=${parent})`,
|
||||
`${variableName}.config(${config})`,
|
||||
`${variableName}.${this.getLayoutCode()}`
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { Layouts } from "../../../canvas/constants/layouts"
|
||||
import Tools from "../../../canvas/constants/tools"
|
||||
import { convertObjectToKeyValueString } from "../../../utils/common"
|
||||
import { getPythonAssetPath } from "../../utils/pythonFilePath"
|
||||
@@ -10,6 +12,7 @@ class Label extends TkinterWidgetBase{
|
||||
static widgetType = "label"
|
||||
static displayName = "Label"
|
||||
|
||||
// static requiredCustomPyFiles = ["imageLabel"]
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
@@ -55,6 +58,34 @@ class Label extends TkinterWidgetBase{
|
||||
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)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,11 +104,20 @@ class Label extends TkinterWidgetBase{
|
||||
const imports = super.getImports()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
getRequiredCustomPyFiles(){
|
||||
const requiredCustomFiles = super.getRequiredCustomPyFiles()
|
||||
|
||||
if (this.getAttrValue("imageUpload"))
|
||||
requiredCustomFiles.push("imageLabel")
|
||||
|
||||
return requiredCustomFiles
|
||||
}
|
||||
|
||||
getRequirements(){
|
||||
const requirements = super.getRequirements()
|
||||
|
||||
@@ -92,10 +132,28 @@ class Label extends TkinterWidgetBase{
|
||||
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
|
||||
}
|
||||
|
||||
@@ -112,10 +170,10 @@ class Label extends TkinterWidgetBase{
|
||||
const code = []
|
||||
|
||||
if (image?.name){
|
||||
code.push(`${variableName}_img = Image.open(${getPythonAssetPath(image.name, "image")})`)
|
||||
code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||
// code.push(`${variableName}_img = Image.open(${getPythonAssetPath(image.name, "image")})`)
|
||||
// code.push(`${variableName}_img = ImageTk.PhotoImage(${variableName}_img)`)
|
||||
// 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")
|
||||
@@ -155,36 +213,53 @@ class Label extends TkinterWidgetBase{
|
||||
center: { justifyContent: 'center', alignItems: 'center' }
|
||||
}
|
||||
|
||||
return anchorStyles[anchor] || anchorStyles["w"];
|
||||
return anchorStyles[anchor] || anchorStyles["center"];
|
||||
}
|
||||
|
||||
renderContent(){
|
||||
|
||||
//FIXME: label image causing issues
|
||||
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 (
|
||||
<div className="tw-w-flex tw-flex-col tw-w-full tw-content-start tw-h-full tw-rounded-md tw-overflow-hidden"
|
||||
style={{
|
||||
flexGrow: 1, // Ensure the content grows to fill the parent
|
||||
minWidth: '100%', // Force the width to 100% of the parent
|
||||
minHeight: '100%', // Force the height to 100% of the parent
|
||||
}}
|
||||
<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={{
|
||||
// flexGrow: 1, // Ensure the content grows to fill the parent
|
||||
// minWidth: '100%', // Force the width 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()}>
|
||||
{/* {this.props.children} */}
|
||||
{
|
||||
image && (
|
||||
<img src={image.previewUrl} className="tw-bg-contain tw-w-full tw-h-full" />
|
||||
)
|
||||
}
|
||||
<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>
|
||||
{/* {this.props.children} */}
|
||||
{
|
||||
image ? (
|
||||
<div className="tw-relative tw-w-full tw-h-full tw-overflow-hidden">
|
||||
<img src={image.previewUrl}
|
||||
className={`${imgClassName}`}
|
||||
alt={this.getAttrValue("widgetName")}
|
||||
style={{
|
||||
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>
|
||||
)
|
||||
@@ -193,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
|
||||
@@ -14,6 +14,9 @@ import "./styles/index.css";
|
||||
|
||||
import { FileUploadProvider } from "./contexts/fileUploadContext";
|
||||
|
||||
window.React = React
|
||||
|
||||
|
||||
const originalSetItem = localStorage.setItem;
|
||||
// triggers itemsChaned event whenever the item in localstorage is chanegd.
|
||||
localStorage.setItem = function (key, value) {
|
||||
|
||||
@@ -47,6 +47,10 @@ function Premium({ children, className = "" }) {
|
||||
>
|
||||
more.
|
||||
</a>
|
||||
<br />
|
||||
<br />
|
||||
Premium features will start rolling out phase wise from mid of April, after which there would be a price increase.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
110
webpack.config.js
Normal file
110
webpack.config.js
Normal file
@@ -0,0 +1,110 @@
|
||||
const webpack = require('webpack');
|
||||
|
||||
const path = require("path");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
||||
|
||||
const dotenv = require('dotenv');
|
||||
const fs = require('fs');
|
||||
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
|
||||
// Load environment variables
|
||||
let envConfig = {};
|
||||
if (!isProduction && fs.existsSync(".env-cmdrc.json")) {
|
||||
const envFile = JSON.parse(fs.readFileSync(".env-cmdrc.json", "utf8"));
|
||||
envConfig = envFile[process.env.NODE_ENV] || {};
|
||||
} else {
|
||||
envConfig = Object.fromEntries(Object.entries(process.env));
|
||||
}
|
||||
|
||||
// Convert JSON to a format Webpack understands
|
||||
const envKeys = Object.keys(envConfig).reduce((prev, next) => {
|
||||
prev[`process.env.${next}`] = JSON.stringify(envConfig[next]);
|
||||
return prev;
|
||||
}, {});
|
||||
|
||||
module.exports = {
|
||||
mode: isProduction ? "production" : "development",
|
||||
|
||||
watch: !isProduction,
|
||||
watchOptions: {
|
||||
ignored: /node_modules/,
|
||||
},
|
||||
|
||||
entry: "./src/index.js",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: isProduction ? "js/[name].[contenthash].js" : "js/[name].js",
|
||||
publicPath: "./",
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: ["@babel/preset-env", "@babel/preset-react"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader",],
|
||||
},
|
||||
{
|
||||
test: /\.py$/,
|
||||
use: "raw-loader", // Load Python files as raw text
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: ["@svgr/webpack"], // Enables importing SVGs as React components
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|ico)$/,
|
||||
type: "asset/resource", // Handles image files
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(envKeys),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./public/index.html",
|
||||
minify: isProduction,
|
||||
inject: true,
|
||||
templateParameters: envConfig
|
||||
}),
|
||||
(isProduction ? new MiniCssExtractPlugin({ filename: 'styles.[contenthash].css' }) : new MiniCssExtractPlugin()),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, "src/assets"),
|
||||
to: "assets", // Copies to `dist/assets`
|
||||
noErrorOnMissing: true, // Prevents errors if the folder is missing
|
||||
},
|
||||
{ from: 'public', to: '', noErrorOnMissing: true, globOptions: { ignore: ['**/index.html'] }}, // Copies everything else from public to dist
|
||||
],
|
||||
}),
|
||||
|
||||
],
|
||||
|
||||
optimization: isProduction
|
||||
? {
|
||||
minimize: true,
|
||||
minimizer: [new CssMinimizerPlugin()],
|
||||
}
|
||||
: {},
|
||||
|
||||
devServer: {
|
||||
static: path.join(__dirname, "public"),
|
||||
port: 3000,
|
||||
hot: true,
|
||||
historyApiFallback: true,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user