diff --git a/src/canvas/canvas.js b/src/canvas/canvas.js index e040ae1..1340cf9 100644 --- a/src/canvas/canvas.js +++ b/src/canvas/canvas.js @@ -135,7 +135,6 @@ class Canvas extends React.Component { } componentWillUnmount() { - console.log("unmounted") this.canvasContainerRef.current.removeEventListener("mousedown", this.mouseDownEvent) this.canvasContainerRef.current.removeEventListener("mouseup", this.mouseUpEvent) @@ -1210,6 +1209,53 @@ class Canvas extends React.Component { } + /** + * Update the widget's initial data, else when there is a remount, you'll loose all the state + * + * TODO: Find a efficient way to call this function + * + * NOTE: this would cause entire widgetList to remount + * NOTE: this would cause the toolbar to loose active widget + */ + updateWidgetData = (widgetId, latestData) => { + + const widgetObj = this.getWidgetById(widgetId)?.current + // console.log("Data unmount: ", this.widgets, this.widgetRefs, widgetObj, widgetId, widgetObj?.serialize(), latestData) + + if (!widgetObj){ + return + } + + + this.setWidgets(prevWidgets => { + const updateWidget = (widgets) => { + return widgets.map(widget => { + if (widget.id === widgetId) { + return { + ...widget, + initialData: { + ...widget.initialData, + ...widgetObj.serialize() + } + }; + } + + if (widget.children && widget.children.length > 0) { + return { + ...widget, + children: updateWidget(widget.children) // Always return new children + }; + } + + return widget; // Return unchanged widget + }); + }; + + return updateWidget(prevWidgets); + }) + + } + renderWidget = (widget) => { @@ -1252,19 +1298,25 @@ class Canvas extends React.Component { pan: this.state.currentTranslate }} + onSelect={handleWidgetSelect} onWidgetDeleteRequest={this.removeWidget} onPanToWidget={this.panToWidget} + + requestWidgetDataUpdate={this.updateWidgetData} + // onWidgetUpdate={this.onActiveWidgetUpdate} + // onWidgetUpdate={this.updateWidgetDataOnUnmount} + // onUnmount={this.updateWidgetDataOnUnmount} - onWidgetUpdate={this.onActiveWidgetUpdate} onAddChildWidget={this.handleAddWidgetChild} onCreateWidgetRequest={this.createWidget} // create widget when dropped from sidebar onWidgetResizing={(resizeSide) => this.setState({ widgetResizing: resizeSide })} // onWidgetDragStart={() => this.setState({isWidgetDragging: true})} // onWidgetDragEnd={() => this.setState({isWidgetDragging: false})} onLayoutUpdate={this.updateChildLayouts} + > {/* Render children inside the parent with layout applied */} {renderChildren(children)} diff --git a/src/canvas/toolbar.js b/src/canvas/toolbar.js index ba8c618..7f89577 100644 --- a/src/canvas/toolbar.js +++ b/src/canvas/toolbar.js @@ -15,7 +15,7 @@ import { AudioOutlined, FileImageOutlined, FileTextOutlined, VideoCameraOutlined import { useWidgetContext } from "./context/widgetContext.js" -// FIXME: Maximum recursion error +// FIXME: input cursors problem // FIXME: Every time the parent attrs are changed a remount happens, which causes input cursor to go to the end /** * @@ -340,10 +340,12 @@ const CanvasToolBar = memo(({ isOpen, widgetType, }) => { const renderToolbar = (obj, parentKey = "", toolCount=0) => { + // console.log("obj: ", obj) const keys = [] return Object.entries(obj).map(([key, val], i) => { const keyName = parentKey ? `${parentKey}.${key}` : key + // console.log("obj2: ", key, val) // Highlight outer labels in blue for first-level keys const isFirstLevel = parentKey === "" @@ -352,6 +354,10 @@ const CanvasToolBar = memo(({ isOpen, widgetType, }) => { ? "tw-text-sm tw-text-black tw-font-medium" : "tw-text-sm" + if (!val?.label){ + return null + } + // Render tool widgets if (typeof val === "object" && val.tool) { diff --git a/src/canvas/widgets/base.js b/src/canvas/widgets/base.js index 31dcde8..e28d991 100644 --- a/src/canvas/widgets/base.js +++ b/src/canvas/widgets/base.js @@ -1,3 +1,5 @@ +import { v4 as uuidv4 } from 'uuid'; + import React from "react" import { NotImplementedError } from "../../utils/errors" @@ -25,7 +27,7 @@ import { Layout, message } from "antd" const ATTRS_KEYS = ['value', 'label', 'tool', 'onChange', 'options', 'toolProps'] // these are attrs keywords, don't use these keywords as keys while defining the attrs property or serializing - +// FIXME: the initial data in canvas should be updated so when it remounts the widget doesn't change state /** * Base class to be extended */ @@ -78,6 +80,8 @@ class Widget extends React.Component { selected: false, isWidgetVisible: true, + forceRerenderId: "", + widgetName: widgetName || 'widget', // this will later be converted to variable name enableRename: false, // will open the widgets editable div for renaming @@ -202,13 +206,13 @@ class Widget extends React.Component { this.getRenderSize = this.getRenderSize.bind(this) this.getInnerRenderStyling = this.getInnerRenderStyling.bind(this) + this.updateState = this.updateState.bind(this) + this.stateUpdateCallback = null // allowing other components such as toolbar to subscribe to changes in this widget } componentDidMount() { - - console.log("state layout: ") this.setLayout({layout: Layouts.FLEX, gap: 10}) // if (this.state.attrs.layout){ @@ -226,8 +230,16 @@ class Widget extends React.Component { if (prevProps !== this.props) { this.canvasMetaData = this.props.canvasMetaData } + } + // componentWillUnmount(){ + // // TODO: serialize and store the widget data in setWidgets under widget context especially initialData + // console.log("unmounting widget: ", this.state.attrs, this.serialize()) + + // // this.props.onUnmount(this.__id, this.serialize()) + // } + stateChangeSubscriberCallback = (callback) => { // NOTE: don't subscribe to multiple callbacks, only the last one will work @@ -241,25 +253,25 @@ class Widget extends React.Component { * @param {} newState - this can either be a callback or a new State like (prevState) => ({key: value}) * @param {*} callback - callback to run after setState */ - updateState = (newState, callback) => { - + updateState(newState, callback){ + // console.trace("Callback trace"); + // debugger; + // FIXME: maximum recursion error when updating size, color etc this.setState(newState, () => { - console.log("updatinhg./..: ", this.state) + // console.log("updatinhg./..: ", this.state, newState) + const { onWidgetUpdate } = this.props if (this.stateUpdateCallback) this.stateUpdateCallback() + + // FIXME: super inefficient // if (onWidgetUpdate) { // onWidgetUpdate(this.__id) // } - // const { activeWidgetId, updateToolAttrs } = this.context - - // if (activeWidgetId === this.__id) - // updateToolAttrs(this.getToolbarAttrs()) - if (callback) callback() }) @@ -326,10 +338,13 @@ class Widget extends React.Component { ...this.state.attrs, }) + } forceRerender = () => { - this.forceUpdate() + // this.forceUpdate() // Don't use forceUpdate widgets will loose their states + this.setState({forceRerenderId: `${uuidv4()}`}) + console.log("rerender") } // TODO: add context menu items such as delete, add etc @@ -501,7 +516,6 @@ class Widget extends React.Component { setAttrValue(path, value, callback) { this.updateState((prevState) => { // since the setState is Async only the prevState contains the latest state - const keys = path.split('.') const lastKey = keys.pop() @@ -806,6 +820,7 @@ class Widget extends React.Component { // NOTE: when serializing make sure, you are only passing serializable objects not functions or other return ({ zIndex: this.state.zIndex, + selected: this.state.selected, widgetName: this.state.widgetName, pos: this.state.pos, size: this.state.size, @@ -814,7 +829,7 @@ class Widget extends React.Component { widgetOuterStyling: this.state.widgetOuterStyling, parentLayout: this.state.parentLayout, positionType: this.state.positionType, - attrs: this.serializeAttrsValues() // makes sure that functions are not serialized + attrs: this.serializeAttrsValues(), // makes sure that functions are not serialized }) } @@ -830,9 +845,9 @@ class Widget extends React.Component { data = {...data} // create a shallow copy - const {attrs={}, pos={x: 0, y: 0}, parentLayout=null, ...restData} = data - + const {attrs={}, selected, pos={x: 0, y: 0}, parentLayout=null, ...restData} = data + let layoutUpdates = { parentLayout: parentLayout.layout || null } @@ -887,6 +902,10 @@ class Widget extends React.Component { this.updateState({ attrs: newAttrs }, callback) + if (selected){ + this.select() + console.log("selected again") + } }) } @@ -1090,12 +1109,14 @@ class Widget extends React.Component { // console.log("Dropped on Sidebar: ", this.__id) - const parentRect = this.getBoundingRect() + // const parentRect = this.getBoundingRect() + const canvasRect = this.props.canvasInnerContainerRef.current.getBoundingClientRect() + const {zoom, pan} = this.props.canvasMetaData let initialPos = { - x: (e.clientX - parentRect.left) / zoom, - y: (e.clientY - parentRect.top) / zoom, + x: (e.clientX - canvasRect.left) / zoom, + y: (e.clientY - canvasRect.top) / zoom, } this.props.onCreateWidgetRequest(widgetClass, {x: initialPos.x, y: initialPos.y},({ id, widgetRef }) => { diff --git a/src/components/header.js b/src/components/header.js index 4a23105..6b8ffb5 100644 --- a/src/components/header.js +++ b/src/components/header.js @@ -52,7 +52,7 @@ function Header({projectName, onProjectNameChange, framework, onFrameworkChange, tw-border-gray-400 tw-rounded-md tw-no-underline tw-border-solid hover:tw-bg-[#9333EA] hover:tw-text-white tw-duration-200 tw-text-black tw-text-center tw-px-4 tw-text-sm tw-cursor-pointer"> - Join Waitlist + Get Updates { - - return { + // NOTE: tis returns (creates) a new object everytime causing unncessary renders + return ({ side: this.state.flexSide, - } + }) + } + + getPackSide(){ + return this.state.flexSide } setParentLayout(layout){ @@ -215,16 +230,23 @@ export class TkinterBase extends Widget { label: "Align Side", tool: Tools.SELECT_DROPDOWN, options: ["left", "right", "top", "bottom", ""].map(val => ({value: val, label: val})), - value: "left", + value: this.state.flexSide, onChange: (value) => { - + console.log("call 0: ", value) // FIXME: force parent rerender because, here only child get rerendered, if only parent get rerendered the widget would move - this.setAttrValue("flexManager.side", value) - - this.setState({flexSide: value}, () => { - console.log("updated side: ", this.state.attrs) - setTimeout(this.props.parentWidgetRef.current.forceRerender, 1) + this.setAttrValue("flexManager.side", value, () => { + this.updateState({flexSide: value}, () => { + console.log("call") + this.props.requestWidgetDataUpdate(this.__id) + this.stateChangeSubscriberCallback() + // console.log("force rendering: ", this.state.flexSide) + // this.props.parentWidgetRef.current.forceRerender() + // setTimeout(, 1) + }) }) + + + // this.props.parentWidgetRef.current.forceRerender() // console.log("updateing state: ", value, this.props.parentWidgetRef.current) } @@ -345,29 +367,38 @@ export class TkinterBase extends Widget { // console.log("updated atters: ", this.state) // }) - this.updateState((prevState) => ({...prevState, ...updates})) + console.log('setting paret layiout') + this.updateState((prevState) => ({...prevState, ...updates}), () => { + console.log("updated layout state: ", this.state.attrs) + }) return updates } - renderTkinterLayout = () => { + renderTkinterLayout(){ const {layout, direction, gap} = this.getLayout() console.log("rendering: ", layout, this.state) if (layout === Layouts.FLEX){ return ( <> - {["top", "bottom", "left", "right", "center"].map((pos) => ( -
- {this.props.children.filter(item => { - const packAttrs = item.ref?.current?.getPackAttrs() - console.log("side: ", packAttrs, item, item.ref?.current?.getPackAttrs()) - // const widgetSide = item.ref.current?.getAttrValue("flexManager.side") || "left" - // console.log("widget side: ", item.ref.current?.__id, item.ref.current, item.ref.current.getAttrValue("flexManager.side")) - return ((packAttrs?.side ) === pos) - })} -
- ))} + {(this.props.children.length > 0) && ["top", "bottom", "left", "right", "center"].map((pos) => { + + const filteredChildren = this.props.children.filter((item) => { + const widgetRef = item.ref?.current + if (!widgetRef) return false // Ensure ref exists before accessing + + const packAttrs = widgetRef.getPackSide()// Cache value + console.log("pack attrs: ", widgetRef.getPackSide()) + return packAttrs === pos + }) + console.log("filtered children:", filteredChildren, pos) + return ( +
+ {filteredChildren} +
+ ) + })} ) } @@ -407,10 +438,6 @@ export class TkinterBase extends Widget { setLayout(value) { const { layout, direction, grid = { rows: 1, cols: 1 }, gap = 10, align } = value - console.log("setting layout: ", layout) - - // FIXME the display grid is still flex - // FIXME: this should be grid only when the widget accepts children else flex or something tp center // console.log("layout value: ", value) // FIXME: In grid layout the layout doesn't adapt to the size of the child if resized @@ -505,8 +532,10 @@ export class TkinterBase extends Widget { serialize(){ + console.log("serialzied item: ", this.state.attrs, super.serialize(), this.__id, this.serializeAttrsValues()) return ({ ...super.serialize(), + attrs: this.serializeAttrsValues(), // makes sure that functions are not serialized flexSide: this.state.flexSide }) } @@ -523,7 +552,10 @@ export class TkinterBase extends Widget { data = {...data} // create a shallow copy - const {attrs={}, pos={x: 0, y: 0}, parentLayout=null, ...restData} = data + console.log("data reloaded: ", data) + + + const {attrs={}, selected, pos={x: 0, y: 0}, parentLayout=null, ...restData} = data let layoutUpdates = { parentLayout: parentLayout @@ -551,14 +583,12 @@ export class TkinterBase extends Widget { pos } - console.log("data: ", newData) this.setState(newData, () => { let layoutAttrs = this.setParentLayout(parentLayout).attrs || {} // UPdates attrs let newAttrs = { ...this.state.attrs, ...layoutAttrs } - console.log("new attrs: ", newAttrs, attrs) // Iterate over each path in the updates object Object.entries(attrs).forEach(([path, value]) => { const keys = path.split('.') @@ -582,9 +612,12 @@ export class TkinterBase extends Widget { // TODO: find a better way to apply innerStyles this.setWidgetInnerStyle("backgroundColor", newAttrs.styling.backgroundColor.value) } - console.log("new arttrs: ", newData) this.updateState({ attrs: newAttrs }, callback) + if (selected){ + this.select() + console.log("selected again") + } }) diff --git a/src/sidebar/utils/premium.js b/src/sidebar/utils/premium.js index 44989e3..4559665 100644 --- a/src/sidebar/utils/premium.js +++ b/src/sidebar/utils/premium.js @@ -51,10 +51,7 @@ function Premium({ children, className = "" }) { > more. -
- Or you could support development by sharing this tool on your socials and you'll be eligible for a -  free license. -
+
@@ -300,6 +297,11 @@ function Premium({ children, className = "" }) { + +
+ Or you could support development by sharing this tool on your socials and you'll be eligible for a +  free license. +
diff --git a/src/utils/random.js b/src/utils/random.js new file mode 100644 index 0000000..a7c2c45 --- /dev/null +++ b/src/utils/random.js @@ -0,0 +1,5 @@ + + +export function randomArrayChoice(arr){ + return arr[Math.floor(Math.random() * arr.length)]; +} \ No newline at end of file