This commit is contained in:
Jeremy Rangel
2025-01-21 18:41:51 -08:00
commit dd4bd0caf5
26 changed files with 70922 additions and 0 deletions

View File

@ -0,0 +1,191 @@
import React, { useState, useEffect } from 'react';
import { Button, Icon, PanelBody, ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import LCPDimensionControl from './LCPDimensionControl';
import LCPHTMLModal from './LCPHTMLModal.js';
const LCPChartBlockSettings = ({ attributes, setAttributes }) => {
// Use `legendAlignment` from props (block attributes)
const { renderLegend,
legendLocation,
legendAlignment,
allowDownloadImage,
downloadImageMaxWidth,
allowDownloadCsv,
allowDownloadJson,
allowSorting,
allowFiltering,
footerContent,
chartTitle,
chartSubtitle,
toolbarLocation,
toolbarAlignment
} = attributes;
// Set local state for legend location (for UI updates)
const [selectedLegendLocation, setSelectedLegendLocation] = useState(legendLocation);
// Update block attribute when location changes
useEffect(() => {
setAttributes({ legendLocation: selectedLegendLocation });
}, [selectedLegendLocation, setAttributes]);
// Set local state for alignment (for UI updates)
const [selectedLegendAlignment, setSelectedLegendAlignment] = useState(legendAlignment);
// Update block attribute when alignment changes
useEffect(() => {
setAttributes({ legendAlignment: selectedLegendAlignment });
}, [selectedLegendAlignment, setAttributes]);
// Set local state for alignment (for UI updates)
const [selectedToolbarAlignment, setSelectedToolbarAlignment] = useState(toolbarAlignment);
// Update block attribute when alignment changes
useEffect(() => {
setAttributes({ toolbarAlignment: selectedToolbarAlignment });
}, [selectedToolbarAlignment, setAttributes]);
// Render the component
return (
<div>
{/* Legend Settings Panel */}
<PanelBody title={__('Legend', 'lcp')} initialOpen={false}>
<ToggleControl
label={__('Render Legend', 'lcp')}
checked={renderLegend}
onChange={(value) => setAttributes({ renderLegend: value })}
/>
{renderLegend && (
<div>
{/* Legend - Alignment Buttons */}
<div>
{/* Left Alignment Button */}
<Button
isPrimary={selectedLegendAlignment === 'left'}
onClick={() => setSelectedLegendAlignment('left')}
>
{__('Left', 'lcp')}
</Button>
{/* Center Alignment Button */}
<Button
isPrimary={selectedLegendAlignment === 'center'}
onClick={() => setSelectedLegendAlignment('center')}
>
{__('Center', 'lcp')}
</Button>
{/* Right Alignment Button */}
<Button
isPrimary={selectedLegendAlignment === 'right'}
onClick={() => setSelectedLegendAlignment('right')}
>
{__('Right', 'lcp')}
</Button>
</div>
{/* Legend - Location Buttons */}
<div>
{/* Top Location Button */}
<Button
isPrimary={selectedLegendLocation === 'top'}
onClick={() => setSelectedLegendLocation('top')}
>
{__('Top', 'lcp')}
</Button>
{/* Bottom Location Button */}
<Button
isPrimary={selectedLegendLocation === 'bottom'}
onClick={() => setSelectedLegendLocation('bottom')}
>
{__('Bottom', 'lcp')}
</Button>
</div>
</div>
)}
</PanelBody>
{/* Controls Settings Panel */}
<PanelBody title={__('Controls', 'lcp')} initialOpen={false}>
<div>
{/* Left Alignment Button */}
<Button
isPrimary={selectedToolbarAlignment === 'left'}
onClick={() => setSelectedToolbarAlignment('left')}
>
{__('Left', 'lcp')}
</Button>
{/* Center Alignment Button */}
<Button
isPrimary={selectedToolbarAlignment === 'center'}
onClick={() => setSelectedToolbarAlignment('center')}
>
{__('Center', 'lcp')}
</Button>
{/* Right Alignment Button */}
<Button
isPrimary={selectedToolbarAlignment === 'right'}
onClick={() => setSelectedToolbarAlignment('right')}
>
{__('Right', 'lcp')}
</Button>
</div>
<ToggleControl
label={__('Allow Filtering', 'lcp')}
checked={allowFiltering}
onChange={(value) => setAttributes({ allowFiltering: value })}
/>
<ToggleControl
label={__('Allow Sorting', 'lcp')}
checked={allowSorting}
onChange={(value) => setAttributes({ allowSorting: value })}
/>
<ToggleControl
label={__('Allow Download Image', 'lcp')}
checked={allowDownloadImage}
onChange={(value) => setAttributes({ allowDownloadImage: value })}
/>
{allowDownloadImage && (
<LCPDimensionControl
unitTypes={['px']}
label={__('Download Image Max Width', 'lcp')}
value={downloadImageMaxWidth}
onChange={(value) => setAttributes({ downloadImageMaxWidth: value })}
/>
)}
<ToggleControl
label={__('Allow Download CSV', 'lcp')}
checked={allowDownloadCsv}
onChange={(value) => setAttributes({ allowDownloadCsv: value })}
/>
<ToggleControl
label={__('Allow Download JSON', 'lcp')}
checked={allowDownloadJson}
onChange={(value) => setAttributes({ allowDownloadJson: value })}
/>
</PanelBody>
{/* Tooltips and Popups Panel */}
<PanelBody title={__('Tooltips and Popups', 'lcp')} initialOpen={false}>
<LCPHTMLModal />
</PanelBody>
{/* Header Settings Panel */}
<PanelBody title={__('Header', 'lcp')} initialOpen={false}>
</PanelBody>
{/* Footer Settings Panel */}
<PanelBody title={__('Footer', 'lcp')} initialOpen={false}>
<LCPHTMLModal />
</PanelBody>
</div>
);
};
export default LCPChartBlockSettings;

View File

@ -0,0 +1,500 @@
import { useMemo, useCallback, useRef, useState } from '@wordpress/element';
import { Popover, Button, Modal, SelectControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
const generateId = () => {
const randomStr = Math.random().toString(36).substring(2, 8);
return `lcpDatapoint-${randomStr}`;
};
// Helper function to determine text color based on background
const getContrastColor = (hexcolor) => {
if (!hexcolor) return 'inherit';
const r = parseInt(hexcolor.slice(1, 3), 16);
const g = parseInt(hexcolor.slice(3, 5), 16);
const b = parseInt(hexcolor.slice(5, 7), 16);
const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
return (yiq >= 128) ? 'black' : 'white';
};
const ColorCellRenderer = (props) => {
if (!props.value) return '';
return (
<div
style={{
backgroundColor: props.value,
color: getContrastColor(props.value),
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '4px',
borderRadius: '4px'
}}
>
{props.value}
</div>
);
};
const ParentCellRenderer = (props) => {
if (!props.data || !props.data.ID) {
return null;
}
// Create options from all available rows except current
const options = [
{ label: __('None', 'lcp-visualize'), value: '' }
];
// Get all rows from the grid
props.api.forEachNode(node => {
if (node.data &&
node.data.ID &&
node.data.ID !== props.data.ID) {
options.push({
label: node.data.Label || node.data.ID,
value: node.data.ID
});
}
});
const handleChange = (newValue) => {
if (props.context && props.context.handleParentChange) {
props.context.handleParentChange(props.data, newValue);
}
};
return (
<SelectControl
value={props.data.Parent || ''}
options={options}
onChange={handleChange}
/>
);
};
const ColumnHeaderComponent = (props) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [headerName, setHeaderName] = useState(props.displayName);
const dataTypeOptions = [
{ label: __('Text', 'lcp'), value: 'lcpText' },
{ label: __('Number', 'lcp'), value: 'lcpNumber' },
{ label: __('Color', 'lcp'), value: 'lcpColor' },
{ label: __('Date/Time', 'lcp'), value: 'lcpDate' }
];
const handleTypeChange = (newType) => {
props.setColumnType(props.column.colId, newType);
};
const handleHeaderChange = (newHeader) => {
if (props.onHeaderChange && newHeader !== props.displayName) {
props.onHeaderChange(props.column.colId, newHeader);
}
};
const handleSave = () => {
handleHeaderChange(headerName);
setIsModalOpen(false);
};
const handleCancel = () => {
setHeaderName(props.displayName);
setIsModalOpen(false);
};
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', height: '100%' }}>
<span>{props.displayName}</span>
<Button
isSmall
variant="tertiary"
onClick={() => setIsModalOpen(true)}
icon={
<svg
width="16"
height="16"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
style={{ fill: 'currentColor' }}
>
<path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" />
</svg>
}
/>
{isModalOpen && (
<Modal
title={__('Column Settings', 'lcp')}
onRequestClose={handleCancel}
style={{ width: '400px' }}
>
<div style={{ padding: '20px' }}>
<div style={{ marginBottom: '20px' }}>
<label style={{ display: 'block', marginBottom: '8px' }}>
{__('Column Header', 'lcp')}
</label>
<input
type="text"
value={headerName}
onChange={(e) => setHeaderName(e.target.value)}
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #757575'
}}
disabled={props.column.colId === 'ID'}
/>
</div>
<SelectControl
label={__('Data Type', 'lcp')}
value={props.currentType || 'lcpText'}
options={dataTypeOptions}
onChange={handleTypeChange}
disabled={props.column.colId === 'ID'}
/>
<div style={{ marginTop: '20px', display: 'flex', justifyContent: 'flex-end', gap: '8px' }}>
<Button
variant="secondary"
onClick={handleCancel}
>
{__('Cancel', 'lcp')}
</Button>
<Button
variant="primary"
onClick={handleSave}
>
{__('Save', 'lcp-visualize')}
</Button>
</div>
</div>
</Modal>
)}
</div>
);
};
const LCPDataGrid = ({
chartData = [],
onDataChange,
columnTypes,
onColumnTypeChange,
attributes,
setAttributes
}) => {
const gridRef = useRef();
const [contextMenu, setContextMenu] = useState(null);
// Helper function to guess column type based on value
const guessColumnType = useCallback((value) => {
if (typeof value === 'number') return 'lcpNumber';
if (typeof value === 'string') {
if (/^#([0-9A-F]{3}){1,2}$/i.test(value)) return 'lcpColor';
if (!isNaN(Date.parse(value))) return 'lcpDate';
}
return 'lcpText';
}, []);
// Handle parent change
const handleParentChange = useCallback((data, newParent) => {
const newData = chartData.map(item =>
item.ID === data.ID ? { ...item, Parent: newParent } : item
);
onDataChange(newData);
}, [chartData, onDataChange]);
// Use columnTypes from attributes if available, otherwise use direct columnTypes prop
const effectiveColumnTypes = attributes?.columnTypes || columnTypes || {};
// Convert any old column types to new format
const migratedColumnTypes = useMemo(() => {
const typeMap = {
'textColumn': 'lcpText',
'numericColumn': 'lcpNumber',
'colorColumn': 'lcpColor',
'dateColumn': 'lcpDate'
};
return Object.entries(effectiveColumnTypes).reduce((acc, [key, value]) => {
acc[key] = typeMap[value] || value;
return acc;
}, {});
}, [effectiveColumnTypes]);
const handleColumnTypeChange = useCallback((colId, newType) => {
if (setAttributes) {
setAttributes({
columnTypes: {
...migratedColumnTypes,
[colId]: newType
}
});
} else if (onColumnTypeChange) {
onColumnTypeChange(colId, newType);
}
}, [migratedColumnTypes, setAttributes, onColumnTypeChange]);
// Define column type configurations
const gridColumnTypes = useMemo(() => ({
lcpNumber: {
filter: 'agNumberColumnFilter',
filterParams: {
buttons: ['apply', 'reset'],
closeOnApply: true
},
valueParser: params => {
if (!params.newValue) return '';
const parsed = parseFloat(params.newValue);
return isNaN(parsed) ? '' : parsed;
}
},
lcpText: {
filter: 'agTextColumnFilter',
filterParams: {
buttons: ['apply', 'reset'],
closeOnApply: true
}
},
lcpColor: {
filter: 'agTextColumnFilter',
filterParams: {
buttons: ['apply', 'reset'],
closeOnApply: true
},
cellRenderer: ColorCellRenderer,
valueParser: params => {
if (!params.newValue) return '';
return /^#([0-9A-F]{3}){1,2}$/i.test(params.newValue) ?
params.newValue.toUpperCase() : '';
}
},
lcpDate: {
filter: 'agDateColumnFilter',
filterParams: {
buttons: ['apply', 'reset'],
closeOnApply: true
},
valueParser: params => {
if (!params.newValue) return '';
const date = new Date(params.newValue);
return isNaN(date.getTime()) ? '' : date.toISOString();
}
}
}), []);
const handleHeaderChange = useCallback((oldHeader, newHeader) => {
if (oldHeader === newHeader || !chartData.length) return;
// Update the chartData with the new header
const newData = chartData.map(row => {
const { [oldHeader]: value, ...rest } = row;
return {
...rest,
[newHeader]: value
};
});
onDataChange(newData);
}, [chartData, onDataChange]);
// Generate column definitions based on chartData
const columnDefs = useMemo(() => {
if (!chartData.length) return [];
// Get all unique keys from the data
const allKeys = Array.from(new Set(
chartData.flatMap(item => Object.keys(item))
)).filter(key => key !== 'Parent'); // Remove Parent from regular columns
// Create column definitions for regular columns
const regularColumns = allKeys.map(key => {
const sampleValue = chartData.find(item => item[key] !== undefined)?.[key];
const defaultType = guessColumnType(sampleValue);
const currentType = migratedColumnTypes[key] || defaultType;
return {
field: key,
headerName: key,
hide: key === 'ID',
headerComponent: ColumnHeaderComponent,
headerComponentParams: {
displayName: key,
onHeaderChange: handleHeaderChange,
setColumnType: handleColumnTypeChange,
currentType: currentType
},
type: currentType,
editable: key !== 'ID',
sortable: true,
filter: true
};
});
// Add Parent column with ParentCellRenderer
const parentColumn = {
field: 'Parent',
headerName: __('Parent', 'lcp-visualize'),
cellRenderer: ParentCellRenderer,
cellRendererParams: {
context: {
handleParentChange
}
},
editable: false,
sortable: true,
filter: true,
minWidth: 200
};
return [...regularColumns, parentColumn];
}, [chartData, migratedColumnTypes, handleColumnTypeChange, handleHeaderChange, guessColumnType, handleParentChange]);
const defaultColDef = useMemo(() => ({
flex: 1,
minWidth: 100,
editable: true,
sortable: true,
filter: true,
resizable: true
}), []);
const rowData = useMemo(() => {
return chartData.map(point => ({
...point,
ID: point.ID || generateId()
}));
}, [chartData]);
const handleContextMenu = useCallback((event) => {
event.preventDefault();
const target = event.target;
const rowCell = target.closest('.ag-cell');
if (!rowCell) return;
const rect = rowCell.getBoundingClientRect();
const rowNode = gridRef.current.api.getRowNode(rowCell.parentElement.getAttribute('row-index'));
if (!rowNode || !rowNode.data || !rowNode.data.ID) return;
setContextMenu({
type: 'row',
position: { top: rect.bottom, left: rect.left },
rowIndex: rowNode.rowIndex,
totalRows: gridRef.current.api.getDisplayedRowCount()
});
}, []);
const handleRowAction = useCallback((action, rowIndex) => {
const newData = [...chartData];
// Create empty row with all existing columns
const emptyRow = { ID: generateId() };
if (chartData.length > 0) {
// Get all columns from existing data
const columns = Array.from(new Set(
chartData.flatMap(item => Object.keys(item))
));
// Initialize each column with an appropriate empty value
columns.forEach(col => {
if (col === 'ID') return; // Skip Id as it's already set
// Get a sample value to determine appropriate empty value
const sampleValue = chartData.find(item => item[col] !== undefined)?.[col];
switch (typeof sampleValue) {
case 'number':
emptyRow[col] = 0;
break;
case 'boolean':
emptyRow[col] = false;
break;
default:
emptyRow[col] = '';
}
});
}
switch (action) {
case 'add-above':
newData.splice(rowIndex - 1, 0, emptyRow);
break;
case 'add-below':
newData.splice(rowIndex, 0, emptyRow);
break;
case 'delete':
newData.splice(rowIndex - 1, 1);
break;
}
onDataChange(newData);
setContextMenu(null);
}, [chartData, onDataChange]);
const handleCellValueChanged = useCallback((params) => {
if (!params.data || !params.data.ID) return;
const newData = chartData.map(item =>
item.ID === params.data.ID ? params.data : item
);
onDataChange(newData);
}, [chartData, onDataChange]);
return (
<div className="ag-theme-alpine" style={{ height: '400px', width: '100%' }}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
columnTypes={gridColumnTypes}
onCellValueChanged={handleCellValueChanged}
suppressRowClickSelection={true}
rowSelection="multiple"
animateRows={true}
context={{
handleParentChange
}}
/>
{contextMenu && (
<Popover
position="bottom left"
onClose={() => setContextMenu(null)}
anchorRect={{
top: contextMenu.mouseEvent.clientY,
left: contextMenu.mouseEvent.clientX,
width: 0,
height: 0,
}}
>
<div style={{ padding: '8px' }}>
<Button
variant="secondary"
onClick={() => {
const selectedRows = gridRef.current.api.getSelectedRows();
const newData = chartData.filter(row => !selectedRows.includes(row));
onDataChange(newData);
setContextMenu(null);
}}
>
{__('Delete Selected Rows', 'lcp-visualize')}
</Button>
</div>
</Popover>
)}
</div>
);
};
export default LCPDataGrid;

View File

@ -0,0 +1,201 @@
import { useState, useEffect, useMemo } from '@wordpress/element';
import { Button, Modal, SelectControl, ToggleControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import LCPDataGrid from './LCPDataGrid';
const LCPDatasetBuilder = ({ chartData = [], onChange, attributes, setAttributes, chartType }) => {
const [isOpen, setIsOpen] = useState(false);
const [options, setOptions] = useState([]);
useEffect(() => {
if (chartData && chartData.length > 0) {
const columns = Object.keys(chartData[0]).filter(key => key !== 'lcpId');
setOptions(columns.map(col => ({ label: col, value: col })));
}
}, [chartData]);
// Get available columns from chartData
const getAvailableColumns = () => {
if (!chartData.length) return [];
// Get all unique keys from the data
const columns = Array.from(new Set(
chartData.flatMap(item => Object.keys(item))
)).filter(key => key !== 'lcpId'); // Exclude lcpId column
return columns;
};
// Get column options based on type filter
const getColumnOptions = (columns, typeFilter = null) => {
return columns
.filter(col => {
if (!typeFilter) return true;
const columnType = attributes.columnTypes?.[col];
return columnType === typeFilter;
})
.map(col => ({ label: col, value: col }));
};
const handleDataChange = (newData) => {
console.log('DatasetBuilder updating chartData:', newData);
onChange(newData);
};
const downloadCSV = () => {
// Get all columns except lcpId
const columns = getAvailableColumns().map(col => col.value);
// Create CSV header
const header = columns.join(',');
// Create CSV rows
const rows = chartData.map(row =>
columns.map(col => {
let value = row[col] || '';
if (typeof value === 'string' && (value.includes(',') || value.includes('"'))) {
value = `"${value.replace(/"/g, '""')}"`;
}
return value;
}).join(',')
).join('\n');
const csv = `${header}\n${rows}`;
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', 'chart-data.csv');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
// Get default value for valueColumn
const defaultValueColumn = useMemo(() => {
return attributes.valueColumn || (options[0]?.value || '');
}, [attributes.valueColumn, options]);
// Get all available columns
const availableColumns = getAvailableColumns();
// Get all column options
const allOptions = getColumnOptions(availableColumns);
// Get numeric column options for bar chart value
const numericOptions = getColumnOptions(availableColumns, 'lcpNumber');
// Determine which options to use for value column
const valueColumnOptions = chartType === 'bar' ? numericOptions : allOptions;
// Add "None" option for optional fields
const optionalOptions = [{ label: __('None', 'lcp-visualize'), value: '' }, ...allOptions];
return (
<div>
<Button
variant="secondary"
onClick={() => setIsOpen(true)}
style={{ marginBottom: '10px', width: '100%' }}
>
{__('Edit Dataset', 'lcp-visualize')}
</Button>
{isOpen && (
<Modal
onRequestClose={() => setIsOpen(false)}
title="Dataset Builder"
style={{ width: '90vw', height: '90vh' }}
>
<div style={{ height: 'calc(90vh - 40px)', padding: '20px', display: 'flex', gap: '20px', marginBottom: '20px' }}>
<div style={{ flex: 1, minWidth: 0 }}>
<LCPDataGrid
chartData={chartData}
onDataChange={handleDataChange}
attributes={attributes}
setAttributes={setAttributes}
columnTypes={attributes.columnTypes}
onColumnTypeChange={(field, type) => {
const newColumnTypes = {
...attributes.columnTypes,
[field]: type
};
setAttributes({
columnTypes: newColumnTypes,
// If we're changing from a numeric type and this is the value column, reset it
valueColumn: field === attributes.valueColumn &&
type !== 'lcpNumber' &&
chartType === 'bar' ? '' : attributes.valueColumn
});
}}
/>
</div>
<div style={{
width: '300px',
padding: '16px',
backgroundColor: '#f8f9fa',
border: '1px solid #ddd',
borderRadius: '4px',
display: 'flex',
flexDirection: 'column',
gap: '16px'
}}>
<SelectControl
label={__('Value Column', 'lcp-visualize')}
help={chartType === 'bar' ?
__('Select a numeric column for bar heights', 'lcp-visualize') :
__('Column to use for values', 'lcp-visualize')
}
value={attributes.valueColumn || ''}
options={valueColumnOptions}
onChange={(value) => setAttributes({ valueColumn: value })}
/>
{chartType === 'bar' && valueColumnOptions.length === 0 && (
<div style={{ color: '#cc1818', marginTop: '4px' }}>
{__('Please set at least one column type to Number to use as the value column', 'lcp-visualize')}
</div>
)}
<SelectControl
label={__('Labels Column', 'lcp-visualize')}
help={__('Column to use for bar labels', 'lcp-visualize')}
value={attributes.labelsColumn || (options[0]?.value || '')}
options={options}
onChange={(value) => setAttributes({ labelsColumn: value })}
/>
<SelectControl
label={__('Color Column', 'lcp-visualize')}
help={__('Column containing bar colors (optional)', 'lcp-visualize')}
value={attributes.colorColumn || ''}
options={optionalOptions}
onChange={(value) => setAttributes({ colorColumn: value })}
/>
<SelectControl
label={__('Popover Content Column', 'lcp-visualize')}
help={__('Column containing HTML content for popovers (optional)', 'lcp-visualize')}
value={attributes.popoverColumn || ''}
options={optionalOptions}
onChange={(value) => setAttributes({ popoverColumn: value })}
/>
<div style={{ marginBottom: '10px' }}>
<Button
variant="secondary"
onClick={downloadCSV}
icon={
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style={{ fill: 'currentColor' }}>
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
</svg>
}
>
{__('Download CSV', 'lcp-visualize')}
</Button>
</div>
</div>
</div>
</Modal>
)}
</div>
);
};
export default LCPDatasetBuilder;

View File

@ -0,0 +1,55 @@
import { __ } from '@wordpress/i18n';
import { SelectControl, TextControl } from '@wordpress/components';
const LCPDimensionControl = ({ label, value, onChange, unitTypes }) => {
// Parse the current value into number and unit
const parseValue = (val) => {
const match = val?.match(/^(\d+)(.*)$/);
return match ? {
number: parseInt(match[1], 10),
unit: match[2] || 'px'
} : { number: 300, unit: 'px' };
};
const { number, unit } = parseValue(value);
// Default units
const allUnits = [
{ label: 'Pixels (px)', value: 'px' },
{ label: 'Percentage (%)', value: '%' },
{ label: 'Viewport Width (vw)', value: 'vw' },
{ label: 'Viewport Height (vh)', value: 'vh' }
];
// Filter units based on unitTypes prop or use allUnits if unitTypes is not passed
const units = unitTypes && unitTypes.length > 0
? allUnits.filter(unit => unitTypes.includes(unit.value))
: allUnits;
return (
<div className="lcp-dimension-control">
<label className="components-base-control__label">{label}</label>
<div style={{ display: 'flex', gap: '8px', alignItems: 'flex-start' }}>
<TextControl
type="number"
value={number}
onChange={(newNumber) => {
onChange(`${newNumber}${unit}`);
}}
min={0}
style={{ width: '80px' }}
/>
<SelectControl
value={unit}
options={units}
onChange={(newUnit) => {
onChange(`${number}${newUnit}`);
}}
style={{ minWidth: '100px' }}
/>
</div>
</div>
);
};
export default LCPDimensionControl;

View File

@ -0,0 +1,56 @@
import { useState } from '@wordpress/element';
import { Button, Modal, TextControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
const LCPHTMLModal= () => {
// State to control modal visibility and text value
const [isModalOpen, setIsModalOpen] = useState(false);
const [textValue, setTextValue] = useState('');
// Open modal function
const openModal = () => setIsModalOpen(true);
// Close modal function
const closeModal = () => setIsModalOpen(false);
// Handle text change
const handleTextChange = (newValue) => {
setTextValue(newValue);
};
return (
<div>
{/* Button to trigger modal */}
<Button isPrimary onClick={openModal}>
{__('Open Modal', 'lcp')}
</Button>
{/* Modal */}
{isModalOpen && (
<Modal
title={__('Text Area Modal', 'lcp')}
onRequestClose={closeModal}
className="my-modal"
>
{/* Modal content */}
<div>
<TextControl
label={__('Enter your text:', 'lcp')}
value={textValue}
onChange={handleTextChange}
placeholder={__('Type here...', 'lcp')}
style={{ width: '100%', minHeight: '100px' }}
/>
</div>
{/* Modal footer */}
<div style={{ marginTop: '16px', textAlign: 'right' }}>
<Button onClick={closeModal}>{__('Close', 'lcp')}</Button>
</div>
</Modal>
)}
</div>
);
};
export default LCPHTMLModal;

View File

@ -0,0 +1,41 @@
import { SelectControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
const ParentCellRenderer = (props) => {
if (!props.data || !props.data.ID) {
return null;
}
// Create options from all available rows except current
const options = [
{ label: __('None', 'lcp-visualize'), value: '' }
];
// Get all rows from the grid
props.api.forEachNode(node => {
if (node.data &&
node.data.ID &&
node.data.ID !== props.data.ID) {
options.push({
label: node.data.Label || node.data.ID,
value: node.data.ID
});
}
});
const handleChange = (newValue) => {
if (props.context && props.context.handleParentChange) {
props.context.handleParentChange(props.data, newValue);
}
};
return (
<SelectControl
value={props.data.Parent || ''}
options={options}
onChange={handleChange}
/>
);
};
export default ParentCellRenderer;