Added Dataset Builder
This commit is contained in:
285
components/LCPDatasetBuilder.js
Normal file
285
components/LCPDatasetBuilder.js
Normal file
@ -0,0 +1,285 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
TextControl,
|
||||
ColorPicker,
|
||||
Card,
|
||||
CardBody,
|
||||
Popover,
|
||||
} from '@wordpress/components';
|
||||
import { useState, useRef } from '@wordpress/element';
|
||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
|
||||
const ItemTypes = {
|
||||
DATASET_ITEM: 'dataset_item'
|
||||
};
|
||||
|
||||
const DatasetItem = ({ item, datasetKey, moveItem, updateItem, items }) => {
|
||||
const [showColorPicker, setShowColorPicker] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
||||
const [{ isDragging }, drag] = useDrag({
|
||||
type: ItemTypes.DATASET_ITEM,
|
||||
item: { id: item.id, datasetKey },
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
});
|
||||
|
||||
const [{ isOver }, drop] = useDrop({
|
||||
accept: ItemTypes.DATASET_ITEM,
|
||||
hover(draggedItem, monitor) {
|
||||
if (!ref.current) return;
|
||||
if (draggedItem.id === item.id) return;
|
||||
|
||||
const hoverBoundingRect = ref.current.getBoundingClientRect();
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||
const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;
|
||||
const clientOffset = monitor.getClientOffset();
|
||||
|
||||
if (!clientOffset) return;
|
||||
|
||||
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
|
||||
const hoverClientX = clientOffset.x - hoverBoundingRect.left;
|
||||
|
||||
// If hovering in the right half and below middle, make it a child
|
||||
const shouldBeChild = hoverClientX > hoverMiddleX && hoverClientY > hoverMiddleY;
|
||||
|
||||
moveItem(draggedItem.id, item.id, shouldBeChild);
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isOver: monitor.isOver(),
|
||||
}),
|
||||
});
|
||||
|
||||
// Initialize drag and drop refs
|
||||
drag(drop(ref));
|
||||
|
||||
// Get child items
|
||||
const childItems = items.filter(i => i.parent === item.id);
|
||||
|
||||
const style = {
|
||||
marginLeft: item.parent ? '20px' : '0',
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
cursor: 'move',
|
||||
background: isOver ? '#f0f0f0' : 'white',
|
||||
border: isOver ? '2px dashed #0073aa' : '1px solid #e2e4e7',
|
||||
marginBottom: '8px',
|
||||
transition: 'all 0.2s ease',
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div ref={ref} style={style}>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||
<TextControl
|
||||
placeholder={__('Label', 'lcp')}
|
||||
value={item.label}
|
||||
onChange={(value) => updateItem(datasetKey, item.id, 'label', value)}
|
||||
style={{ flex: 2 }}
|
||||
/>
|
||||
<TextControl
|
||||
type="number"
|
||||
placeholder={__('Value', 'lcp')}
|
||||
value={item.value}
|
||||
onChange={(value) => updateItem(datasetKey, item.id, 'value', parseFloat(value))}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<Button
|
||||
onClick={() => setShowColorPicker(true)}
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
width: '30px',
|
||||
height: '30px',
|
||||
borderRadius: '50%',
|
||||
padding: 0,
|
||||
border: '1px solid #ddd'
|
||||
}}
|
||||
/>
|
||||
{showColorPicker && (
|
||||
<Popover
|
||||
onClose={() => setShowColorPicker(false)}
|
||||
position="bottom center"
|
||||
>
|
||||
<div style={{ padding: '10px' }}>
|
||||
<ColorPicker
|
||||
color={item.color}
|
||||
onChange={(value) => updateItem(datasetKey, item.id, 'color', value)}
|
||||
enableAlpha={false}
|
||||
/>
|
||||
</div>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
{/* Render child items */}
|
||||
{childItems.map(childItem => (
|
||||
<DatasetItem
|
||||
key={childItem.id}
|
||||
item={childItem}
|
||||
datasetKey={datasetKey}
|
||||
moveItem={moveItem}
|
||||
updateItem={updateItem}
|
||||
items={items}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LCPDatasetBuilder = ({ value, onChange }) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [datasets, setDatasets] = useState(value || {});
|
||||
|
||||
const addNewDataset = () => {
|
||||
const newDatasetName = `Dataset ${Object.keys(datasets).length + 1}`;
|
||||
const updatedDatasets = {
|
||||
...datasets,
|
||||
[newDatasetName]: [{
|
||||
id: `dataset-${Date.now()}`,
|
||||
label: newDatasetName,
|
||||
parent: null,
|
||||
value: 0,
|
||||
color: '#000000'
|
||||
}]
|
||||
};
|
||||
setDatasets(updatedDatasets);
|
||||
onChange(updatedDatasets);
|
||||
};
|
||||
|
||||
const addNewItem = (datasetKey) => {
|
||||
const newItem = {
|
||||
id: `item-${Date.now()}`,
|
||||
label: 'New Item',
|
||||
parent: null,
|
||||
value: 0,
|
||||
color: '#000000'
|
||||
};
|
||||
|
||||
const updatedDatasets = {
|
||||
...datasets,
|
||||
[datasetKey]: [...datasets[datasetKey], newItem]
|
||||
};
|
||||
setDatasets(updatedDatasets);
|
||||
onChange(updatedDatasets);
|
||||
};
|
||||
|
||||
const updateItem = (datasetKey, itemId, field, value) => {
|
||||
const updatedDatasets = {
|
||||
...datasets,
|
||||
[datasetKey]: datasets[datasetKey].map(item =>
|
||||
item.id === itemId ? { ...item, [field]: value } : item
|
||||
)
|
||||
};
|
||||
setDatasets(updatedDatasets);
|
||||
onChange(updatedDatasets);
|
||||
};
|
||||
|
||||
const moveItem = (draggedId, targetId, makeChild) => {
|
||||
const datasetKey = Object.keys(datasets).find(key =>
|
||||
datasets[key].some(item => item.id === draggedId)
|
||||
);
|
||||
|
||||
if (!datasetKey) return;
|
||||
|
||||
const items = [...datasets[datasetKey]];
|
||||
const draggedItem = items.find(item => item.id === draggedId);
|
||||
const targetItem = items.find(item => item.id === targetId);
|
||||
|
||||
if (!draggedItem || !targetItem) return;
|
||||
|
||||
// If target is a descendant of dragged item, prevent the move
|
||||
let current = targetItem;
|
||||
while (current.parent) {
|
||||
if (current.parent === draggedId) return;
|
||||
current = items.find(item => item.id === current.parent);
|
||||
}
|
||||
|
||||
// Update parent reference
|
||||
draggedItem.parent = makeChild ? targetId : targetItem.parent;
|
||||
|
||||
const updatedDatasets = {
|
||||
...datasets,
|
||||
[datasetKey]: items
|
||||
};
|
||||
|
||||
setDatasets(updatedDatasets);
|
||||
onChange(updatedDatasets);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
if (Object.keys(datasets).length === 0) {
|
||||
addNewDataset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{__('Open Dataset Builder', 'lcp')}
|
||||
</Button>
|
||||
|
||||
{isModalOpen && (
|
||||
<Modal
|
||||
title={__('Dataset Builder', 'lcp')}
|
||||
onRequestClose={() => setIsModalOpen(false)}
|
||||
style={{ width: '90vw' }}
|
||||
>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<div style={{ padding: '20px' }}>
|
||||
{Object.entries(datasets).map(([datasetKey, items]) => (
|
||||
<div key={datasetKey} style={{ marginBottom: '20px' }}>
|
||||
<h3>{datasetKey}</h3>
|
||||
<div>
|
||||
{items
|
||||
.filter(item => !item.parent) // Only render top-level items
|
||||
.map(item => (
|
||||
<DatasetItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
datasetKey={datasetKey}
|
||||
moveItem={moveItem}
|
||||
updateItem={updateItem}
|
||||
items={items}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => addNewItem(datasetKey)}
|
||||
style={{ marginTop: '10px' }}
|
||||
>
|
||||
{__('Add Item', 'lcp')}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={addNewDataset}
|
||||
style={{ marginTop: '20px' }}
|
||||
>
|
||||
{__('Add New Dataset', 'lcp')}
|
||||
</Button>
|
||||
</div>
|
||||
</DndProvider>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LCPDatasetBuilder;
|
||||
Reference in New Issue
Block a user