moved resize handler from widget to canvas, canvas handles resizing

This commit is contained in:
paul
2024-09-18 11:39:07 +05:30
parent 2b1e3a6ee9
commit c3759e0457
3 changed files with 198 additions and 114 deletions

View File

@@ -61,14 +61,15 @@ class Canvas extends React.Component {
this.widgetRefs = {} // stores the actual refs to the widgets inside the canvas {id: ref, id2, ref2...}
this.state = {
widgets: [], // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass}]
widgetResizing: "", // set this to "nw", "sw" etc based on the side when widgets resizing handles are selected
widgets: [], // stores the mapping to widgetRefs, stores id and WidgetType, later used for rendering [{id: , widgetType: WidgetClass, children: [], parent: ""}]
zoom: 1,
isPanning: false,
currentTranslate: { x: 0, y: 0 },
canvasSize: { width: 500, height: 500 },
contextMenuItems: [],
selectedWidgets: [],
selectedWidget: null,
toolbarOpen: true,
toolbarAttrs: null
@@ -209,20 +210,17 @@ class Canvas extends React.Component {
if (selectedWidget){
// if the widget is selected don't pan, instead move the widget
if (!selectedWidget._disableSelection){
const selectedLength = this.state.selectedWidgets.length
// console.log("selected widget: ", selectedWidget)
if (selectedLength === 0 || (selectedLength === 1 && selectedWidget.__id !== this.state.selectedWidgets[0].__id)){
this.state.selectedWidgets[0]?.deSelect() // deselect the previous widget before adding the new one
this.state.selectedWidgets[0]?.setZIndex(0)
if (!this.state.selectedWidget || (selectedWidget.__id !== this.state.selectedWidget?.__id)){
this.state.selectedWidget?.deSelect() // deselect the previous widget before adding the new one
this.state.selectedWidget?.setZIndex(0)
selectedWidget.setZIndex(1000)
selectedWidget.select()
this.setState({
selectedWidgets: [selectedWidget],
selectedWidget: selectedWidget,
toolbarAttrs: selectedWidget.getToolbarAttrs()
})
@@ -240,7 +238,7 @@ class Canvas extends React.Component {
this.clearSelections()
this.currentMode = CanvasModes.PAN
this.setCursor(Cursor.GRAB)
console.log("clear selection")
}
this.setState({
@@ -253,14 +251,14 @@ class Canvas extends React.Component {
}else if (event.button === 2){
//right click
if (this.state.selectedWidgets.length > 0 && this.state.selectedWidgets[0].__id !== selectedWidget.__id){
if (this.state.selectedWidget && this.state.selectedWidget.__id !== selectedWidget.__id){
this.clearSelections()
}
if (selectedWidget){
this.setState({
selectedWidget: [selectedWidget],
selectedWidget: selectedWidget,
contextMenuItems: [
{
key: "rename",
@@ -284,12 +282,18 @@ class Canvas extends React.Component {
mouseMoveEvent(event){
if (this.state.widgetResizing !== ""){
// if resizing is taking place don't do anything else
this.handleResize(event)
return
}
// console.log("mode: ", this.currentMode, this.getActiveObjects())
if (this.mousePressed && [CanvasModes.PAN, CanvasModes.MOVE_WIDGET].includes(this.currentMode)) {
const deltaX = event.clientX - this.mousePos.x
const deltaY = event.clientY - this.mousePos.y
if (this.state.selectedWidgets.length === 0){
if (!this.state.selectedWidget){
// if there aren't any selected widgets, then pan the canvas
this.setState(prevState => ({
currentTranslate: {
@@ -320,6 +324,10 @@ class Canvas extends React.Component {
this.mousePressed = false
this.currentMode = CanvasModes.DEFAULT
this.setCursor(Cursor.DEFAULT)
if (this.state.widgetResizing) {
this.setState({ widgetResizing: "" })
}
}
wheelZoom(event){
@@ -329,6 +337,59 @@ class Canvas extends React.Component {
this.setZoom(zoom, {x: event.offsetX, y: event.offsetY})
}
/**
* handles widgets resizing
* @param {MouseEvent} event - mouse move event
* @returns
*/
handleResize = (event) => {
if (this.state.resizing === "") return
const widget = this.state.selectedWidget
if (!widget) return
const resizeCorner = this.state.widgetResizing
const size = widget.getSize()
const pos = widget.getPos()
const deltaX = event.movementX
const deltaY = event.movementY
let newSize = { ...size }
let newPos = { ...pos }
const { width: minWidth, height: minHeight } = widget.minSize
const { width: maxWidth, height: maxHeight } = widget.maxSize
// console.log("resizing: ", deltaX, deltaY, event)
switch (resizeCorner) {
case "nw":
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width - deltaX))
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height - deltaY))
newPos.x += (newSize.width !== size.width) ? deltaX : 0
newPos.y += (newSize.height !== size.height) ? deltaY : 0
break
case "ne":
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width + deltaX))
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height - deltaY))
newPos.y += (newSize.height !== size.height) ? deltaY : 0
break
case "sw":
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width - deltaX))
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height + deltaY))
newPos.x += (newSize.width !== size.width) ? deltaX : 0
break
case "se":
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width + deltaX))
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height + deltaY))
break
default:
break
}
widget.setResize(newPos, newSize)
}
getCanvasContainerBoundingRect(){
return this.canvasContainerRef.current.getBoundingClientRect()
}
@@ -411,6 +472,10 @@ class Canvas extends React.Component {
}
clearSelections(){
if (!this.state.selectedWidget)
return
this.getActiveObjects().forEach(widget => {
widget.current?.deSelect()
})
@@ -419,7 +484,7 @@ class Canvas extends React.Component {
// this.context.updateToolAttrs({})
this.setState({
selectedWidgets: [],
selectedWidget: null,
toolbarAttrs: null,
// toolbarOpen:
})
@@ -462,7 +527,8 @@ class Canvas extends React.Component {
// Store the ref in the instance variable
this.widgetRefs[id] = widgetRef
const widgets = [...this.state.widgets, { id, widgetType: widgetComponentType }] // don't add the widget refs in the state
const widgets = [...this.state.widgets, { id, widgetType: widgetComponentType, children: [], parent: "" }] // don't add the widget refs in the state
// Update the state to include the new widget's type and ID
this.setState({
widgets: widgets
@@ -491,7 +557,7 @@ class Canvas extends React.Component {
deleteSelectedWidgets(widgets=[]){
let activeWidgets = removeDuplicateObjects([...widgets, ...this.state.selectedWidgets], "__id")
let activeWidgets = removeDuplicateObjects([...widgets, this.state.selectedWidget], "__id")
const widgetIds = activeWidgets.map(widget => widget.__id)
@@ -520,11 +586,6 @@ class Canvas extends React.Component {
clearCanvas(){
// NOTE: Don't remove from it using remove() function since, it already removed from the DOM tree when its removed from widgets
// for (let [key, value] of Object.entries(this.widgetRefs)){
// console.log("removed: ", value, value.current?.getElement())
// value.current?.remove()
// }
this.widgetRefs = {}
this.setState({
@@ -552,15 +613,15 @@ class Canvas extends React.Component {
onActiveWidgetUpdate(widgetId){
if (this.state.selectedWidgets.length === 0 || widgetId !== this.state.selectedWidgets[0].__id)
if (!this.state.selectedWidget || widgetId !== this.state.selectedWidget.__id)
return
console.log("updating...", this.state.toolbarAttrs, this.state.selectedWidgets.at(0).getToolbarAttrs())
// console.log("updating...", this.state.toolbarAttrs, this.state.selectedWidget.getToolbarAttrs())
// console.log("attrs: ", this.state.selectedWidgets.at(0).getToolbarAttrs())
this.setState({
toolbarAttrs: this.state.selectedWidgets.at(0).getToolbarAttrs()
toolbarAttrs: this.state.selectedWidget.getToolbarAttrs()
})
}
@@ -601,12 +662,15 @@ class Canvas extends React.Component {
}
renderWidget(widget){
const { id, widgetType: ComponentType } = widget
const { id, widgetType: ComponentType, children=[], parent } = widget
// console.log("widet: ", this.widgetRefs, id)
// TODO: need to pass the widget ref for child elements as well
return <ComponentType key={id} id={id} ref={this.widgetRefs[id]}
canvasRef={this.canvasContainerRef}
onWidgetUpdate={this.onActiveWidgetUpdate}
childWidgets={children}
parent={parent}
onWidgetResizing={(resizeSide) => this.setState({widgetResizing: resizeSide })}
/>
}
@@ -631,7 +695,7 @@ class Canvas extends React.Component {
onDrop={this.handleDropEvent}>
{/* <DragWidgetProvider> */}
<Dropdown trigger={['contextMenu']} mouseLeaveDelay={0} menu={{items: this.state.contextMenuItems, }}>
<div className="dots-bg tw-w-full tw-h-full tw-outline-none tw-flex tw-relative tw-bg-[#f2f2f2] tw-overflow-hidden"
<div className="tw-w-full tw-h-full tw-outline-none tw-flex tw-relative tw-bg-[#f2f2f2] tw-overflow-hidden"
ref={this.canvasContainerRef}
style={{
transition: " transform 0.3s ease-in-out",
@@ -664,7 +728,7 @@ class Canvas extends React.Component {
</DroppableWrapper>
<CanvasToolBar isOpen={this.state.toolbarOpen}
widgetType={this.state.selectedWidgets?.at(0)?.getWidgetType() || ""}
widgetType={this.state.selectedWidget?.getWidgetType() || ""}
attrs={this.state.toolbarAttrs}
/>
{/* </ActiveWidgetProvider> */}

View File

@@ -142,24 +142,24 @@ class Widget extends React.Component {
this.setWidgetStyling = this.setWidgetStyling.bind(this)
this.startResizing = this.startResizing.bind(this)
this.handleResize = this.handleResize.bind(this)
this.stopResizing = this.stopResizing.bind(this)
// this.startResizing = this.startResizing.bind(this)
// this.handleResize = this.handleResize.bind(this)
// this.stopResizing = this.stopResizing.bind(this)
}
componentDidMount() {
this.elementRef.current?.addEventListener("click", this.mousePress)
this.canvas.addEventListener("mousemove", this.handleResize)
this.canvas.addEventListener("mouseup", this.stopResizing)
// this.canvas.addEventListener("mousemove", this.handleResize)
// this.canvas.addEventListener("mouseup", this.stopResizing)
}
componentWillUnmount() {
this.elementRef.current?.removeEventListener("click", this.mousePress)
this.canvas.addEventListener("mousemove", this.handleResize)
this.canvas.addEventListener("mouseup", this.stopResizing)
// this.canvas.addEventListener("mousemove", this.handleResize)
// this.canvas.addEventListener("mouseup", this.stopResizing)
}
componentDidUpdate(prevProps, prevState) {
@@ -287,10 +287,10 @@ class Widget extends React.Component {
setPos(x, y) {
if (this.state.resizing) {
// don't change position when resizing the widget
return
}
// if (this.state.resizing) {
// // don't change position when resizing the widget
// return
// }
this.setState({
pos: { x, y }
@@ -301,19 +301,6 @@ class Widget extends React.Component {
// })
}
setParent(parentId) {
this._parent = parentId
}
addChild(childId) {
this._children.push(childId)
}
removeChild(childId) {
this._children = this._children.filter(function (item) {
return item !== childId
})
}
getPos() {
return this.state.pos
@@ -368,11 +355,6 @@ class Widget extends React.Component {
})
}
startResizing(corner, event) {
event.stopPropagation()
this.setState({ resizing: true, resizeCorner: corner })
}
setZIndex(zIndex) {
this.setState({
zIndex: zIndex
@@ -381,10 +363,6 @@ class Widget extends React.Component {
setWidgetName(name) {
// this.setState((prev) => ({
// widgetName: name.length > 0 ? name : prev.widgetName
// }))
this.updateState({
widgetName: name.length > 0 ? name : this.state.widgetName
})
@@ -394,7 +372,6 @@ class Widget extends React.Component {
*
* @param {string} key - The string in react Style format
* @param {string} value - Value of the style
* @param {function():void} [callback] - optional callback, thats called after setting the internal state
*/
setWidgetStyling(key, value) {
@@ -425,57 +402,70 @@ class Widget extends React.Component {
})
}
handleResize(event) {
if (!this.state.resizing) return
const { resizeCorner, size, pos } = this.state
const deltaX = event.movementX
const deltaY = event.movementY
let newSize = { ...size }
let newPos = { ...pos }
const { width: minWidth, height: minHeight } = this.minSize
const { width: maxWidth, height: maxHeight } = this.maxSize
// console.log("resizing: ", deltaX, deltaY, event)
switch (resizeCorner) {
case "nw":
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width - deltaX))
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height - deltaY))
newPos.x += (newSize.width !== size.width) ? deltaX : 0
newPos.y += (newSize.height !== size.height) ? deltaY : 0
break
case "ne":
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width + deltaX))
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height - deltaY))
newPos.y += (newSize.height !== size.height) ? deltaY : 0
break
case "sw":
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width - deltaX))
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height + deltaY))
newPos.x += (newSize.width !== size.width) ? deltaX : 0
break
case "se":
newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width + deltaX))
newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height + deltaY))
break
default:
break
}
// this.setState({ size: newSize, pos: newPos })
setResize(pos, size){
// useful when resizing the widget relative to the canvas, sets all pos, and size
this.updateState({
size: newSize,
pos: newPos
size: size,
pos: pos
})
}
stopResizing() {
if (this.state.resizing) {
this.setState({ resizing: false })
}
}
// startResizing(corner, event) {
// event.stopPropagation()
// this.setState({ resizing: true, resizeCorner: corner })
// }
// handleResize(event) {
// if (!this.state.resizing) return
// const { resizeCorner, size, pos } = this.state
// const deltaX = event.movementX
// const deltaY = event.movementY
// let newSize = { ...size }
// let newPos = { ...pos }
// const { width: minWidth, height: minHeight } = this.minSize
// const { width: maxWidth, height: maxHeight } = this.maxSize
// // console.log("resizing: ", deltaX, deltaY, event)
// switch (resizeCorner) {
// case "nw":
// newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width - deltaX))
// newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height - deltaY))
// newPos.x += (newSize.width !== size.width) ? deltaX : 0
// newPos.y += (newSize.height !== size.height) ? deltaY : 0
// break
// case "ne":
// newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width + deltaX))
// newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height - deltaY))
// newPos.y += (newSize.height !== size.height) ? deltaY : 0
// break
// case "sw":
// newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width - deltaX))
// newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height + deltaY))
// newPos.x += (newSize.width !== size.width) ? deltaX : 0
// break
// case "se":
// newSize.width = Math.max(minWidth, Math.min(maxWidth, newSize.width + deltaX))
// newSize.height = Math.max(minHeight, Math.min(maxHeight, newSize.height + deltaY))
// break
// default:
// break
// }
// // this.setState({ size: newSize, pos: newPos })
// this.updateState({
// size: newSize,
// pos: newPos
// })
// }
// stopResizing() {
// if (this.state.resizing) {
// this.setState({ resizing: false })
// }
// }
openRenaming() {
this.setState({
@@ -490,6 +480,22 @@ class Widget extends React.Component {
})
}
setParent(parentId) {
this._parent = parentId
}
addChild(childWidget) {
childWidget.setParent(this.__id)
this._children.push(childWidget)
}
removeChild(childId) {
this._children = this._children.filter(function (item) {
return item !== childId
})
}
handleDrop = (event, dragElement) => {
console.log("dragging event: ", event, dragElement)
@@ -509,7 +515,7 @@ class Widget extends React.Component {
// throw new NotImplementedError("render method has to be implemented")
return (
<div className="tw-w-full tw-h-full tw-rounded-md tw-bg-red-500" style={this.state.widgetStyling}>
{/* {this.props.children} */}
</div>
)
}
@@ -598,7 +604,8 @@ class Widget extends React.Component {
className="tw-w-2 tw-h-2 tw-absolute tw--left-1 tw--top-1 tw-bg-blue-500"
style={{ cursor: Cursor.NW_RESIZE }}
onMouseDown={(e) => {
this.startResizing("nw", e)
// this.startResizing("nw", e)
this.props.onWidgetResizing("nw")
this.setState({dragEnabled: false})
}}
onMouseLeave={() => this.setState({dragEnabled: true})}
@@ -607,7 +614,8 @@ class Widget extends React.Component {
className="tw-w-2 tw-h-2 tw-absolute tw--right-1 tw--top-1 tw-bg-blue-500"
style={{ cursor: Cursor.SW_RESIZE }}
onMouseDown={(e) => {
this.startResizing("ne", e)
// this.startResizing("ne", e)
this.props.onWidgetResizing("ne")
this.setState({dragEnabled: false})
}}
onMouseLeave={() => this.setState({dragEnabled: true})}
@@ -616,7 +624,8 @@ class Widget extends React.Component {
className="tw-w-2 tw-h-2 tw-absolute tw--left-1 tw--bottom-1 tw-bg-blue-500"
style={{ cursor: Cursor.SW_RESIZE }}
onMouseDown={(e) => {
this.startResizing("sw", e)
// this.startResizing("sw", e)
this.props.onWidgetResizing("sw")
this.setState({dragEnabled: false})
}}
onMouseLeave={() => this.setState({dragEnabled: true})}
@@ -625,7 +634,8 @@ class Widget extends React.Component {
className="tw-w-2 tw-h-2 tw-absolute tw--right-1 tw--bottom-1 tw-bg-blue-500"
style={{ cursor: Cursor.SE_RESIZE }}
onMouseDown={(e) => {
this.startResizing("se", e)
// this.startResizing("se", e)
this.props.onWidgetResizing("se")
this.setState({dragEnabled: false})
}}
onMouseLeave={() => this.setState({dragEnabled: true})}

View File

@@ -17,6 +17,16 @@ body {
background-size: cover;
}
.stripes-bg {
width: 100%;
height: 100%;
--color: #E1E1E1;
background-color: #F3F3F3;
background-image: linear-gradient(0deg, transparent 24%, var(--color) 25%, var(--color) 26%, transparent 27%,transparent 74%, var(--color) 75%, var(--color) 76%, transparent 77%,transparent),
linear-gradient(90deg, transparent 24%, var(--color) 25%, var(--color) 26%, transparent 27%,transparent 74%, var(--color) 75%, var(--color) 76%, transparent 77%,transparent);
background-size: 55px 55px;
}
.input{
border: 2px solid #e3e5e8;
padding: 2px 8px;