import { useReducer, useEffect, useState } from '@wordpress/element'; import { Button, TextControl, TextareaControl, CheckboxControl, Spinner } from '@wordpress/components'; import { DndContext, DragOverlay, pointerWithin } from '@dnd-kit/core'; import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; import apiFetch from '@wordpress/api-fetch'; import BlockPalette from '../components/BlockPalette'; import BuilderCanvas from '../components/BuilderCanvas'; import SchemaPreview from '../components/SchemaPreview'; let blockIdCounter = 0; function generateBlockId() { blockIdCounter += 1; return `block-${ blockIdCounter }`; } const initialState = { tool_name: '', description: '', required_roles: [ 'administrator' ], blocks: [], }; function reducer( state, action ) { switch ( action.type ) { case 'SET_TOOL_NAME': return { ...state, tool_name: action.payload }; case 'SET_DESCRIPTION': return { ...state, description: action.payload }; case 'SET_REQUIRED_ROLES': return { ...state, required_roles: action.payload }; case 'ADD_BLOCK': { const config = { fields: [], meta_keys: {}, taxonomies: {}, filters: {}, }; if ( 'posts' === action.payload ) { config.post_type = 'post'; } return { ...state, blocks: [ ...state.blocks, { id: generateBlockId(), block_type: action.payload, config, }, ], }; } case 'REMOVE_BLOCK': return { ...state, blocks: state.blocks.filter( ( b ) => b.id !== action.payload ), }; case 'UPDATE_BLOCK_CONFIG': return { ...state, blocks: state.blocks.map( ( b ) => b.id === action.payload.id ? { ...b, config: { ...b.config, ...action.payload.config } } : b ), }; case 'REORDER_BLOCKS': { const { oldIndex, newIndex } = action.payload; const blocks = [ ...state.blocks ]; const [ moved ] = blocks.splice( oldIndex, 1 ); blocks.splice( newIndex, 0, moved ); return { ...state, blocks }; } case 'LOAD_TOOL': return action.payload; default: return state; } } export default function ToolBuilder( { toolId, navigate } ) { const [ state, dispatch ] = useReducer( reducer, initialState ); const [ saving, setSaving ] = useState( false ); const [ saveError, setSaveError ] = useState( '' ); const [ loaded, setLoaded ] = useState( ! toolId ); const [ activeDragType, setActiveDragType ] = useState( null ); useEffect( () => { if ( toolId ) { apiFetch( { path: `wlconduit/v1/tools/${ toolId }` } ) .then( ( response ) => { const config = response.config || {}; // Add unique IDs to blocks for drag-and-drop. const blocks = ( config.blocks || [] ).map( ( b ) => ( { id: generateBlockId(), block_type: b.block_type, config: b.config || {}, } ) ); dispatch( { type: 'LOAD_TOOL', payload: { tool_name: config.tool_name || '', description: config.description || '', required_roles: config.required_roles || [ 'administrator' ], blocks, }, } ); setLoaded( true ); } ) .catch( () => { window.alert( 'Failed to load tool.' ); navigate( '/' ); } ); } }, [ toolId ] ); async function handleSave() { if ( ! state.tool_name.trim() ) { setSaveError( 'Tool name is required.' ); return; } setSaving( true ); setSaveError( '' ); const payload = { tool_name: state.tool_name, description: state.description, required_roles: state.required_roles, blocks: state.blocks.map( ( b ) => ( { block_type: b.block_type, config: b.config, } ) ), }; try { if ( toolId ) { await apiFetch( { path: `wlconduit/v1/tools/${ toolId }`, method: 'PUT', data: payload, } ); } else { await apiFetch( { path: 'wlconduit/v1/tools', method: 'POST', data: payload, } ); } navigate( '/' ); } catch ( err ) { setSaveError( err.message || 'Failed to save tool.' ); } setSaving( false ); } function handleDragStart( event ) { const { active } = event; if ( active.data.current?.type === 'palette' ) { setActiveDragType( active.data.current.blockType ); } } function handleDragEnd( event ) { const { active, over } = event; setActiveDragType( null ); // Dropping from palette onto canvas. if ( active.data.current?.type === 'palette' && over?.id === 'canvas' ) { dispatch( { type: 'ADD_BLOCK', payload: active.data.current.blockType } ); return; } // Reordering within canvas. if ( active.data.current?.type === 'canvas-block' && over?.data.current?.type === 'canvas-block' ) { const oldIndex = state.blocks.findIndex( ( b ) => b.id === active.id ); const newIndex = state.blocks.findIndex( ( b ) => b.id === over.id ); if ( oldIndex !== newIndex ) { dispatch( { type: 'REORDER_BLOCKS', payload: { oldIndex, newIndex } } ); } } } if ( ! loaded ) { return ; } const blockDefinitions = window.wlconduitData?.blocks || {}; return (

{ toolId ? 'Edit Tool' : 'Create New Tool' }

{ dispatch( { type: 'SET_TOOL_NAME', payload: val } ); setSaveError( '' ); } } /> { saveError && (

{ saveError }

) } dispatch( { type: 'SET_DESCRIPTION', payload: val } ) } />
Required Roles

Select which roles can access this tool. Administrator is always included.

{ ( window.wlconduitData?.roles || [] ).map( ( r ) => ( { const current = state.required_roles || [ 'administrator' ]; const updated = checked ? [ ...current, r.name ] : current.filter( ( role ) => role !== r.name ); dispatch( { type: 'SET_REQUIRED_ROLES', payload: updated } ); } } /> ) ) }
{ activeDragType && (
{ blockDefinitions[ activeDragType ]?.label || activeDragType }
) }
); }