//src/constants/BlocksMap.js

const BlocksMap = [
	{ id: 'space', width: 1, height: 1, content: 'Blank Space', type: 'blank' },
	{ id: 'contact', width: 2, height: 3, content: 'Contact Us', type: 'contact' },
	{ id: 'header', width: 6, height: 2, content: 'Header', type: 'header' },
	{ id: 'logo', width: 1, height: 1, content: 'Logo', type: 'logo' },
	{ id: 'photo', width: 2, height: 2, content: 'Photo', type: 'photo' },
	{ id: 'text', width: 1, height: 2, content: 'Text', type: 'text' },
];

export default BlocksMap;


// src/components/main/LayoutEditor.js
import React, { useState } from 'react';
import BlocksMap from '../../constants/BlocksMap'
import BlocksGrid from './blocks/BlocksGrid';
// Load Block Modals for Blocks here. Blocks for the Grid are loaded in BlockGrid.js
import ModalPhoto from './modals/ModalPhoto';
import ModalText from './modals/ModalText';

const LayoutEditor = () => {

	const rowHeight = 120;
	const gridCols = 6;
	const gridRows = 10;
	
	const [layout, setLayout] = useState([]);
	const [blocks, setBlocks] = useState([]);// Current blocks on the grid
	const [blockContent, setBlockContent] = useState([]);// Content of current block
	const [editingBlock, setEditingBlock] = useState(null);// Currently edited block
	const [newBlockCount, setNewBlockCount] = useState(0);// Counter for new blocks added
	const [isModalOpen, setModalOpen] = useState(false);
	const [currentModalType, setCurrentModalType] = useState('');
	
	const calculateMaxBlocks = (blockList, newBlockWidth, newBlockHeight) => {
	
		const occupiedGrid = Array.from({ length: gridRows }, () => Array(gridCols).fill(false));
		blockList.forEach(block => {
			
			for (let dy = 0; dy < block.h; dy++) {
				for (let dx = 0; dx < block.w; dx++) {
					
					const gridY = block.y + dy;
					const gridX = block.x + dx;
					if (gridY < gridRows && gridX < gridCols) {
						
						occupiedGrid[gridY][gridX] = true;
					}
				}
			}
		});
		
		let maxPossible = 0;
		for (let y = 0; y <= gridRows - newBlockHeight; y++) {
			for (let x = 0; x <= gridCols - newBlockWidth; x++) {
				
				let canPlace = true;
				for (let dy = 0; dy < newBlockHeight; dy++) {
					for (let dx = 0; dx < newBlockWidth; dx++) {
						
						if (occupiedGrid[y + dy][x + dx]) {
							
							canPlace = false;
							break;
						}
					}
					if (!canPlace) {
						
						break;
					}
				}
				if (canPlace) {
					
					maxPossible++;
				}
			}
		}
		
		return maxPossible;
	};
	
	/**
	* Add Block to Grid
	* Function to add a new block to the grid
	*/
	const handleBlockAdd = (width, height, content, id) => {
	
		const MAX_BLOCKS = calculateMaxBlocks(blocks, width, height);
		if (blocks.length >= MAX_BLOCKS) {
			
			alert("Maximum number of blocks reached!");
			return;
		}
		
		const newId = `block-${id}-${newBlockCount}`;
		let availablePosition = null;
		
		if (blocks.length < MAX_BLOCKS) {
		
			for (let y = 0; y <= gridRows - height; y++) {
				for (let x = 0; x <= gridCols - width; x++) {
					if (x + width <= gridCols && y + height <= gridRows) {
						
						let canPlace = true;
						for (let dy = 0; dy < height; dy++) {
							for (let dx = 0; dx < width; dx++) {
								
								const checkX = x + dx;
								const checkY = y + dy;
								if (checkX < gridCols && checkY < gridRows) {
									
									if (blocks.some(block => 
										(block.x === checkX && block.y === checkY) || 
										(block.x < (checkX + width) && block.x + block.w > x && 
										block.y < (checkY + height) && block.y + block.h > y)))
									{
										canPlace = false;
										break;
									}
								}
							}
							if (!canPlace) {
								
								break;
							}
						}
						if (canPlace) {
							
							availablePosition = { x, y, w: width, h: height };
							break;
						}
					}
				}
				if (availablePosition) {
					
					break;
				}
			}
		}
		if (availablePosition) {
			
			setBlocks(prevBlocks => [
				...prevBlocks,
				{ i: newId, x: availablePosition.x, y: availablePosition.y, w: width, h: height, content }
			]);
			
			setNewBlockCount(prevCount => prevCount + 1);
			setLayout(prevLayout => [...prevLayout, { i: newId, ...availablePosition }]);
		}
		else {
			alert("No available space to add a new block.");
		}
	};
	
	/**
	* Save Block Data
	* This function is intended to save changes to the specific block data, such as when editing 
	* the content of a block. It receives the updated block data (data) as a parameter and updates 
	* the appropriate block in the state based on that data.
	*/
	const saveBlockData = (data) => {
		
		setBlocks((prevBlocks) => 
			prevBlocks.map((block) => (block.i === data.i ? { ...block, ...data } : block))
		);
		
		closeModal();// Close the modal after saving
	};
	
	/**
	* Edit Block in Grid
	* Function to set a block to be edited
	*/
	const handleBlockEdit = (block) => {
		
		setEditingBlock(block);// Set the block that is currently being edited
		setBlockContent(block.content);// Pre-populate with current block content
		setModalOpen(true);
	};
	
	/**
	* Remove Block from Grid
	* Function to remove a block from the grid
	*/
	const handleBlockRemove = (id) => {
	
		setBlocks(prevBlocks => prevBlocks.filter(block => block.i !== id));
		setLayout(prevLayout => prevLayout.filter(item => item.i !== id));
	};
	
	/**
	* Close Modal for Block
	* Function to close modal and reset state
	*/
	const closeModal = () => {
		
		setModalOpen(false);
		setEditingBlock(null);// Set block being edited as none
		setBlockContent('');// Set block content as empty
	};
	
	/**
	* Save Grid Layout
	* This function is responsible for handling changes to the layout of the blocks when 
	* they are moved or resized within the grid. It updates the layout state based on the 
	* new layout configuration (newLayout) provided by the react-grid-layout library whenever 
	* a user drags or resizes a block.
	*/
	const saveGridLayout = (newLayout) => {
	
		const updatedLayout = newLayout.map(item => {
		
			return {
				...item,
				y: Math.min(item.y, gridRows - 1),// Ensure y position does not exceed gridRows
			};
		});
		
		setLayout(updatedLayout);
		const validLayout = updatedLayout.filter((item, index) => {
			
			const block = blocks.find(b => b.i === item.i);
			return block && item.x + block.w <= gridCols && item.y + block.h <= gridRows;
		});
		
		setBlocks(layout => layout.map(block => {
		
			const layoutItem = updatedLayout.find(item => item.i === block.i);
			return layoutItem ? { ...block, x: layoutItem.x, y: layoutItem.y, w: layoutItem.w, h: layoutItem.h } : block;
		}));
	};
	
	return (
		
		<div className="container-md">
		
			<div className="alert alert-info mobile-prompt" role="alert">Rotate your device to landscape to use layout editor.</div>
			
			{/* Create buttons for adding each block size defined in BlocksMap */}
			{BlocksMap.map((size, index) => (
				<button 
					key={index} 
					className="btn btn-primary mb-3 me-2"
					onClick={() => handleBlockAdd(size.id)}
				>
					Add {size.content} Block
				</button>
			))}
			
			{/* Pass the constants as props to BlocksGrid */}
			<BlocksGrid 
				layout={layout}
				onLayoutChange={saveGridLayout}
				blocks={blocks}
				onEdit={handleBlockEdit}
				rowHeight={rowHeight}
				//gridHeight={gridHeight}
				gridCols={gridCols}
				gridRows={gridRows}
			/>
			
			{/* Bootstrap Modal for Editing Block Content */}
			{isModalOpen && currentModalType === 'photo' && (
				<ModalPhoto
					isOpen={isModalOpen}
					onRequestClose={closeModal}
					blockData={editingBlock}
					onSave={saveBlockData}
				/>
			)}
			
			{isModalOpen && currentModalType === 'text' && (
				<ModalText
					isOpen={isModalOpen}
					onRequestClose={closeModal}
					blockData={editingBlock}
					onSave={saveBlockData}
				/>
			)}
			
			<div className="layout-editor-intro">
				<h4>How it works:</h4>
				<ul>
					<li><i className="bi bi-arrows-move me-2"></i><strong>Drag and Drop Functionality:</strong> Easily reposition content blocks within the canvas without overlap for clear visibility.</li>
					<li><i className="bi bi-pencil"></i> <strong>Add and Edit Content:</strong> Click on the blocks’ edit icon to modify their content easily.</li>
					<li><i className="bi bi-trash"></i> <strong>Remove Blocks:</strong> To remove a block from the grid, click on the trash icon.</li>
				</ul>
				
				<h4>Key Features:</h4>
				<ul>
					<li><i className="bi bi-arrow-up-circle me-2"></i><strong>Automatic Adjustment:</strong> When removing a block, lower blocks will automatically shift up, maintaining a tidy layout.</li>
					<li><i className="bi bi-eye me-2"></i><strong>Visual Indicators:</strong> Highlighted areas show where you can safely place blocks, facilitating efficient arrangement.</li>
					<li><i className="bi bi-lightbulb me-2"></i><strong>Flexible Exploration:</strong> Experiment with different layouts to find the best configuration for your content, ensuring optimal user experience.</li>
					
				</ul>
			</div>
			
		</div>
	);
};

