255 lines
8.8 KiB
JavaScript
255 lines
8.8 KiB
JavaScript
/**
|
|
* WordPress dependencies
|
|
*/
|
|
import { __ } from '@wordpress/i18n';
|
|
import { SelectControl, TextareaControl, Button, TextControl } from '@wordpress/components';
|
|
import { useState, useEffect } from '@wordpress/element';
|
|
import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';
|
|
|
|
const DEFAULT_COLORS = [
|
|
'#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF',
|
|
'#FF9F40', '#FF6384', '#C9CBCF', '#4BC0C0', '#FF9F40'
|
|
];
|
|
|
|
/**
|
|
* LCPDataSelector Component
|
|
*
|
|
* @param {Object} props Component properties
|
|
* @param {Object} props.value Current data value
|
|
* @param {Function} props.onChange Callback function when data changes
|
|
* @param {string} props.dataSource Current data source type
|
|
* @param {Function} props.onDataSourceChange Callback function when data source changes
|
|
*/
|
|
const LCPDataSelector = ({ value, onChange, dataSource, onDataSourceChange }) => {
|
|
const [csvUrl, setCsvUrl] = useState('');
|
|
const [jsonData, setJsonData] = useState('');
|
|
const [error, setError] = useState('');
|
|
|
|
// Initialize component state with existing data
|
|
useEffect(() => {
|
|
if (value && Object.keys(value).length > 0) {
|
|
if (dataSource === 'manual_json') {
|
|
setJsonData(JSON.stringify(value, null, 2));
|
|
} else if (dataSource === 'csv_url') {
|
|
const storedUrl = value[Object.keys(value)[0]]?.[0]?.sourceUrl;
|
|
if (storedUrl) {
|
|
setCsvUrl(storedUrl);
|
|
}
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
const dataSourceOptions = [
|
|
{ label: __('Manual JSON', 'lcp'), value: 'manual_json' },
|
|
{ label: __('CSV Upload', 'lcp'), value: 'csv_upload' },
|
|
{ label: __('CSV URL', 'lcp'), value: 'csv_url' },
|
|
];
|
|
|
|
const validateJsonData = (jsonString) => {
|
|
try {
|
|
const parsed = JSON.parse(jsonString);
|
|
if (typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
throw new Error('Data must be an object with dataset names as keys');
|
|
}
|
|
|
|
// Validate each dataset
|
|
Object.entries(parsed).forEach(([datasetName, dataset]) => {
|
|
if (!Array.isArray(dataset)) {
|
|
throw new Error(`Dataset "${datasetName}" must be an array`);
|
|
}
|
|
|
|
dataset.forEach((item, index) => {
|
|
if (!item.label || !item.value) {
|
|
throw new Error(`Item at index ${index} in dataset "${datasetName}" is missing required fields (label or value)`);
|
|
}
|
|
// Add default color if not provided
|
|
if (!item.color) {
|
|
item.color = DEFAULT_COLORS[index % DEFAULT_COLORS.length];
|
|
}
|
|
});
|
|
});
|
|
|
|
setError('');
|
|
return parsed;
|
|
} catch (e) {
|
|
setError(e.message);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const handleJsonChange = (newJsonData) => {
|
|
setJsonData(newJsonData);
|
|
const validData = validateJsonData(newJsonData);
|
|
if (validData) {
|
|
onChange(validData);
|
|
}
|
|
};
|
|
|
|
const handleCsvUpload = (media) => {
|
|
const sourceUrl = media.url;
|
|
fetch(media.url)
|
|
.then(response => response.text())
|
|
.then(csvText => {
|
|
const data = parseCsvToJson(csvText, sourceUrl);
|
|
if (data) {
|
|
onChange(data);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
setError('Error reading CSV file: ' + err.message);
|
|
});
|
|
};
|
|
|
|
const handleCsvUrlChange = (url) => {
|
|
setCsvUrl(url);
|
|
if (url) {
|
|
fetch(url)
|
|
.then(response => response.text())
|
|
.then(csvText => {
|
|
const data = parseCsvToJson(csvText, url);
|
|
if (data) {
|
|
onChange(data);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
setError('Error fetching CSV file: ' + err.message);
|
|
});
|
|
}
|
|
};
|
|
|
|
const parseCsvToJson = (csvText, sourceUrl = '') => {
|
|
try {
|
|
const lines = csvText.split('\n');
|
|
if (lines.length < 2) {
|
|
throw new Error('CSV must have at least a header row and one data row');
|
|
}
|
|
|
|
const headers = lines[0].split(',').map(h => h.trim());
|
|
if (!headers.includes('label') || !headers.includes('value')) {
|
|
throw new Error('CSV must have "label" and "value" columns');
|
|
}
|
|
|
|
// Create a single dataset from CSV
|
|
const dataset = lines.slice(1)
|
|
.filter(line => line.trim())
|
|
.map((line, index) => {
|
|
const values = line.split(',').map(v => v.trim());
|
|
const item = {};
|
|
headers.forEach((header, i) => {
|
|
if (header === 'color' && !values[i]) {
|
|
item[header] = DEFAULT_COLORS[index % DEFAULT_COLORS.length];
|
|
} else {
|
|
item[header] = values[i];
|
|
}
|
|
});
|
|
if (sourceUrl) {
|
|
item.sourceUrl = sourceUrl;
|
|
}
|
|
return item;
|
|
});
|
|
|
|
// Create the new data structure
|
|
const data = {
|
|
'Dataset 1': dataset
|
|
};
|
|
|
|
setError('');
|
|
return data;
|
|
} catch (e) {
|
|
setError('Error parsing CSV: ' + e.message);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="lcp-data-selector">
|
|
<SelectControl
|
|
label={__('Data Source', 'lcp')}
|
|
value={dataSource}
|
|
options={dataSourceOptions}
|
|
onChange={onDataSourceChange}
|
|
/>
|
|
|
|
{dataSource === 'manual_json' && (
|
|
<TextareaControl
|
|
label={__('JSON Data', 'lcp')}
|
|
help={__('Enter datasets with arrays containing objects with label and value properties. Color is optional.', 'lcp')}
|
|
value={jsonData}
|
|
onChange={handleJsonChange}
|
|
placeholder={`{
|
|
"Dataset 1": [
|
|
{
|
|
"label": "Label 1",
|
|
"value": 100,
|
|
"color": "red"
|
|
},
|
|
{
|
|
"label": "Label 2",
|
|
"value": 200
|
|
}
|
|
],
|
|
"Dataset 2": [
|
|
{
|
|
"label": "Label 3",
|
|
"value": 300,
|
|
"color": "green"
|
|
}
|
|
]
|
|
}`}
|
|
/>
|
|
)}
|
|
|
|
{dataSource === 'csv_upload' && (
|
|
<MediaUploadCheck>
|
|
<MediaUpload
|
|
onSelect={handleCsvUpload}
|
|
allowedTypes={['text/csv']}
|
|
render={({ open }) => (
|
|
<div>
|
|
<Button onClick={open} isPrimary>
|
|
{__('Upload CSV File', 'lcp')}
|
|
</Button>
|
|
{value && Object.keys(value).length > 0 && value[Object.keys(value)[0]]?.[0]?.sourceUrl && (
|
|
<div className="current-file" style={{ marginTop: '10px' }}>
|
|
{__('Current file:', 'lcp')} {value[Object.keys(value)[0]][0].sourceUrl.split('/').pop()}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
/>
|
|
</MediaUploadCheck>
|
|
)}
|
|
|
|
{dataSource === 'csv_url' && (
|
|
<TextControl
|
|
label={__('CSV URL', 'lcp')}
|
|
value={csvUrl}
|
|
onChange={handleCsvUrlChange}
|
|
help={__('Enter URL of a CSV file with label and value columns. Color column is optional.', 'lcp')}
|
|
/>
|
|
)}
|
|
|
|
{error && (
|
|
<div className="lcp-data-selector-error" style={{ color: 'red', marginTop: '10px' }}>
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{value && Object.keys(value).length > 0 && (
|
|
<div className="data-preview" style={{ marginTop: '15px' }}>
|
|
<h4>{__('Current Data Preview:', 'lcp')}</h4>
|
|
<div style={{ fontSize: '12px', color: '#666' }}>
|
|
{Object.entries(value).map(([datasetName, dataset]) => (
|
|
<div key={datasetName}>
|
|
{datasetName}: {dataset.length} {__('data points', 'lcp')}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LCPDataSelector;
|