Unfinished support for updating attributes

This commit is contained in:
Jeremy Rangel
2025-01-27 02:56:29 -08:00
parent 3fe81a23ff
commit a274fe32e9
9 changed files with 319 additions and 112 deletions

View File

@ -11,6 +11,10 @@
"html": false "html": false
}, },
"attributes": { "attributes": {
"selectedDataset": {
"type": "number",
"default": 1
},
"columnTypes": { "columnTypes": {
"type": "object", "type": "object",
"default": {} "default": {}
@ -39,29 +43,93 @@
"type": "number", "type": "number",
"default": 1 "default": 1
}, },
"datasets": { "gridColumnDefinitions": {
"type": "array", "type": "array",
"default": [ "default": [
{ {
"name": "Data", "name": "Data",
"columns": [
{
"field": "Department",
"dataType": "text",
"renderFormat": "text",
"sorted": "",
"width": 150,
"editable": true,
"position": 0
},
{
"field": "Budget",
"dataType": "number",
"renderFormat": "currency",
"sorted": "asc",
"width": 120,
"editable": true,
"position": 1
},
{
"field": "MeetAt",
"dataType": "date",
"renderFormat": "datetime",
"sorted": "",
"width": 150,
"editable": true,
"position": 2
}
]
},
{
"name": "Locations",
"columns": [
{
"field": "State",
"dataType": "text",
"renderFormat": "text",
"sorted": "",
"width": 150,
"editable": true,
"position": 0
},
{
"field": "Coordinates",
"dataType": "number",
"renderFormat": "number",
"sorted": "",
"width": 120,
"editable": true,
"position": 1
}
]
}
]
},
"test": {
"type": "string",
"default": ""
},
"datasets": {
"type": "array",
"default": [
{
"name": "Main",
"data": [ "data": [
{ {
"Department": "Sheriffs Office", "Department": "Sheriffs Office",
"Budget": "150", "Budget": 1,
"MeetAt": "2025-01-26T14:30:00Z", "MeetAt": "2025-01-26T14:30:00Z",
"preferredColor": "red", "preferredColor": "red",
"PostContent": "<div> </div>" "PostContent": "<div> </div>"
}, },
{ {
"Department": "Assessor", "Department": "Assessor",
"Budget": "100", "Budget": 2,
"MeetAt": "2025-01-26T14:30:00Z", "MeetAt": "2025-01-26T14:30:00Z",
"preferredColor": "#232323", "preferredColor": "#232323",
"PostContent": "<div> </div>" "PostContent": "<div> </div>"
}, },
{ {
"Department": "Treasurer", "Department": "Treasurer",
"Budget": "50", "Budget": 3,
"MeetAt": "2025-01-26T14:30:00Z", "MeetAt": "2025-01-26T14:30:00Z",
"preferredColor": "#E72323", "preferredColor": "#E72323",
"PostContent": "<div> </div>" "PostContent": "<div> </div>"

View File

@ -1 +1 @@
<?php return array('dependencies' => array('react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => '5afb8b4f337b11cc4837'); <?php return array('dependencies' => array('react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '7a75b5732cbc1928c7a2');

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,10 @@
"html": false "html": false
}, },
"attributes": { "attributes": {
"selectedDataset": {
"type": "number",
"default": 1
},
"columnTypes": { "columnTypes": {
"type": "object", "type": "object",
"default": { "default": {
@ -41,14 +45,79 @@
"type": "number", "type": "number",
"default": 1 "default": 1
}, },
"gridColumnDefinitions": {
"type": "array",
"default": [
{
"name": "Data",
"columns": [
{
"field": "Department",
"dataType": "text",
"renderFormat": "text",
"sorted": "",
"width": 150,
"editable": true,
"position": 0
},
{
"field": "Budget",
"dataType": "number",
"renderFormat": "currency",
"sorted": "asc",
"width": 120,
"editable": true,
"position": 1
},
{
"field": "MeetAt",
"dataType": "date",
"renderFormat": "datetime",
"sorted": "",
"width": 150,
"editable": true,
"position": 2
}
]
},
{
"name": "Locations",
"columns": [
{
"field": "State",
"dataType": "text",
"renderFormat": "text",
"sorted": "",
"width": 150,
"editable": true,
"position": 0
},
{
"field": "Coordinates",
"dataType": "number",
"renderFormat": "number",
"sorted": "",
"width": 120,
"editable": true,
"position": 1
}
]
}
]
},
"test": {
"type": "string",
"default": ""
},
"datasets": { "datasets": {
"type": "array", "type": "array",
"default": [{ "default": [{
"name": "Data", "name": "Main",
"data": [ "data": [
{ "Department": "Sheriffs Office", "Budget": "150", "MeetAt": "2025-01-26T14:30:00Z", "preferredColor": "red", "PostContent": "<div> </div>" }, { "Department": "Sheriffs Office", "Budget": 1, "MeetAt": "2025-01-26T14:30:00Z", "preferredColor": "red", "PostContent": "<div> </div>" },
{ "Department": "Assessor", "Budget": "100", "MeetAt": "2025-01-26T14:30:00Z", "preferredColor": "#232323", "PostContent": "<div> </div>" }, { "Department": "Assessor", "Budget": 2, "MeetAt": "2025-01-26T14:30:00Z", "preferredColor": "#232323", "PostContent": "<div> </div>" },
{ "Department": "Treasurer", "Budget": "50", "MeetAt": "2025-01-26T14:30:00Z", "preferredColor": "#E72323", "PostContent": "<div> </div>" } { "Department": "Treasurer", "Budget": 3, "MeetAt": "2025-01-26T14:30:00Z", "preferredColor": "#E72323", "PostContent": "<div> </div>" }
] ]
}, },
{ {

View File

@ -1,6 +1,6 @@
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { useBlockProps, InspectorControls } from '@wordpress/block-editor'; import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
import { ToggleControl, TextControl,PanelBody, ColorPicker, SelectControl, __experimentalNumberControl as NumberControl} from '@wordpress/components'; import { ToggleControl, TextControl, PanelBody, ColorPicker, SelectControl, __experimentalNumberControl as NumberControl } from '@wordpress/components';
import './editor.scss'; import './editor.scss';
import BarGraph from './components/BarGraph'; import BarGraph from './components/BarGraph';
import LCPDatasetBuilder from '../../components/LCPDatasetBuilder'; import LCPDatasetBuilder from '../../components/LCPDatasetBuilder';
@ -8,23 +8,18 @@ import LCPDimensionControl from '../../components/LCPDimensionControl';
import LCPChartBlockSettings from '../../components/LCPChartBlockSettings'; import LCPChartBlockSettings from '../../components/LCPChartBlockSettings';
import LCPLegend from '../../components/LCPLegend'; import LCPLegend from '../../components/LCPLegend';
export default function Edit({ attributes, setAttributes }) { export default function Edit({ attributes, setAttributes }) {
const items = [ const items = [
{ Label: "Category 1", color: "#ff0000" }, { Label: "Category 1", color: "#ff0000" },
{ Label: "Category 2", color: "#00ff00" }, { Label: "Category 2", color: "#00ff00" },
{ Label: "Category 1", color: "#ff0000" }, { Label: "Category 1", color: "#ff0000" },
{ Label: "Categokhkjhkjhkjhky 2", color: "#00ff00" }, { Label: "Category 2", color: "#00ff00" },
{ Label: "Category 1", color: "#ff0000" }, { Label: "Category 1", color: "#ff0000" },
{ Label: "Category 2", color: "#00ff00" }, { Label: "Category 2", color: "#00ff00" },
{ Label: "Category 1", color: "#ff0000" }, { Label: "Category 1", color: "#ff0000" },
{ Label: "Cat 2", color: "#00ff00" }, { Label: "Category 2", color: "#00ff00" },
{ Label: "Coy 1", color: "#ff0000" },
]; ];
const { const {
@ -45,18 +40,12 @@ export default function Edit({ attributes, setAttributes }) {
renderLegend = false, renderLegend = false,
toolbarLocation = 'top', toolbarLocation = 'top',
enableGroupedBars = false, enableGroupedBars = false,
groupedBarsColumn = '' groupedBarsColumn = '',
datasets
} = attributes; } = attributes;
const blockProps = useBlockProps(); const blockProps = useBlockProps();
const handleDatasetChange = (newData) => {
setAttributes({ chartData: newData });
};
return ( return (
<div {...blockProps}> <div {...blockProps}>
<InspectorControls> <InspectorControls>
@ -64,10 +53,10 @@ export default function Edit({ attributes, setAttributes }) {
<PanelBody title={__('Chart Settings', 'lcp')}> <PanelBody title={__('Chart Settings', 'lcp')}>
<LCPDatasetBuilder <LCPDatasetBuilder
chartType="bar" chartType="bar"
chartData={chartData} datasets={attributes.datasets} // Ensure datasets are passed to the component
onChange={handleDatasetChange} attributes={attributes} // Pass the full attributes
attributes={attributes} setAttributes={setAttributes} // Pass setAttributes to LCPDatasetBuilder
setAttributes={setAttributes}
/> />
{/* Select the source of the hierarchies */} {/* Select the source of the hierarchies */}
<SelectControl <SelectControl
@ -108,29 +97,27 @@ export default function Edit({ attributes, setAttributes }) {
value={chartWidth} value={chartWidth}
onChange={(value) => setAttributes({ chartWidth: value })} onChange={(value) => setAttributes({ chartWidth: value })}
/> />
</PanelBody> </PanelBody>
{/* Color Settings */} {/* Color Settings */}
<PanelBody title={__('Color Settings', 'lcp-visualize')} initialOpen={true}> <PanelBody title={__('Color Settings', 'lcp-visualize')} initialOpen={true}>
{/* Color Settings */}
{/* Select how the colors will be chosen */}
<SelectControl <SelectControl
label={__('Color Source', 'lcp')} label={__('Color Source', 'lcp')}
help={__('Select the logic for setting the colors', 'lcp')} help={__('Select the logic for setting the colors', 'lcp')}
value={barsColorSource || 'default'} value={barsColorSource || 'default'}
options={[ options={[
{ label: 'Default', value: 'default' }, // Use the default color logic, value: 'option1' }, { label: 'Default', value: 'default' },
{ label: 'Swatch', value: 'swatch' } { label: 'Swatch', value: 'swatch' }
]} ]}
onChange={(value) => setAttributes({ barsColorSource: value })} onChange={(value) => setAttributes({ barsColorSource: value })}
/> />
</PanelBody> </PanelBody>
{/* Common Chart Settings */} {/* Common Chart Settings */}
<LCPChartBlockSettings <LCPChartBlockSettings
attributes={attributes} attributes={attributes}
setAttributes={setAttributes} setAttributes={setAttributes}
/> />
</InspectorControls> </InspectorControls>
<div className="lcp-bar-graph-container"> <div className="lcp-bar-graph-container">
@ -165,7 +152,6 @@ export default function Edit({ attributes, setAttributes }) {
</div> </div>
)} )}
</div> </div>
</div> </div>
); );
} }

View File

@ -3,9 +3,40 @@ import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css'; import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css'; import 'ag-grid-community/styles/ag-theme-alpine.css';
import LCPDataGridHeader from './LCPDataGridHeader';
import LCPGridColorRender from './LCPGridColorRender'; import LCPGridColorRender from './LCPGridColorRender';
const LCPDataGrid = ({dataset}) => { const LCPDataGrid = ({ dataset, index, updateDataset }) => {
const onCellValueChanged = (event) => {
// console.log('Cell value changed. Grid index:', index);
// Access the grid API
const gridApi = gridRef.current.api;
// Fetch all the data in the grid after the change
const allRowData = [];
gridApi.forEachNode(node => allRowData.push(node.data));
// Log the entire row data
//console.log('All Grid Data:', allRowData);
// Optionally, log the updated row data (just the changed row)
// console.log('Updated Row Data:', event.data);
// Optionally, log the updated cell value
// console.log('Updated Cell Value:', event.newValue);
// Create a fresh copy of the dataset and update its data
const updatedDataset = { data: allRowData };
console.log(updatedDataset.data);
// Send the updated data back to the parent (LCPDatasetBuilder) through updateDataset
updateDataset(index, updatedDataset); // Call the parent function to update the dataset
};
// lcpCellRenderer to dynamically assign the right cellRenderer // lcpCellRenderer to dynamically assign the right cellRenderer
function lcpCellRenderer(params) { function lcpCellRenderer(params) {
@ -66,6 +97,10 @@ function lcpCellRenderer(params) {
const isValidCSSColor = (value) => { const isValidCSSColor = (value) => {
// Ensure the value is a string before trimming // Ensure the value is a string before trimming
value = String(value).trim(); // Convert to string if it's not already value = String(value).trim(); // Convert to string if it's not already
// If the value is a Date object, it should not be processed as a CSS color
if (value instanceof Date) {
return false; // Return false if it's a Date object
}
// Regex to match valid CSS color formats (hex, rgb, rgba, hsl, hsla, named colors) // Regex to match valid CSS color formats (hex, rgb, rgba, hsl, hsla, named colors)
const cssColorRegex = /^(#([0-9a-fA-F]{3}){1,2}|[a-zA-Z]+|rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)|rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(0(\.\d+)?|1(\.0+)?)\)|hsl\((\d{1,3}),\s*(\d{1,3})%,\s*(\d{1,3})%\)|hsla\((\d{1,3}),\s*(\d{1,3})%,\s*(\d{1,3})%,\s*(0(\.\d+)?|1(\.0+)?)\))$/i; const cssColorRegex = /^(#([0-9a-fA-F]{3}){1,2}|[a-zA-Z]+|rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)|rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(0(\.\d+)?|1(\.0+)?)\)|hsl\((\d{1,3}),\s*(\d{1,3})%,\s*(\d{1,3})%\)|hsla\((\d{1,3}),\s*(\d{1,3})%,\s*(\d{1,3})%,\s*(0(\.\d+)?|1(\.0+)?)\))$/i;
@ -215,13 +250,21 @@ const isValidCSSColor = (value) => {
// If it's the pinned top row, return the value as-is // If it's the pinned top row, return the value as-is
return params.newValue; return params.newValue;
} else { } else {
// Check if the value is a Date object first
let value = params.newValue;
if (value instanceof Date) {
// If it's a Date object, return the string representation (this could be customized if needed)
return value.toLocaleString(); // or use `toISOString()` if you prefer
}
// Process based on the detected data type // Process based on the detected data type
switch (dataType) { switch (dataType) {
case 'number': case 'number':
// If it's a number, return the number or NaN // If it's a number, return the number or NaN
return Number(params.newValue); return Number(value);
case 'date': case 'date':
const dateStr = params.newValue; const dateStr = value;
// Check if the value is an empty string or undefined // Check if the value is an empty string or undefined
if (!dateStr || dateStr.trim() === '') { if (!dateStr || dateStr.trim() === '') {
@ -245,13 +288,13 @@ const isValidCSSColor = (value) => {
return !isNaN(parsedDate.getTime()) ? parsedDate : dateStr; return !isNaN(parsedDate.getTime()) ? parsedDate : dateStr;
case 'color': case 'color':
// If it's a color, return the value as-is // If it's a color, return the value as-is
return params.newValue; return value;
case 'html': case 'html':
// If it's HTML, we can either sanitize or leave the value as is (for now, leaving it as is) // If it's HTML, we can either sanitize or leave the value as is (for now, leaving it as is)
return params.newValue; return value;
default: default:
// If it's text (or any other type), return the value as-is // If it's text (or any other type), return the value as-is
return params.newValue; return value;
} }
} }
} }
@ -259,6 +302,7 @@ const isValidCSSColor = (value) => {
// Create columnDefs dynamically // Create columnDefs dynamically
const [columnDefs, setColumnDefs] = useState([]); const [columnDefs, setColumnDefs] = useState([]);
const [pinnedTopRowData, setPinnedTopRowData] = useState([]); const [pinnedTopRowData, setPinnedTopRowData] = useState([]);
@ -275,7 +319,7 @@ const isValidCSSColor = (value) => {
field: key, // Field will match the key (Department2, Budget, etc.) field: key, // Field will match the key (Department2, Budget, etc.)
valueParser: lcpDataTypeParser, valueParser: lcpDataTypeParser,
cellRenderer: lcpCellRenderer, // Reference the custom cell renderer cellRenderer: lcpCellRenderer, // Reference the custom cell renderer
headerComponent: LCPDataGridHeader
}; };
}); });
@ -378,6 +422,8 @@ const isValidCSSColor = (value) => {
onSortChanged={onSortChanged} onSortChanged={onSortChanged}
onFilterChanged={onFilterChanged} onFilterChanged={onFilterChanged}
onGridReady={onGridReady} onGridReady={onGridReady}
onCellValueChanged={onCellValueChanged}
/> />
</div> </div>

View File

@ -1,67 +1,46 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import {Icon, calendar } from '@wordpress/icons'; // Make sure to use the correct import
const LCPDataGridHeader = (props) => { const LCPDataGridHeader = (props) => {
const { displayName, column, updateData, sort, menu } = props; const { displayName, column, updateData, sort, menu } = props;
const [editing, setEditing] = useState(false);
const [newHeader, setNewHeader] = useState(displayName);
const colId = column.colId; const colId = column.colId;
const handleEditClick = () => {
setEditing(true);
};
const handleBlur = () => { // Sorting function
setEditing(false); const handleSort = () => {
if (updateData && typeof updateData === 'function') { if (sort === 'asc') {
updateData(newHeader); // Save the new header name updateData(colId, 'desc'); // Assuming updateData is a function to set sort direction
} else {
updateData(colId, 'asc');
} }
}; };
const handleChange = (e) => {
setNewHeader(e.target.value);
};
return ( return (
<div className="custom-header"> <div className="lcp-data-grid-column-header" style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}>
{/* Calendar Icon */}
<span style={{ marginRight: '8px', display: 'inline-block' }}>
<Icon icon={ calendar } />
{/* Column ID
<div>
<span style={{ marginRight: '8px' }}>
<strong>{colId}</strong>
</span> </span>
</div>*/}
{/* Editable Header Text */} {/* Editable Header Text */}
{/* Icon */}
<span style={{ marginRight: '8px' }}>
<img src="your-icon-path.svg" alt="icon" style={{ width: '20px', height: '20px' }} />
</span>
{editing ? (
<input
type="text"
value={newHeader}
onBlur={handleBlur}
onChange={handleChange}
style={{ fontSize: '14px', padding: '2px 5px', width: '100%' }}
/>
) : (
<span <span
onClick={handleEditClick} style={{ fontWeight: 'bold', fontSize: '16px' }}
style={{ cursor: 'pointer', fontWeight: 'bold', fontSize: '16px' }} onClick={handleSort} // Click header for sorting
> >
{newHeader} {displayName}
</span> </span>
)}
{/* Sorting * {/* Sorting Indicators */}
<span style={{ marginLeft: '8px' }}> <span style={{ marginLeft: '8px' }}>
{sort === 'asc' && <span></span>} {sort === 'asc' && <span></span>}
{sort === 'desc' && <span></span>} {sort === 'desc' && <span></span>}
</span> */} </span>
{/* Additional Menu * {/* Additional Menu */}
<span style={{ marginLeft: '8px' }}> <span style={{ marginLeft: '8px' }}>
<button onClick={menu}>Menu</button> <button onClick={menu}>Menu</button>
</span> */} </span>
</div> </div>
); );
}; };

View File

@ -3,9 +3,36 @@ import { Button, Modal } from '@wordpress/components';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import LCPDataGrid from './LCPDataGrid'; import LCPDataGrid from './LCPDataGrid';
const LCPDatasetBuilder = ({ attributes }) => { const LCPDatasetBuilder = ({ attributes, setAttributes }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [activeTab, setActiveTab] = useState(0); // Track the active tab const [activeTab, setActiveTab] = useState(0); // Track the active tab
const handleUpdateDataset = (index, newData) => {
// Log the newData in a readable format
console.log("newData (formatted for copy-pasting):", JSON.stringify(newData, null, 2));
// Make a shallow copy of the datasets to avoid mutation
const datasets = [...attributes.datasets];
// Ensure the data structure is correct before replacing
if (newData && newData.data) {
// Replace the data array at the specified index with the new data
datasets[0].data = newData.data;
// Log the datasets before the update
console.log("Before Update:", JSON.stringify(datasets, null, 2));
// Update the attributes with the modified datasets
setAttributes({
datasets: datasets
});
// Log the updated attributes (you can check this after re-rendering)
console.log("After Update:", JSON.stringify(datasets, null, 2));
} else {
console.error("Invalid data format:", newData);
}
};
return ( return (
<> <>
@ -56,7 +83,11 @@ const LCPDatasetBuilder = ({ attributes }) => {
transition: 'display 0.3s ease', transition: 'display 0.3s ease',
}} }}
> >
<LCPDataGrid dataset={dataset.data} /> <LCPDataGrid
dataset={dataset.data} // Pass the specific dataset data (only the data, not the entire dataset)
index={index}
updateDataset={handleUpdateDataset} // Function defined outside of class context
/>
</div> </div>
))} ))}
</div> </div>

View File

@ -1,8 +1,10 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Popover, ColorPicker } from '@wordpress/components';
// AG-Grid Cell Renderer Component // AG-Grid Cell Renderer Component
const LCPGridColorRender = (props) => { const LCPGridColorRender = (props) => {
const [color, setColor] = useState(props.value || ''); // Get the color value from the cell data const [color, setColor] = useState(props.value || ''); // Get the color value from the cell data
const [isPopoverVisible, setPopoverVisible] = useState(false); // To toggle the visibility of the Popover
useEffect(() => { useEffect(() => {
// Update color if the value from AG-Grid changes // Update color if the value from AG-Grid changes
@ -11,18 +13,44 @@ const LCPGridColorRender = (props) => {
} }
}, [props.value]); // Dependency on props.value so it updates when the cell value changes }, [props.value]); // Dependency on props.value so it updates when the cell value changes
// Handle the color change from the color picker
const handleColorChange = (newColor) => {
setColor(newColor);
// Optionally, update the cell value in AG-Grid here
if (props.setValue) {
props.setValue(newColor);
}
};
return ( return (
<div>
{/* Color square */}
<div <div
onClick={() => setPopoverVisible(!isPopoverVisible)} // Toggle popover visibility on click
style={{ style={{
width: '30px', width: '30px',
height: '30px', height: '30px',
backgroundColor: color, backgroundColor: color,
border: '1px solid #000', border: '1px solid #000',
margin: '0 auto' margin: '0 auto',
cursor: 'pointer',
}} }}
/> />
{/* Popover for color picker */}
{isPopoverVisible && (
<Popover
position="bottom center"
onClose={() => setPopoverVisible(false)} // Close popover when clicking outside
>
<ColorPicker
color={color}
onChangeComplete={(value) => handleColorChange(value.hex)} // Update color on change
/>
</Popover>
)}
</div>
); );
}; };
export default LCPGridColorRender; export default LCPGridColorRender;