@@ -61,7 +61,7 @@ The discount's will be available for limited time only on pre-orders.
| Type | Free | Premium - Hobbyist / Per user | Premium - Commercial / Per user |
|-------------------------------------------------------------------|-------------------|----------------------------------------------------------|------------------------------------------------------------|
| **Support open-source development** | 👍️ | 😎 | 🚀 |
-| **Priority support** - (priorities your feature requests, issues) | community support | ✅ | ✅ |
+| **Priority support** - (prioritize your feature requests, issues) | community support | ✅ | ✅ |
| **Lifetime license** (one-time purchase) | 👍️ | ✅ | ✅ |
| **Early access** to upcoming features | ❌ | ✅ | ✅ |
| **Downloadable Electron App** (upcoming) | ❌ | ✅ | ✅ |
@@ -72,7 +72,7 @@ The discount's will be available for limited time only on pre-orders.
| **Commercial Use** | ✅ | ❌ | ✅ |
| **Support for PyQt/PySide frameworks** (upcoming) | ❌ | ❌ | ✅ |
| **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]() | [Get license]() |
## Newsletter
@@ -105,6 +105,12 @@ To keep up with the latest developments considering starting ⭐️ this repo
* Support for 3rd party UI libraries. Many GUI builders don't come with support for 3rd party libraries.
+4. **Why doesn't the theme of the GUI builder match the theme of Tkinter?**
+
+ **A.** Tkinter is a OS-dependent library, so it would render differently on different OS. Having a common UI the the GUI builder makes it simpler for development.
+
+ If you want a live preview before generating the code you can get a premium license and you'll be notified when that feature releases.
+
## License Information
To support development of this project, license differ depending on the usecase.
diff --git a/src/canvas/canvas.js b/src/canvas/canvas.js
index 5cf3bc8..261db6f 100644
--- a/src/canvas/canvas.js
+++ b/src/canvas/canvas.js
@@ -1,11 +1,11 @@
import React from "react"
-import { DndContext } from '@dnd-kit/core'
+// import { DndContext } from '@dnd-kit/core'
-import { CloseOutlined, DeleteOutlined, EditOutlined, FullscreenOutlined, ReloadOutlined } from "@ant-design/icons"
+import { DeleteOutlined, EditOutlined, ReloadOutlined } from "@ant-design/icons"
import { Button, Tooltip, Dropdown } from "antd"
-import Droppable from "../components/utils/droppableDnd"
+// import Droppable from "../components/utils/droppableDnd"
import Widget from "./widgets/base"
import Cursor from "./constants/cursor"
@@ -14,15 +14,12 @@ import CanvasToolBar from "./toolbar"
import { UID } from "../utils/uid"
import { removeDuplicateObjects } from "../utils/common"
-import { WidgetContext } from './context/widgetContext'
-// import {ReactComponent as DotsBackground} from "../assets/background/dots.svg"
// import DotsBackground from "../assets/background/dots.svg"
import { ReactComponent as DotsBackground } from "../assets/background/dots.svg"
import DroppableWrapper from "../components/draggable/droppable"
-import { ActiveWidgetContext, ActiveWidgetProvider, withActiveWidget } from "./activeWidgetContext"
-import { DragWidgetProvider } from "./widgets/draggableWidgetContext"
+
import { PosType } from "./constants/layouts"
import WidgetContainer from "./constants/containers"
import { isSubClassOfWidget } from "../utils/widget"
@@ -629,7 +626,6 @@ class Canvas extends React.Component {
*/
handleAddWidgetChild = ({ parentWidgetId, dragElementID, swap = false }) => {
- // TODO: creation of the child widget if its not created
// widgets data structure { id, widgetType: widgetComponentType, children: [], parent: "" }
const dropWidgetObj = this.findWidgetFromListById(parentWidgetId)
// Find the dragged widget object
@@ -846,7 +842,6 @@ class Canvas extends React.Component {
throw new Error("WidgetClass has to be passed for widgets dropped from sidebar")
}
- // TODO: handle drop from sidebar
// if the widget is being dropped from the sidebar, use the info to create the widget first
this.createWidget(widgetClass, ({ id, widgetRef }) => {
widgetRef.current.setPos(finalPosition.x, finalPosition.y)
@@ -878,8 +873,6 @@ class Canvas extends React.Component {
// remove child from current position
- // console.log("pre updated widgets: ", updatedWidgets)
-
const updatedChildWidget = {
...childWidgetObj,
parent: "",
diff --git a/src/canvas/constants/tools.js b/src/canvas/constants/tools.js
index 632211c..c7e4af9 100644
--- a/src/canvas/constants/tools.js
+++ b/src/canvas/constants/tools.js
@@ -6,6 +6,8 @@ const Tools = {
COLOR_PICKER: "color_picker",
EVENT_HANDLER: "event_handler", // shows a event handler with all the possible function in the dropdown
CHECK_BUTTON: "check_button",
+ INPUT_LIST: "input_list",
+ INPUT_RADIO_LIST: "input_radio_list",
LAYOUT_MANAGER: "layout_manager"
}
diff --git a/src/canvas/toolbar.js b/src/canvas/toolbar.js
index 51b353b..64d8eb4 100644
--- a/src/canvas/toolbar.js
+++ b/src/canvas/toolbar.js
@@ -6,6 +6,7 @@ import { capitalize } from "../utils/common"
import Tools from "./constants/tools.js"
import { useActiveWidget } from "./activeWidgetContext.js"
import { Layouts } from "./constants/layouts.js"
+import { DynamicRadioInputList } from "../components/inputs.js"
// FIXME: Maximum recursion error
@@ -188,12 +189,20 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
{val.tool === Tools.CHECK_BUTTON && (
handleChange(e.target.checked, val.onChange)}
>{val.label}
)}
+ {val.tool === Tools.INPUT_RADIO_LIST && (
+ handleChange({inputs, selectedRadio}, val.onChange)}
+ />
+ )}
+
{
val.tool === Tools.LAYOUT_MANAGER && (
renderLayoutManager(val)
@@ -207,7 +216,7 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
// Handle nested objects and horizontal display for inner elements
if (typeof val === "object") {
const containerClass = val.display === "horizontal"
- ? "tw-flex tw-flex-row tw-gap-4"
+ ? "tw-flex tw-flex-row tw-flex-wrap tw-content-start tw-gap-4"
: "tw-flex tw-flex-col tw-gap-2"
return (
@@ -227,7 +236,7 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
diff --git a/src/canvas/widgets/base.js b/src/canvas/widgets/base.js
index 5349d9d..25408d3 100644
--- a/src/canvas/widgets/base.js
+++ b/src/canvas/widgets/base.js
@@ -569,18 +569,39 @@ class Widget extends React.Component {
if (Object.keys(data).length === 0) return // no data to load
- for (let [key, value] of Object.entries(data.attrs | {}))
- this.setAttrValue(key, value)
+ data = {...data} // create a shallow copy
- delete data.attrs
+ const {attrs, ...restData} = data
- /**
- * const obj = { a: 1, b: 2, c: 3 }
- * const { b, ...newObj } = obj
- * console.log(newObj) // { a: 1, c: 3 }
- */
+ // for (let [key, value] of Object.entries(attrs | {}))
+ // this.setAttrValue(key, value)
- this.setState(data)
+ // delete data.attrs
+
+ this.setState(restData, () => {
+ // UPdates attrs
+ let newAttrs = { ...this.state.attrs }
+
+ // Iterate over each path in the updates object
+ Object.entries(attrs).forEach(([path, value]) => {
+ const keys = path.split('.')
+ const lastKey = keys.pop()
+
+ // Traverse the nested object within attrs
+ let nestedObject = newAttrs
+
+ keys.forEach(key => {
+ nestedObject[key] = { ...nestedObject[key] } // Ensure immutability for each nested level
+ nestedObject = nestedObject[key]
+ })
+
+ // Set the value at the last key
+ nestedObject[lastKey].value = value
+ })
+
+ this.updateState({ attrs: newAttrs })
+
+ })
}
@@ -698,17 +719,6 @@ class Widget extends React.Component {
})
- const dragEleType = draggedElement.getAttribute("data-draggable-type")
-
- const allowDrop = (this.droppableTags && this.droppableTags !== null && (Object.keys(this.droppableTags).length === 0 ||
- (this.droppableTags.include?.length > 0 && this.droppableTags.include?.includes(dragEleType)) ||
- (this.droppableTags.exclude?.length > 0 && !this.droppableTags.exclude?.includes(dragEleType))
- ))
-
- if (!allowDrop) {
- return // prevent drop if the draggable element doesn't match
- }
-
if (draggedElement === this.elementRef.current){
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
return
@@ -731,6 +741,17 @@ class Widget extends React.Component {
// If swaparea is true, then it swaps instead of adding it as a child, also make sure that the parent widget(this widget) is on the widget and not on the canvas
const swapArea = (this.swappableAreaRef.current.contains(e.target) && !this.innerAreaRef.current.contains(e.target) && thisContainer === WidgetContainer.WIDGET)
+ const dragEleType = draggedElement.getAttribute("data-draggable-type")
+
+ const allowDrop = (this.droppableTags && this.droppableTags !== null && (Object.keys(this.droppableTags).length === 0 ||
+ (this.droppableTags.include?.length > 0 && this.droppableTags.include?.includes(dragEleType)) ||
+ (this.droppableTags.exclude?.length > 0 && !this.droppableTags.exclude?.includes(dragEleType))
+ ))
+
+ if (!allowDrop && !swapArea) {
+ // only if both swap and drop is not allowed return, if swap is allowed continue
+ return
+ }
// TODO: check if the drop is allowed
if ([WidgetContainer.CANVAS, WidgetContainer.WIDGET].includes(container)) {
// console.log("Dropped on meee: ", swapArea, this.swappableAreaRef.current.contains(e.target), thisContainer)
diff --git a/src/components/inputs.js b/src/components/inputs.js
new file mode 100644
index 0000000..5b67347
--- /dev/null
+++ b/src/components/inputs.js
@@ -0,0 +1,124 @@
+import React, { useEffect, useState } from "react"
+import { Input, Button, Space, Radio } from "antd"
+import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"
+
+
+export const DynamicInputList = () => {
+ const [inputs, setInputs] = useState([""]) // Initialize with one input
+
+ const addInput = () => {
+ setInputs([...inputs, ""])
+ }
+
+ const removeInput = (index) => {
+ setInputs(inputs.filter((_, i) => i !== index))
+ }
+
+ const handleInputChange = (value, index) => {
+ const newInputs = [...inputs]
+ newInputs[index] = value
+ setInputs(newInputs)
+ }
+
+ return (
+
+ {inputs.map((input, index) => (
+
+ handleInputChange(e.target.value, index)}
+ placeholder={`Input ${index + 1}`}
+ />
+ {index !== 0 && ( // Do not show delete button for the first input
+ removeInput(index)} />
+ )}
+
+ ))}
+
+ }>
+ Add Input
+
+