Files
PyUIBuilder/src/canvas/toolbar.js

304 lines
11 KiB
JavaScript
Raw Normal View History

2024-09-15 15:31:04 +05:30
import { memo, useEffect, useState } from "react"
2024-09-13 19:24:03 +05:30
import {
Checkbox, ColorPicker, Input,
InputNumber, Select, Collapse
} from "antd"
2024-09-13 19:24:03 +05:30
2024-09-13 16:03:58 +05:30
import { capitalize } from "../utils/common"
2024-09-13 22:05:38 +05:30
import Tools from "./constants/tools.js"
2024-09-16 22:04:24 +05:30
import { useActiveWidget } from "./activeWidgetContext.js"
2024-09-19 19:26:10 +05:30
import { Layouts } from "./constants/layouts.js"
import { DynamicRadioInputList } from "../components/inputs.js"
2024-09-13 16:03:58 +05:30
2024-09-15 12:08:29 +05:30
// FIXME: Maximum recursion error
2024-09-13 19:24:03 +05:30
/**
*
* @param {boolean} isOpen
2024-09-15 12:08:29 +05:30
* @param {string} widgetType
* @param {object} attrs - widget attributes
2024-09-13 19:24:03 +05:30
*/
2024-09-18 22:16:34 +05:30
const CanvasToolBar = memo(({ isOpen, widgetType, attrs = {} }) => {
2024-09-13 16:03:58 +05:30
2024-09-16 22:04:24 +05:30
// const { activeWidgetAttrs } = useActiveWidget()
// console.log("active widget context: ", activeWidgetAttrs)
2024-09-13 16:03:58 +05:30
const [toolbarOpen, setToolbarOpen] = useState(isOpen)
2024-09-15 12:08:29 +05:30
const [toolbarAttrs, setToolbarAttrs] = useState(attrs)
2024-09-13 16:03:58 +05:30
2024-09-13 16:03:58 +05:30
useEffect(() => {
setToolbarOpen(isOpen)
}, [isOpen])
2024-09-15 12:08:29 +05:30
useEffect(() => {
setToolbarAttrs(attrs)
}, [attrs])
const handleChange = (value, callback) => {
if (callback) {
2024-09-14 16:03:26 +05:30
callback(value)
}
}
2024-09-18 22:16:34 +05:30
const renderLayoutManager = (val) => {
return (
<div className="tw-flex tw-flex-col tw-gap-2">
<Select
options={[
{ value: Layouts.FLEX, label: "Flex" },
{ value: Layouts.GRID, label: "Grid" },
{ value: Layouts.PLACE, label: "Place" },
]}
showSearch
value={val.value?.layout || ""}
placeholder={`${val.label}`}
size="medium"
onChange={(value) => handleChange({ ...val.value, layout: value }, val.onChange)}
2024-09-18 22:16:34 +05:30
/>
<div className="tw-flex tw-flex-col tw-gap-1">
<span className="tw-text-sm">Direction</span>
<Select
options={[
{ value: "column", label: "Vertical" },
{ value: "row", label: "Horizontal" },
2024-09-18 22:16:34 +05:30
]}
showSearch
value={val.value?.direction || "row"}
2024-09-18 22:16:34 +05:30
placeholder={`${val.label}`}
onChange={(value) => handleChange({ ...val.value, direction: value }, val.onChange)}
/>
</div>
<div className="tw-flex tw-flex-col">
<span className="tw-text-sm">Gap</span>
<InputNumber
max={500}
min={1}
value={val.value?.gap || 10}
size="small"
onChange={(value) => {
handleChange({ ...val.value, gap: value }, val.onChange)
}}
2024-09-18 22:16:34 +05:30
/>
</div>
2024-09-25 17:27:12 +05:30
{/* <div className="tw-flex tw-flex-col">
2024-09-18 22:16:34 +05:30
<span className="tw-text-sm tw-font-medium">Grids</span>
<div className="tw-flex tw-gap-2">
<div className="tw-flex tw-flex-col">
<span className="tw-text-sm">Rows</span>
<InputNumber
max={12}
min={1}
value={val.value?.grid.rows || 1}
size="small"
onChange={(value) => {
let newGrid = {
rows: value,
cols: val.value?.grid.cols
}
handleChange({ ...val.value, grid: newGrid }, val.onChange)
}}
2024-09-18 22:16:34 +05:30
/>
</div>
<div className="tw-flex tw-flex-col">
<span className="tw-text-sm">Columns</span>
<InputNumber
max={12}
min={1}
value={val.value?.grid.cols || 1}
size="small"
onChange={(value) => {
let newGrid = {
rows: val.value?.grid.cols,
cols: value
}
handleChange({ ...val.value, grid: newGrid }, val.onChange)
}}
2024-09-18 22:16:34 +05:30
/>
</div>
</div>
2024-09-25 17:27:12 +05:30
</div> */}
2024-09-18 22:16:34 +05:30
</div>
)
}
const renderTool = (keyName, val) => {
return (
<>
{val.tool === Tools.INPUT && (
<Input
{...val.toolProps}
value={val.value}
onChange={(e) => handleChange(e.target.value, val.onChange)}
/>
)}
{val.tool === Tools.NUMBER_INPUT && (
<InputNumber
{...val.toolProps}
value={val.value || 0}
size="small"
onChange={(value) => handleChange(value, val.onChange)}
/>
)}
{val.tool === Tools.COLOR_PICKER && (
<ColorPicker
// defaultValue={val.value || "#fff"}
value={val.value || "#fff"}
disabledAlpha
arrow={false}
size="middle"
showText
format="hex"
placement="bottomRight"
className="tw-w-fit !tw-min-w-[110px]"
onChange={(value) => handleChange(value.toHexString(), val.onChange)}
/>
)}
{val.tool === Tools.SELECT_DROPDOWN && (
<Select
options={val.options}
showSearch
value={val.value || ""}
placeholder={`${val.label}`}
onChange={(value) => handleChange(value, val.onChange)}
/>
)}
{val.tool === Tools.CHECK_BUTTON && (
<Checkbox
checked={val.value}
defaultChecked={val.value}
onChange={(e) => handleChange(e.target.checked, val.onChange)}
>{val.label}</Checkbox>
)}
{val.tool === Tools.INPUT_RADIO_LIST && (
<DynamicRadioInputList
defaultInputs={val.value.inputs}
defaultSelected={val.value.selectedRadio}
onChange={({ inputs, selectedRadio }) => handleChange({ inputs, selectedRadio }, val.onChange)}
/>
)}
{
val.tool === Tools.LAYOUT_MANAGER && (
renderLayoutManager(val)
)
}
</>
)
}
const renderToolbar = (obj, parentKey = "", toolCount=0) => {
const keys = []
2024-09-14 16:03:26 +05:30
return Object.entries(obj).map(([key, val], i) => {
2024-09-15 12:08:29 +05:30
const keyName = parentKey ? `${parentKey}.${key}` : key
// Highlight outer labels in blue for first-level keys
const isFirstLevel = parentKey === ""
const outerLabelClass = isFirstLevel
? "tw-text-sm tw-text-black tw-font-medium"
: "tw-text-sm"
2024-09-15 12:08:29 +05:30
// Render tool widgets
2024-09-14 16:03:26 +05:30
if (typeof val === "object" && val.tool) {
if (isFirstLevel && keys.length < 3) keys.push(keyName)
if (isFirstLevel){
return (
<Collapse key={keyName} ghost defaultActiveKey={keys}>
<Collapse.Panel header={val.label} key={keyName}>
{renderTool(keyName, val)}
</Collapse.Panel>
</Collapse>
)
}
else
return (
<div key={keyName} className="tw-flex tw-flex-col tw-gap-2">
<div className={`${isFirstLevel ? outerLabelClass : "tw-text-sm"}`}>{val.label}</div>
{renderTool(keyName, val)}
</div>
)
2024-09-14 16:03:26 +05:30
}
2024-09-15 12:08:29 +05:30
// Handle nested objects and horizontal display for inner elements
2024-09-14 16:03:26 +05:30
if (typeof val === "object") {
2024-09-15 12:08:29 +05:30
const containerClass = val.display === "horizontal"
? "tw-flex tw-flex-row tw-flex-wrap tw-content-start tw-gap-4"
2024-09-15 12:08:29 +05:30
: "tw-flex tw-flex-col tw-gap-2"
if (isFirstLevel && keys.length < 3) keys.push(keyName)
if (isFirstLevel){
return (
<Collapse key={keyName} ghost defaultActiveKey={keys}>
<Collapse.Panel header={val.label} key={keyName}>
<div className={`${containerClass} tw-px-2`}>
{renderToolbar(val, keyName, toolCount+1)}
</div>
</Collapse.Panel>
</Collapse>
)
}else{
return (
<div key={keyName} className="tw-flex tw-flex-col tw-gap-2">
{/* Outer label highlighted in blue for first-level */}
<div className={outerLabelClass}>{val.label}</div>
<div className={`${containerClass} tw-px-2`}>
{renderToolbar(val, keyName, toolCount+1)}
</div>
</div>
)
}
2024-09-14 16:03:26 +05:30
}
2024-09-15 12:08:29 +05:30
return null
2024-09-14 16:03:26 +05:30
})
}
2024-09-13 16:03:58 +05:30
return (
2024-09-15 12:08:29 +05:30
<div
2024-09-16 12:23:15 +05:30
className={`tw-absolute tw-top-20 tw-right-5 tw-bg-white ${toolbarOpen ? "tw-w-[280px]" : "tw-w-0"
} tw-px-3 tw-p-2 tw-h-[600px] tw-rounded-md tw-z-[1000] tw-shadow-lg
tw-transition-transform tw-duration-75 tw-overflow-x-hidden
2024-09-15 12:08:29 +05:30
tw-flex tw-flex-col tw-gap-2 tw-overflow-y-auto`}
>
<h3 className="tw-text-lg tw-text-center">
{capitalize(`${widgetType || ""}`).replace(/_/g, " ")}
2024-09-13 16:03:58 +05:30
</h3>
<div className="tw-flex tw-flex-col tw-gap-2">
{renderToolbar(toolbarAttrs || {})}
</div>
2024-09-13 16:03:58 +05:30
</div>
)
2024-09-15 15:31:04 +05:30
})
2024-09-13 16:03:58 +05:30
export default CanvasToolBar