export default LayoutEditor;


//src/components/main/blocks/BlocksGrid.js
import React from 'react';
import { WidthProvider, Responsive } from 'react-grid-layout';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import '../../../styles/GridLayout.css';
// Load Blocks for the Grid here. Modals for Blocks are loaded in LayoutEditor.js
import BlockPhoto from '../blocks/BlockPhoto';
import BlockText from '../blocks/BlockText';

// Wrapping Responsive in WidthProvider to handle width calculations for the grid layout
const ResponsiveGridLayout = WidthProvider(Responsive);

const BlocksGrid = ({ layout, layoutChange, blocks, onEdit, rowHeight, gridCols, gridRows }) => {
	
	const renderBlock = (block) => {
		
		switch (block.type) {
		
			case 'photo' :
				return <BlockPhoto block={block} onEdit={onEdit} />;
				
			case 'text' :
				return <BlockText block={block} onEdit={onEdit} />;
				
			default :
				return null;
		}
	};
	
	return (
		<ResponsiveGridLayout 
			className="layout"
			layouts={layout}
			style={{ minHeight: `${rowHeight * 5}px`, position: 'relative' }}// Minimum height for 5 rows
			breakpoints={{ lg: 1200 }}// Breakpoint to determine layout changes
			cols={{ lg: gridCols }}// Number of columns for the grid
			rowHeight={ rowHeight }// Height of each grid row
			onLayoutChange={ layoutChange }// Function to call on layout change
			isBounded={ false }// Prevent blocks from being dragged outside the grid
			preventCollision={ true }// Prevent collisions when dragging
			isResizable={ false }// Disable resizing of blocks
			draggableHandle=".block-option-drag"// Handle class for dragging
			verticalCompact={ true }
		>
		
			{blocks.map(block => (
				<div key={block.i} data-grid={block}>
					{renderBlock(block)}
				</div>
			))}
			
		</ResponsiveGridLayout>
	);
};

