feat: added checkbox and other widgets for tkinter

This commit is contained in:
paul
2024-09-23 12:31:01 +05:30
parent 1b9e049d91
commit 5d40894a40
18 changed files with 437 additions and 223 deletions

View File

@@ -1,3 +1,5 @@
{
"prettier.configPath": ".vscode/.prettierrc",
"markdown.extension.toc.updateOnSave": false
}

View File

@@ -1,5 +1,6 @@
# PyUIBuilder - The only Python GUI builder you'll ever need
<p align="center">
<a href="https://twitter.com/share?url=https://github.com/PaulleDemon/tkbuilder&text=Check out PyUIBuilder tool">
<img src="./assets/share/1.png" height="30" />
@@ -18,6 +19,20 @@
Build Python GUI's with the ease of Canva
## Table of contents
- [Features](#features)
- [Roadmap](#roadmap)
- [License](#license)
- [Newsletter](#newsletter)
- [FAQ](#faq)
- [License Information](#license-information)
- [Webbased Editor](#webbased-editor)
- [Electron App - Hobbyist License](#electron-app---hobbyist-license)
- [Electron App - Commercial License](#electron-app---commercial-license)
## Features
* Framework agnostic - Can outputs code in multiple frameworks.
@@ -37,7 +52,7 @@ To learn more/ see upcoming features visit [roadmap](./roadmap.md)
## License
To support open-source and development of this tool, consider buying a one-time license.
To support open-source and development of this tool and upcoming free open-source tools, consider buying a one-time license.
License will give you access to upcoming features, early access and more.
@@ -66,6 +81,10 @@ Join the free newsletter to know about upcoming updates, learn how I built this
[Join free newsletter](https://paulfreeman.substack.com/subscribe?utm_source=Github-Pybuilder)
#### Keep yourself updated
To keep up with the latest developments considering starting ⭐️ this repo
## FAQ

View File

@@ -1,12 +1,13 @@
## Road map for PyUIBuilder
Any feature that has 👑 beside it, is meant only for [premium users](./readme.md#license--support)
Any feature that has 👑 beside it, is meant only for [premium users](./readme.md#license)
### 1.0.0
- [x] Create the initial version for UI builder
### 1.2.0
- [ ] UI fixes and enhancement
- [ ] Documentation
- [ ] Tree view for elements on the canvas
- [ ] Add text editor to support event handlers
- [ ] Support more widgets

View File

@@ -26,6 +26,7 @@ import { DragWidgetProvider } from "./widgets/draggableWidgetContext"
import { PosType } from "./constants/layouts"
import WidgetContainer from "./constants/containers"
import { isSubClassOfWidget } from "../utils/widget"
import { ButtonModal } from "../components/modals"
// const DotsBackground = require("../assets/background/dots.svg")
@@ -115,8 +116,6 @@ class Canvas extends React.Component {
componentDidMount() {
this.initEvents()
this.createWidget(Widget)
}
componentWillUnmount() {
@@ -636,7 +635,7 @@ class Canvas extends React.Component {
// Find the dragged widget object
let dragWidgetObj = this.findWidgetFromListById(dragElementID)
console.log("Drag widget obj: ", dragWidgetObj)
// console.log("Drag widget obj: ", dragWidgetObj)
if (dropWidgetObj && dragWidgetObj) {
const dragWidget = this.widgetRefs[dragWidgetObj.id]
@@ -713,7 +712,7 @@ class Canvas extends React.Component {
throw new Error("widgetComponentType must be a subclass of Widget class")
}
console.log("componete: ", widgetComponentType)
// console.log("componete: ", widgetComponentType)
const widgetRef = React.createRef()
@@ -759,6 +758,11 @@ class Canvas extends React.Component {
let activeWidgets = removeDuplicateObjects([...widgets, this.state.selectedWidget], "__id")
this.setState({
toolbarAttrs: null,
selectedWidget: null
})
const widgetIds = activeWidgets.map(widget => widget.__id)
for (let widgetId of widgetIds) {
@@ -963,9 +967,17 @@ class Canvas extends React.Component {
<Tooltip title="Reset viewport">
<Button icon={<ReloadOutlined />} onClick={this.resetTransforms} />
</Tooltip>
<Tooltip title="Clear canvas">
<Button danger icon={<DeleteOutlined />} onClick={this.clearCanvas} />
</Tooltip>
<ButtonModal
message={"Are you sure you want to clear the canvas? This cannot be undone."}
title={"Clear canvas"}
onOk={this.clearCanvas}
okText="Yes"
okButtonType="danger"
>
<Tooltip title="Clear canvas">
<Button danger icon={<DeleteOutlined />} />
</Tooltip>
</ButtonModal>
</div>
{/* <ActiveWidgetProvider> */}

View File

@@ -5,6 +5,7 @@ const Tools = {
SELECT_DROPDOWN: "select_dropdown",
COLOR_PICKER: "color_picker",
EVENT_HANDLER: "event_handler", // shows a event handler with all the possible function in the dropdown
CHECK_BUTTON: "check_button",
LAYOUT_MANAGER: "layout_manager"
}

View File

@@ -1,6 +1,6 @@
import { memo, useEffect, useState } from "react"
import { ColorPicker, Input, InputNumber, Select } from "antd"
import { Checkbox, ColorPicker, Input, InputNumber, Select } from "antd"
import { capitalize } from "../utils/common"
import Tools from "./constants/tools.js"
@@ -186,6 +186,14 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
/>
)}
{val.tool === Tools.CHECK_BUTTON && (
<Checkbox
value={val.value}
defaultChecked={val.value}
onChange={(e) => handleChange(e.target.checked, val.onChange)}
>{val.label}</Checkbox>
)}
{
val.tool === Tools.LAYOUT_MANAGER && (
renderLayoutManager(val)
@@ -223,7 +231,7 @@ const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
tw-flex tw-flex-col tw-gap-2 tw-overflow-y-auto`}
>
<h3 className="tw-text-xl tw-text-center">
{capitalize(`${widgetType || ""}`)}
{capitalize(`${widgetType || ""}`).replace(/_/g, " ")}
</h3>
<div className="tw-flex tw-flex-col tw-gap-4">{renderWidgets(toolbarAttrs || {})}</div>

View File

@@ -55,7 +55,9 @@ class Widget extends React.Component {
"load": { "args1": "number", "args2": "string" }
}
this.droppableTags = {} // This indicates if the draggable can be dropped on this widget
// This indicates if the draggable can be dropped on this widget, set this to null to disable drops
this.droppableTags = {}
this.boundingRect = {
x: 0,
y: 0,
@@ -168,9 +170,14 @@ class Widget extends React.Component {
componentDidMount() {
// FIXME: initial layout is not set properly
console.log("prior layout: ", this.state.attrs.layout.value)
this.setLayout(this.state.attrs.layout.value)
this.setWidgetStyling('backgroundColor', this.state.attrs.styling?.backgroundColor.value || "#fff")
if (this.state.attrs.layout){
this.setLayout(this.state.attrs.layout.value)
console.log("prior layout: ", this.state.attrs.layout.value)
}
if (this.state.attrs.styling.backgroundColor)
this.setWidgetStyling('backgroundColor', this.state.attrs.styling?.backgroundColor.value || "#fff")
this.load(this.props.initialData || {}) // load the initial data
@@ -577,26 +584,6 @@ class Widget extends React.Component {
}
/**
*
* @depreciated - This function is depreciated in favour of handleDropEvent()
*/
handleDrop = (event, dragElement) => {
// THIS function is depreciated in favour of handleDropEvent()
// console.log("dragging event: ", event, dragElement)
const container = dragElement.getAttribute("data-container")
// TODO: check if the drop is allowed
if (container === "canvas") {
this.props.onAddChildWidget(this.__id, dragElement.getAttribute("data-widget-id"))
} else if (container === "sidebar") {
this.props.onAddChildWidget(this.__id, null, true) // if dragged from the sidebar create the widget first
}
}
handleDragStart = (e, callback) => {
e.stopPropagation()
@@ -653,7 +640,7 @@ class Widget extends React.Component {
show: true
}
const allowDrop = (this.droppableTags && (Object.keys(this.droppableTags).length === 0 ||
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))
))
@@ -686,7 +673,7 @@ class Widget extends React.Component {
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
const dragEleType = draggedElement.getAttribute("data-draggable-type")
const allowDrop = (this.droppableTags && (Object.keys(this.droppableTags).length === 0 ||
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))
))
@@ -713,7 +700,7 @@ class Widget extends React.Component {
const dragEleType = draggedElement.getAttribute("data-draggable-type")
const allowDrop = (this.droppableTags && (Object.keys(this.droppableTags).length === 0 ||
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))
))
@@ -816,7 +803,7 @@ class Widget extends React.Component {
* This is an internal methods don't override
* @returns {HTMLElement}
*/
render() {
render() {
let outerStyle = {
cursor: this.cursor,
@@ -962,27 +949,6 @@ class Widget extends React.Component {
}
</DragContext.Consumer>
// <WidgetDraggable widgetRef={this.elementRef}
// enableDrag={this.state.dragEnabled}
// onDrop={this.handleDrop}
// onDragEnter={({dragElement, showDrop}) => {
// this.setState({
// showDroppableStyle: showDrop
// })
// }
// }
// onDragLeave={ () => {
// this.setState({
// showDroppableStyle: {
// allow: false,
// show: false
// }
// })
// }
// }
// >
// </WidgetDraggable>
)
}

View File

@@ -29,7 +29,7 @@ export function SidebarWidgetCard({ name, img, url, widgetClass, innerRef}){
return (
// <Draggable className="tw-cursor-pointer" id={name}>
<DraggableWrapper data-container={"sidebar"}
dragElementType={"widget"}
dragElementType={widgetClass.widgetType}
dragWidgetClass={widgetClass}
className="tw-cursor-pointer tw-w-fit tw-h-fit">

View File

@@ -3,7 +3,7 @@ import { useDragContext } from "./draggableContext"
/**
* @param {{include: [], exclude: []} || {}} - droppableTags - if empty object, allows everything to be dropped, define include to allow only included widgets, define exclude to exclude
* @param {{include: [], exclude: []} || {} || null} - droppableTags - if empty object, allows everything to be dropped, if null nothing can be dropped, define include to allow only included widgets, define exclude to exclude
*/
const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
@@ -20,11 +20,10 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
const dragElementType = draggedElement.getAttribute("data-draggable-type")
console.log("Current target: ", droppableTags, Object.keys(droppableTags))
setOverElement(e.currentTarget)
const allowDrop = (droppableTags && (Object.keys(droppableTags).length === 0 ||
const allowDrop = (droppableTags && droppableTags !== null && (Object.keys(droppableTags).length === 0 ||
(droppableTags.include?.length > 0 && droppableTags.include?.includes(dragElementType)) ||
(droppableTags.exclude?.length > 0 && !droppableTags.exclude?.includes(dragElementType))
))
@@ -46,7 +45,7 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
const dragElementType = draggedElement.getAttribute("data-draggable-type")
const allowDrop = (droppableTags && (Object.keys(droppableTags).length === 0 ||
const allowDrop = (droppableTags && droppableTags !== null && (Object.keys(droppableTags).length === 0 ||
(droppableTags.include?.length > 0 && droppableTags.include?.includes(dragElementType)) ||
(droppableTags.exclude?.length > 0 && !droppableTags.exclude?.includes(dragElementType))
))
@@ -67,7 +66,7 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
const dragElementType = draggedElement.getAttribute("data-draggable-type")
const allowDrop = (droppableTags && (Object.keys(droppableTags).length === 0 ||
const allowDrop = (droppableTags && droppableTags !== null && (Object.keys(droppableTags).length === 0 ||
(droppableTags.include?.length > 0 && droppableTags.include?.includes(dragElementType)) ||
(droppableTags.exclude?.length > 0 && !droppableTags.exclude?.includes(dragElementType))
))

51
src/components/modals.js Normal file
View File

@@ -0,0 +1,51 @@
import { useState } from "react"
import { Modal } from "antd"
/**
* opens modal when clicked, acts as a wrapper
* @param {string} - message
* @param {string} - title
* @param {string} - okText
* @param {"default"|"danger"|"primary"} - okButtonType
* @returns
*/
export const ButtonModal = ({ message, title, okText="OK", onOk, onCancel, okButtonType="default", children}) => {
const [isModalOpen, setIsModalOpen] = useState(false)
const showModal = () => {
setIsModalOpen(true)
}
const handleOk = () => {
setIsModalOpen(false)
console.log("Ok pressed")
if (onOk){
onOk()
}
}
const handleCancel = (e) => {
e.stopPropagation()
setIsModalOpen(false)
console.log("cancel pressed")
if (onCancel){
onCancel()
}
}
return (
<div onClick={showModal}>
{children}
<Modal title={title} open={isModalOpen} onClose={handleCancel}
okText={okText}
onOk={handleOk} okType={okButtonType} onCancel={handleCancel}>
<p>{message}</p>
</Modal>
</div>
)
}

View File

@@ -2,9 +2,10 @@
import Widget from "../../canvas/widgets/base"
import ButtonWidget from "./assets/widgets/button.png"
import { CheckBox } from "./widgets/ checkButton"
import Button from "./widgets/button"
import Frame from "./widgets/frame"
import Input from "./widgets/input"
import { Input, Text } from "./widgets/input"
import Label from "./widgets/label"
import MainWindow from "./widgets/mainWindow"
import TopLevel from "./widgets/toplevel"
@@ -42,12 +43,50 @@ const TkinterSidebar = [
widgetClass: Button
},
{
name: "Input",
name: "Entry",
img: ButtonWidget,
link: "https://github.com",
widgetClass: Input
},
{
name: "Text",
img: ButtonWidget,
link: "https://github.com",
widgetClass: Text
},
{
name: "CheckBox",
img: ButtonWidget,
link: "https://github.com",
widgetClass: CheckBox
},
]
export default TkinterSidebar
export default TkinterSidebar
/**
* widgets = {
"Tk": set(),
"Label": set(),
"Button": set(),
"Entry": set(),
"CheckButton": set(),
"RadioButton": set(),
"Scale": set(),
"ListBox": set(),
"Frame": set(),
"LabelFrame": set(),
"PanedWindow": set(),
"SpinBox": set(),
"OptionMenu": set(),
"Canvas": set(),
"TopLevel": set(),
"Message": set(),
"Menu": set(),
"MenuButton": set(),
"ScrollBar": set(),
"Text": set()
}
*/

View File

@@ -0,0 +1,106 @@
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
import { Checkbox } from "antd"
import { removeKeyFromObject } from "../../../utils/common"
import { CheckOutlined, CheckSquareFilled } from "@ant-design/icons"
export class CheckBox extends Widget{
static widgetType = "check_button"
// TODO: remove layouts
constructor(props) {
super(props)
this.droppableTags = null // disables drops
// const {layout, ...newAttrs} = this.state.attrs // Removes the layout attribute
let newAttrs = removeKeyFromObject("layout", this.state.attrs)
newAttrs = removeKeyFromObject("styling.backgroundColor", newAttrs)
this.minSize = {width: 50, height: 30}
this.state = {
...this.state,
size: { width: 120, height: 30 },
attrs: {
...newAttrs,
styling: {
foregroundColor: {
label: "Foreground Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "#000",
onChange: (value) => {
this.setWidgetStyling("color", value)
this.setAttrValue("styling.foregroundColor", value)
}
}
},
checkLabel: {
label: "Check Label",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: {placeholder: "Button label", maxLength: 100},
value: "Checkbox",
onChange: (value) => this.setAttrValue("checkLabel", value)
},
defaultChecked: {
label: "Checked",
tool: Tools.CHECK_BUTTON, // the tool to display, can be either HTML ELement or a constant string
toolProps: {placeholder: "text", maxLength: 100},
value: true,
onChange: (value) => this.setAttrValue("defaultChecked", value)
}
}
}
}
componentDidMount(){
super.componentDidMount()
// this.setAttrValue("styling.backgroundColor", "#fff")
this.setWidgetName("Checkbox")
this.setWidgetStyling("backgroundColor", "#fff0")
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
const attrs = this.state.attrs
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
checkLabel: attrs.checkLabel,
size: toolBarAttrs.size,
...attrs,
})
}
renderContent(){
return (
<div className="tw-flex tw-p-1 tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden"
style={this.state.widgetStyling}
>
<div className="tw-flex tw-gap-2 tw-w-full tw-h-full tw-place-items-center tw-place-content-center">
<div className="tw-border-solid tw-border-[#D9D9D9] tw-border-2
tw-min-w-[20px] tw-min-h-[20px] tw-w-[20px] tw-h-[20px]
tw-text-blue-600 tw-flex tw-items-center tw-justify-center
tw-rounded-md tw-overflow-hidden">
{
this.getAttrValue("defaultChecked") === true &&
<CheckSquareFilled className="tw-text-[20px]" />
}
</div>
{this.getAttrValue("checkLabel")}
</div>
</div>
)
}
}

View File

@@ -1,5 +1,6 @@
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
import { removeKeyFromObject } from "../../../utils/common"
class Button extends Widget{
@@ -15,14 +16,15 @@ class Button extends Widget{
// TODO: exclude all
exclude: ["image", "video", "media", "main_window", "toplevel"]
}
const newAttrs = removeKeyFromObject("layout", this.state.attrs)
this.state = {
...this.state,
size: { width: 80, height: 40 },
attrs: {
...this.state.attrs,
...newAttrs,
styling: {
...this.state.attrs.styling,
...newAttrs.styling,
foregroundColor: {
label: "Foreground Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
@@ -47,38 +49,20 @@ class Button extends Widget{
componentDidMount(){
super.componentDidMount()
this.setWidgetName("button")
this.setAttrValue("styling.backgroundColor", "#E4E2E2")
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: {
label: "Widget Name",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: { placeholder: "Widget name", maxLength: 40 },
value: this.state.widgetName,
onChange: (value) => this.setWidgetName(value)
},
widgetName: toolBarAttrs.widgetName,
buttonLabel: this.state.attrs.buttonLabel,
size: {
label: "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: this.maxSize.width, min: this.minSize.width },
value: this.state.size.width || 100,
onChange: (value) => this.setWidgetSize(value, null)
},
height: {
label: "Height",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "height", max: this.maxSize.height, min: this.minSize.height },
value: this.state.size.height || 100,
onChange: (value) => this.setWidgetSize(null, value)
},
},
size: toolBarAttrs.widgetName,
...this.state.attrs,
@@ -87,7 +71,8 @@ class Button extends Widget{
renderContent(){
return (
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-border tw-border-solid tw-overflow-hidden">
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md
tw-border tw-border-solid tw-border-gray-400 tw-overflow-hidden">
<div className="tw-p-2 tw-w-full tw-h-full tw-content-start " style={this.state.widgetStyling}>
{/* {this.props.children} */}
<div className="tw-text-sm" style={{color: this.getAttrValue("styling.foregroundColor")}}>

View File

@@ -1,26 +1,26 @@
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
import { removeKeyFromObject } from "../../../utils/common"
class Input extends Widget{
export class Input extends Widget{
static widgetType = "input"
// TODO: override the widgetName value
static widgetType = "entry"
// TODO: remove layouts
constructor(props) {
super(props)
this.droppableTags = {
// TODO: exclude all
exclude: ["image", "video", "media", "main_window", "toplevel"]
}
this.droppableTags = null // disables drops
const newAttrs = removeKeyFromObject("layout", this.state.attrs)
this.state = {
...this.state,
size: { width: 120, height: 40 },
attrs: {
...this.state.attrs,
...newAttrs,
styling: {
...this.state.attrs.styling,
...newAttrs.styling,
foregroundColor: {
label: "Foreground Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
@@ -46,37 +46,93 @@ class Input extends Widget{
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#fff")
this.setWidgetName("Entry")
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: {
label: "Widget Name",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: { placeholder: "Widget name", maxLength: 40 },
value: this.state.widgetName,
onChange: (value) => this.setWidgetName(value)
},
widgetName: toolBarAttrs.widgetName,
placeHolder: this.state.attrs.placeHolder,
size: {
label: "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: this.maxSize.width, min: this.minSize.width },
value: this.state.size.width || 100,
onChange: (value) => this.setWidgetSize(value, null)
size: toolBarAttrs.widgetName,
...this.state.attrs,
})
}
renderContent(){
return (
<div className="tw-w-flex tw-flex-col tw-w-full tw-h-full tw-rounded-md tw-overflow-hidden">
<div className="tw-p-2 tw-w-full tw-h-full tw-flex tw-place-items-center" style={this.state.widgetStyling}>
<div className="tw-text-sm tw-text-gray-300">
{this.getAttrValue("placeHolder")}
</div>
</div>
</div>
)
}
}
export class Text extends Widget{
static widgetType = "Text"
constructor(props) {
super(props)
this.droppableTags = null
const newAttrs = removeKeyFromObject("layout", this.state.attrs)
this.state = {
...this.state,
size: { width: 120, height: 80 },
attrs: {
...newAttrs,
styling: {
...newAttrs.styling,
foregroundColor: {
label: "Foreground Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
value: "#000",
onChange: (value) => {
this.setWidgetStyling("color", value)
this.setAttrValue("styling.foregroundColor", value)
}
}
},
height: {
label: "Height",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "height", max: this.maxSize.height, min: this.minSize.height },
value: this.state.size.height || 100,
onChange: (value) => this.setWidgetSize(null, value)
},
},
placeHolder: {
label: "PlaceHolder",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: {placeholder: "text", maxLength: 100},
value: "placeholder text",
onChange: (value) => this.setAttrValue("placeHolder", value)
}
}
}
}
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#fff")
this.setWidgetName("text")
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: toolBarAttrs.widgetName,
placeHolder: this.state.attrs.placeHolder,
size: toolBarAttrs.size,
...this.state.attrs,
@@ -96,6 +152,3 @@ class Input extends Widget{
}
}
export default Input

View File

@@ -1,5 +1,6 @@
import Widget from "../../../canvas/widgets/base"
import Tools from "../../../canvas/constants/tools"
import { removeKeyFromObject } from "../../../utils/common"
class Label extends Widget{
@@ -14,13 +15,16 @@ class Label extends Widget{
exclude: ["image", "video", "media", "main_window", "toplevel"]
}
const newAttrs = removeKeyFromObject("layout", this.state.attrs)
this.state = {
...this.state,
size: { width: 80, height: 40 },
attrs: {
...this.state.attrs,
...newAttrs,
styling: {
...this.state.attrs.styling,
...newAttrs.styling,
foregroundColor: {
label: "Foreground Color",
tool: Tools.COLOR_PICKER, // the tool to display, can be either HTML ELement or a constant string
@@ -45,38 +49,18 @@ class Label extends Widget{
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#E4E2E2")
this.setAttrValue("styling.backgroundColor", "#fff")
this.setWidgetStyling("backgroundColor", "#fff0")
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: {
label: "Widget Name",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: { placeholder: "Widget name", maxLength: 40 },
value: this.state.widgetName,
onChange: (value) => this.setWidgetName(value)
},
widgetName: toolBarAttrs.widgetName,
labelWidget: this.state.attrs.labelWidget,
size: {
label: "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: this.maxSize.width, min: this.minSize.width },
value: this.state.size.width || 100,
onChange: (value) => this.setWidgetSize(value, null)
},
height: {
label: "Height",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "height", max: this.maxSize.height, min: this.minSize.height },
value: this.state.size.height || 100,
onChange: (value) => this.setWidgetSize(null, value)
},
},
size: toolBarAttrs.size,
...this.state.attrs,

View File

@@ -33,37 +33,17 @@ class MainWindow extends Widget{
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#E4E2E2")
this.setWidgetName("main")
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: {
label: "Widget Name",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: { placeholder: "Widget name", maxLength: 40 },
value: this.state.widgetName,
onChange: (value) => this.setWidgetName(value)
},
widgetName: toolBarAttrs.widgetName,
title: this.state.attrs.title,
size: {
label: "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: this.maxSize.width, min: this.minSize.width },
value: this.state.size.width || 100,
onChange: (value) => this.setWidgetSize(value, null)
},
height: {
label: "Height",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "height", max: this.maxSize.height, min: this.minSize.height },
value: this.state.size.height || 100,
onChange: (value) => this.setWidgetSize(null, value)
},
},
size: toolBarAttrs.size,
...this.state.attrs,

View File

@@ -34,37 +34,16 @@ class TopLevel extends Widget{
componentDidMount(){
super.componentDidMount()
this.setAttrValue("styling.backgroundColor", "#E4E2E2")
this.setWidgetName("toplevel")
}
getToolbarAttrs(){
const toolBarAttrs = super.getToolbarAttrs()
return ({
id: this.__id,
widgetName: {
label: "Widget Name",
tool: Tools.INPUT, // the tool to display, can be either HTML ELement or a constant string
toolProps: { placeholder: "Widget name", maxLength: 40 },
value: this.state.widgetName,
onChange: (value) => this.setWidgetName(value)
},
widgetName: toolBarAttrs.widgetName,
title: this.state.attrs.title,
size: {
label: "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: this.maxSize.width, min: this.minSize.width },
value: this.state.size.width || 100,
onChange: (value) => this.setWidgetSize(value, null)
},
height: {
label: "Height",
tool: Tools.NUMBER_INPUT,
toolProps: { placeholder: "height", max: this.maxSize.height, min: this.minSize.height },
value: this.state.size.height || 100,
onChange: (value) => this.setWidgetSize(null, value)
},
},
size: toolBarAttrs.size,
...this.state.attrs,

View File

@@ -2,15 +2,15 @@
export function removeDuplicateObjects(array, key) {
const seen = new Set()
return array.filter(item => {
if (!seen.has(item[key])) {
seen.add(item[key])
return true
}
return false
})
const seen = new Set()
return array.filter(item => {
if (!seen.has(item[key])) {
seen.add(item[key])
return true
}
return false
})
}
@@ -20,5 +20,34 @@ export function removeDuplicateObjects(array, key) {
* @returns
*/
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
return str.charAt(0).toUpperCase() + str.slice(1)
}
/**
* Given the key as a path, removes the widget attribute at the given path
* @param {string} path - path to the key, eg: styling.backgroundColor
* @param {{}} _object - object with key and value
*/
export function removeKeyFromObject(path, _object) {
const keys = path.split('.')
const lastKey = keys.pop()
// Traverse the state and find the nested object up to the second last key
let newAttrs = { ..._object }
let nestedObject = newAttrs
for (const key of keys) {
if (nestedObject[key] !== undefined) {
nestedObject[key] = { ...nestedObject[key] } // Ensure immutability
nestedObject = nestedObject[key]
} else {
return // Key doesn't exist, so nothing to remove
}
}
// Remove the attribute
delete nestedObject[lastKey]
return newAttrs
}