/* eslint-disable import/no-named-as-default */ import React, { useCallback, useState, useEffect } from 'react'; import { useDrop, useDragLayer } from 'react-dnd'; import { v4 as uuidv4 } from 'uuid'; import { ItemTypes } from './ItemTypes'; import { DraggableBox } from './DraggableBox'; import { snapToGrid as doSnapToGrid } from './snapToGrid'; import update from 'immutability-helper'; import { componentTypes } from './WidgetManager/components'; import { computeComponentName } from '@/_helpers/utils'; import produce from 'immer'; export const SubContainer = ({ mode, snapToGrid, onComponentClick, onEvent, appDefinition, appDefinitionChanged, currentState, onComponentOptionChanged, onComponentOptionsChanged, appLoading, zoomLevel, parent, parentRef, setSelectedComponent, deviceWindowWidth, selectedComponent, currentLayout, removeComponent, darkMode, containerCanvasWidth, readOnly, customResolvables, parentComponent, listViewItemOptions, onComponentHover, hoveredComponent, selectedComponents, }) => { const [_containerCanvasWidth, setContainerCanvasWidth] = useState(0); useEffect(() => { if (parentRef.current) { const canvasWidth = getContainerCanvasWidth(); setContainerCanvasWidth(canvasWidth); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [parentRef, getContainerCanvasWidth()]); zoomLevel = zoomLevel || 1; // eslint-disable-next-line react-hooks/exhaustive-deps const allComponents = appDefinition ? appDefinition.components : {}; let childComponents = []; Object.keys(allComponents).forEach((key) => { if (allComponents[key].parent === parent) { childComponents[key] = { ...allComponents[key], component: { ...allComponents[key]['component'], parent } }; } }); const [boxes, setBoxes] = useState(allComponents); const [isDragging, setIsDragging] = useState(false); const [isResizing, setIsResizing] = useState(false); useEffect(() => { setBoxes(allComponents); }, [allComponents]); const moveBox = useCallback( (id, left, top) => { setBoxes( update(boxes, { [id]: { $merge: { left, top }, }, }) ); }, [boxes] ); useEffect(() => { if (appDefinitionChanged) { appDefinitionChanged({ ...appDefinition, components: boxes }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [boxes]); const { draggingState } = useDragLayer((monitor) => { // TODO: Need to move to a performant version of the block below if (monitor.getItem()) { if (monitor.getItem().id === undefined) { if (parentRef.current) { const currentOffset = monitor.getSourceClientOffset(); if (currentOffset) { const canvasBoundingRect = parentRef.current .getElementsByClassName('real-canvas')[0] .getBoundingClientRect(); if ( currentOffset.x > canvasBoundingRect.x && currentOffset.x < canvasBoundingRect.x + canvasBoundingRect.width ) { return { draggingState: true }; } } } } } if (monitor.isDragging() && monitor.getItem().parent) { if (monitor.getItem().parent === parent) { return { draggingState: true }; } else { return { draggingState: false }; } } else { return { draggingState: false }; } }); useEffect(() => { setIsDragging(draggingState); }, [draggingState]); function convertXToPercentage(x, canvasWidth) { return (x * 100) / canvasWidth; } function convertXFromPercentage(x, canvasWidth) { return (x * canvasWidth) / 100; } const [, drop] = useDrop( () => ({ accept: ItemTypes.BOX, drop(item, monitor) { let componentData = {}; let componentMeta = {}; let id = item.id; let left = 0; let top = 0; let layouts = item['layouts']; const currentLayoutOptions = layouts ? layouts[item.currentLayout] : {}; const canvasBoundingRect = parentRef.current.getElementsByClassName('real-canvas')[0].getBoundingClientRect(); // Component already exists and this is just a reposition event if (id) { const delta = monitor.getDifferenceFromInitialOffset(); componentData = item.component; left = Math.round(convertXFromPercentage(currentLayoutOptions.left, canvasBoundingRect.width) + delta.x); top = Math.round(currentLayoutOptions.top + delta.y); if (snapToGrid) { [left, top] = doSnapToGrid(canvasBoundingRect.width, left, top); } left = convertXToPercentage(left, canvasBoundingRect.width); let newBoxes = { ...boxes, [id]: { ...boxes[id], parent: parent, layouts: { ...boxes[id]['layouts'], [item.currentLayout]: { ...boxes[id]['layouts'][item.currentLayout], top: top, left: left, }, }, }, }; setBoxes(newBoxes); } else { // This is a new component componentMeta = componentTypes.find((component) => component.component === item.component.component); componentData = JSON.parse(JSON.stringify(componentMeta)); componentData.name = computeComponentName(componentData.component, boxes); const offsetFromTopOfWindow = canvasBoundingRect.top; const offsetFromLeftOfWindow = canvasBoundingRect.left; const currentOffset = monitor.getSourceClientOffset(); const initialClientOffset = monitor.getInitialClientOffset(); const delta = monitor.getDifferenceFromInitialOffset(); left = Math.round(currentOffset.x + currentOffset.x * (1 - zoomLevel) - offsetFromLeftOfWindow); top = Math.round( initialClientOffset.y - 10 + delta.y + initialClientOffset.y * (1 - zoomLevel) - offsetFromTopOfWindow ); id = uuidv4(); } const subContainerWidth = canvasBoundingRect.width; if (snapToGrid) { [left, top] = doSnapToGrid(subContainerWidth, left, top); } if (item.currentLayout === 'mobile') { componentData.definition.others.showOnDesktop.value = false; componentData.definition.others.showOnMobile.value = true; } // convert the left offset to percentage left = (left * 100) / subContainerWidth; const width = (componentMeta.defaultSize.width * 100) / 43; setBoxes({ ...boxes, [id]: { component: componentData, parent: parentRef.current.id, layouts: { [item.currentLayout]: { top: top, left: left, width: width, height: componentMeta.defaultSize.height, }, }, }, }); return undefined; }, }), [moveBox] ); function getContainerCanvasWidth() { if (containerCanvasWidth !== undefined) { return containerCanvasWidth; } let width = 0; if (parentRef.current) { const realCanvas = parentRef.current.getElementsByClassName('real-canvas')[0]; if (realCanvas) { const canvasBoundingRect = realCanvas.getBoundingClientRect(); width = canvasBoundingRect.width; } } return width; } function onDragStop(e, componentId, direction, currentLayout) { const canvasWidth = getContainerCanvasWidth(); const nodeBounds = direction.node.getBoundingClientRect(); const canvasBounds = parentRef.current.getElementsByClassName('real-canvas')[0].getBoundingClientRect(); // Computing the left offset const leftOffset = nodeBounds.x - canvasBounds.x; const currentLeftOffset = boxes[componentId].layouts[currentLayout].left; const leftDiff = currentLeftOffset - convertXToPercentage(leftOffset, canvasWidth); const topDiff = boxes[componentId].layouts[currentLayout].top - (nodeBounds.y - canvasBounds.y); let newBoxes = { ...boxes }; if (selectedComponents) { for (const selectedComponent of selectedComponents) { newBoxes = produce(newBoxes, (draft) => { const topOffset = draft[selectedComponent.id].layouts[currentLayout].top; const leftOffset = draft[selectedComponent.id].layouts[currentLayout].left; draft[selectedComponent.id].layouts[currentLayout].top = topOffset - topDiff; draft[selectedComponent.id].layouts[currentLayout].left = leftOffset - leftDiff; }); } } setBoxes(newBoxes); } function onResizeStop(id, e, direction, ref, d, position) { const deltaWidth = d.width; const deltaHeight = d.height; let { x, y } = position; const defaultData = { top: 100, left: 0, width: 445, height: 500, }; let { left, top, width, height } = boxes[id]['layouts'][currentLayout] || defaultData; const canvasBoundingRect = parentRef.current.getElementsByClassName('real-canvas')[0].getBoundingClientRect(); const subContainerWidth = canvasBoundingRect.width; top = y; left = (x * 100) / subContainerWidth; width = width + (deltaWidth * 43) / subContainerWidth; height = height + deltaHeight; let newBoxes = { ...boxes, [id]: { ...boxes[id], layouts: { ...boxes[id]['layouts'], [currentLayout]: { ...boxes[id]['layouts'][currentLayout], width, height, top, left, }, }, }, }; setBoxes(newBoxes); } function paramUpdated(id, param, value) { if (Object.keys(value).length > 0) { setBoxes( update(boxes, { [id]: { $merge: { component: { ...boxes[id].component, definition: { ...boxes[id].component.definition, properties: { ...boxes[id].component.definition.properties, [param]: value, }, }, }, }, }, }) ); } } const styles = { width: '100%', height: '100%', position: 'absolute', backgroundSize: `${getContainerCanvasWidth() / 43}px 10px`, }; function onComponentOptionChangedForSubcontainer(component, optionName, value, extraProps) { if (parentComponent?.component === 'Listview') { let newData = currentState.components[parentComponent.name]?.data || []; newData[listViewItemOptions.index] = { ...newData[listViewItemOptions.index], [component.name]: { ...(newData[listViewItemOptions.index] ? newData[listViewItemOptions.index][component.name] : {}), [optionName]: value, }, }; return onComponentOptionChanged(parentComponent, 'data', newData); } else { return onComponentOptionChanged(component, optionName, value, extraProps); } } function customRemoveComponent(component) { const componentName = appDefinition.components[component.id]['component'].name; removeComponent(component); if (parentComponent.component === 'Listview') { const currentData = currentState.components[parentComponent.name]?.data || []; const newData = currentData.map((widget) => { delete widget[componentName]; return widget; }); onComponentOptionChanged(parentComponent, 'data', newData); } } return (
{Object.keys(childComponents).map((key) => ( setIsResizing(status)} draggingStatusChanged={(status) => setIsDragging(status)} inCanvas={true} zoomLevel={zoomLevel} setSelectedComponent={setSelectedComponent} currentLayout={currentLayout} selectedComponent={selectedComponent} deviceWindowWidth={deviceWindowWidth} isSelectedComponent={mode === 'edit' ? selectedComponents.find((component) => component.id === key) : false} removeComponent={customRemoveComponent} canvasWidth={_containerCanvasWidth} readOnly={readOnly} darkMode={darkMode} customResolvables={customResolvables} onComponentHover={onComponentHover} hoveredComponent={hoveredComponent} parentId={parentComponent?.name} isMultipleComponentsSelected={selectedComponents?.length > 1 ? true : false} containerProps={{ mode, snapToGrid, onComponentClick, onEvent, appDefinition, appDefinitionChanged, currentState, onComponentOptionChanged, onComponentOptionsChanged, appLoading, zoomLevel, setSelectedComponent, removeComponent, currentLayout, deviceWindowWidth, selectedComponents, darkMode, readOnly, onComponentHover, hoveredComponent, }} /> ))} {Object.keys(boxes).length === 0 && !appLoading && !isDragging && (
Drag components from the right sidebar and drop here. Check out our{' '} guide {' '} on adding widgets.
)} {appLoading && (
)}
); };