export default BlocksGrid;


//src/components/main/blocks/BlockPhoto.js

import React from 'react';

const BlockPhoto = ({ block, onEdit }) => (
  <div>
    <h3>{block.content}</h3>
    <img src={block.content || "placeholder.png"} alt={block.content} height="100" />
    <button onClick={() => onEdit(block)}>Edit</button>
  </div>
);

export default BlockPhoto;


//src/components/main/blocks/ModalText.js
import React, { useState, useEffect } from 'react';

const ModalText = ({ isOpen, onRequestClose, blockData, onSave }) => {
    const [formData, setFormData] = useState(blockData);

    useEffect(() => {
        setFormData(blockData);
    }, [blockData]);

    const handleChange = (e) => {
        const { name, value } = e.target;
        setFormData({ ...formData, [name]: value });
    };

    const handleSubmit = (e) => {
        e.preventDefault();
        onSave(formData); // Passes the modified data back to the editor
        onRequestClose();
    };

    return (
        <div className={`modal fade ${isOpen ? 'show' : ''}`} style={{ display: isOpen ? 'block' : 'none' }} tabIndex="-1" role="dialog">
            <div className="modal-dialog" role="document">
                <div className="modal-content">
                    <div className="modal-header">
                        <h5 className="modal-title">Edit Text Block</h5>
                        <button type="button" className="btn-close" onClick={onRequestClose} aria-label="Close"></button>
                    </div>
                    <div className="modal-body">
                        <form onSubmit={handleSubmit}>
                            <div className="mb-3">
                                <label htmlFor="content" className="form-label">Content:</label>
                                <textarea
                                    id="content"
                                    name="content"
                                    value={formData.content || ''}
                                    onChange={handleChange}
                                    className="form-control"
                                    rows="4"
                                />
                            </div>
                            <button type="submit" className="btn btn-primary">Save</button>
                            <button type="button" className="btn btn-secondary" onClick={onRequestClose}>Cancel</button>
                        </form>
                    </div>
                </div>
            </div>
            {isOpen && <div className="modal-backdrop fade show"></div>}
        </div>
    );
};

export default ModalText;


what is wrong with my code? cant add blocks to the grid.