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
{ saveError }
) }