UNPKG

testeranto

Version:

the AI powered BDD test framework for typescript projects

283 lines (282 loc) 14 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unused-vars */ import React, { useState, useCallback } from "react"; import { GenericXMLEditorPage } from "./GenericXMLEditorPage"; export const DratoPage = () => { const initialTree = { id: "root", type: "Container", attributes: { fluid: "true" }, children: [], }; const [bootstrapTree, setBootstrapTree] = useState(initialTree); const [selectedNodeId, setSelectedNodeId] = useState(null); const [activeTab, setActiveTab] = useState("preview"); const [history, setHistory] = useState([initialTree]); const [historyIndex, setHistoryIndex] = useState(0); const [hiddenNodes, setHiddenNodes] = useState(new Set()); // Find a node by ID const findNode = useCallback((node, id) => { if (node.id === id) return node; for (const child of node.children) { const found = findNode(child, id); if (found) return found; } return null; }, []); // Helper to update tree and manage history const updateTree = useCallback((newTree) => { setBootstrapTree(newTree); setHistory((prevHistory) => { const newHistory = prevHistory.slice(0, historyIndex + 1); newHistory.push(newTree); return newHistory; }); setHistoryIndex((prevIndex) => prevIndex + 1); }, [historyIndex]); // Update node attributes const updateNodeAttributes = useCallback((nodeId, attributes) => { setBootstrapTree((prevTree) => { const newTree = JSON.parse(JSON.stringify(prevTree)); const node = findNode(newTree, nodeId); if (node) { node.attributes = Object.assign(Object.assign({}, node.attributes), attributes); } updateTree(newTree); return newTree; }); }, [findNode, updateTree]); // Update node text content const updateNodeTextContent = useCallback((nodeId, textContent) => { setBootstrapTree((prevTree) => { const newTree = JSON.parse(JSON.stringify(prevTree)); const node = findNode(newTree, nodeId); if (node) { node.textContent = textContent; } updateTree(newTree); return newTree; }); }, [findNode, updateTree]); // Toggle node visibility const toggleNodeVisibility = useCallback((nodeId) => { setHiddenNodes((prev) => { const newHidden = new Set(prev); if (newHidden.has(nodeId)) { newHidden.delete(nodeId); } else { newHidden.add(nodeId); } return newHidden; }); }, []); // Add a new child node const addChildNode = useCallback((parentId, nodeType) => { setBootstrapTree((prevTree) => { const newTree = JSON.parse(JSON.stringify(prevTree)); const parent = findNode(newTree, parentId); if (parent) { // Default attributes based on node type let defaultAttributes = {}; switch (nodeType) { case "Container": defaultAttributes = { fluid: "true" }; break; case "Row": defaultAttributes = {}; break; case "Col": defaultAttributes = { xs: "12" }; break; case "Button": defaultAttributes = { variant: "primary" }; break; case "Card": defaultAttributes = {}; break; default: defaultAttributes = {}; } const newNode = { id: `${nodeType}-${Date.now()}`, type: nodeType, attributes: defaultAttributes, children: [], }; parent.children = [...parent.children, newNode]; setSelectedNodeId(newNode.id); updateTree(newTree); } return newTree; }); }, [findNode, updateTree]); // Remove a node const removeNode = useCallback((nodeId) => { setBootstrapTree((prevTree) => { const newTree = JSON.parse(JSON.stringify(prevTree)); const removeFromParent = (node, targetId) => { for (let i = 0; i < node.children.length; i++) { if (node.children[i].id === targetId) { node.children.splice(i, 1); return true; } if (removeFromParent(node.children[i], targetId)) { return true; } } return false; }; removeFromParent(newTree, nodeId); if (selectedNodeId === nodeId) { setSelectedNodeId(null); } updateTree(newTree); return newTree; }); }, [selectedNodeId, updateTree]); // Undo/Redo handlers const handleUndo = useCallback(() => { if (historyIndex > 0) { setHistoryIndex((prevIndex) => prevIndex - 1); setBootstrapTree(history[historyIndex - 1]); } }, [history, historyIndex]); const handleRedo = useCallback(() => { if (historyIndex < history.length - 1) { setHistoryIndex((prevIndex) => prevIndex + 1); setBootstrapTree(history[historyIndex + 1]); } }, [history, historyIndex]); const selectedNode = selectedNodeId ? findNode(bootstrapTree, selectedNodeId) : null; // Define node types for Bootstrap const bootstrapNodeTypes = [ { label: 'Container', type: 'Container' }, { label: 'Row', type: 'Row' }, { label: 'Column', type: 'Col' }, { label: 'Button', type: 'Button' }, { label: 'Card', type: 'Card' }, { label: 'Text', type: 'Text' } ]; // Function to create new Bootstrap nodes const createBootstrapNode = (parentId, nodeType) => { // Default attributes based on node type let defaultAttributes = {}; switch (nodeType) { case "Container": defaultAttributes = { fluid: "true" }; break; case "Row": defaultAttributes = {}; break; case "Col": defaultAttributes = { xs: "12" }; break; case "Button": defaultAttributes = { variant: "primary" }; break; case "Card": defaultAttributes = {}; break; case "Text": defaultAttributes = { class: "" }; break; default: defaultAttributes = {}; } return { id: `${nodeType}-${Date.now()}`, type: nodeType, attributes: defaultAttributes, children: [], }; }; // Render preview for Bootstrap const renderBootstrapPreview = (node, isSelected, eventHandlers) => { // Render Bootstrap components based on node type const { type, attributes, children, textContent } = node; const style = isSelected ? { outline: '2px dashed blue' } : {}; // Convert children to React elements const childElements = children.map(child => renderBootstrapPreview(child, selectedNodeId === child.id, {})); // Handle different Bootstrap component types switch (type) { case 'Container': return (React.createElement("div", Object.assign({ key: node.id, className: `container${attributes.fluid === 'true' ? '-fluid' : ''}`, style: style }, eventHandlers), childElements, textContent)); case 'Row': return (React.createElement("div", Object.assign({ key: node.id, className: "row", style: style }, eventHandlers), childElements, textContent)); case 'Col': // Generate column classes based on attributes const colClasses = Object.entries(attributes) .filter(([key]) => ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].includes(key)) .map(([breakpoint, size]) => `col-${breakpoint}-${size}`) .join(' '); return (React.createElement("div", Object.assign({ key: node.id, className: colClasses || 'col', style: style }, eventHandlers), childElements, textContent)); case 'Button': return (React.createElement("button", Object.assign({ key: node.id, className: `btn btn-${attributes.variant || 'primary'}`, style: style }, eventHandlers), textContent || 'Button')); case 'Card': return (React.createElement("div", Object.assign({ key: node.id, className: "card", style: style }, eventHandlers), React.createElement("div", { className: "card-body" }, childElements, textContent))); case 'Text': return (React.createElement("div", Object.assign({ key: node.id, className: attributes.class || '', style: style }, eventHandlers), textContent, childElements)); default: return (React.createElement("div", Object.assign({ key: node.id, style: style }, eventHandlers), type, " element", childElements, textContent)); } }; // Attribute editor for Bootstrap const renderBootstrapAttributeEditor = (node, onUpdateAttributes, onUpdateTextContent) => { const handleAttributeChange = (key, value) => { const newAttributes = Object.assign(Object.assign({}, node.attributes), { [key]: value }); onUpdateAttributes(newAttributes); }; const handleTextChange = (e) => { onUpdateTextContent(e.target.value); }; // Render different attribute editors based on node type return (React.createElement("div", null, (node.type === 'Button' || node.type === 'Card' || node.type === 'Text') && (React.createElement("div", { className: "mb-3" }, React.createElement("label", { className: "form-label" }, "Text Content"), React.createElement("input", { type: "text", className: "form-control", value: node.textContent || '', onChange: handleTextChange }))), node.type === 'Container' && (React.createElement("div", { className: "mb-3" }, React.createElement("label", { className: "form-label" }, "Fluid"), React.createElement("select", { className: "form-select", value: node.attributes.fluid || 'false', onChange: (e) => handleAttributeChange('fluid', e.target.value) }, React.createElement("option", { value: "true" }, "True"), React.createElement("option", { value: "false" }, "False")))), node.type === 'Col' && (React.createElement("div", null, ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].map(breakpoint => (React.createElement("div", { key: breakpoint, className: "mb-2" }, React.createElement("label", { className: "form-label" }, breakpoint.toUpperCase()), React.createElement("input", { type: "text", className: "form-control", value: node.attributes[breakpoint] || '', onChange: (e) => handleAttributeChange(breakpoint, e.target.value), placeholder: "1-12" })))))), node.type === 'Button' && (React.createElement("div", { className: "mb-3" }, React.createElement("label", { className: "form-label" }, "Variant"), React.createElement("select", { className: "form-select", value: node.attributes.variant || 'primary', onChange: (e) => handleAttributeChange('variant', e.target.value) }, ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark'].map(variant => (React.createElement("option", { key: variant, value: variant }, variant)))))), node.type === 'Text' && (React.createElement("div", { className: "mb-3" }, React.createElement("label", { className: "form-label" }, "CSS Classes"), React.createElement("input", { type: "text", className: "form-control", value: node.attributes.class || '', onChange: (e) => handleAttributeChange('class', e.target.value), placeholder: "e.g., text-primary fs-4" }))), React.createElement("div", { className: "mb-3" }, React.createElement("label", { className: "form-label" }, "Other Attributes"), Object.entries(node.attributes) .filter(([key]) => !['fluid', 'variant'].includes(key) && !['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].includes(key)) .map(([key, value]) => (React.createElement("div", { key: key, className: "input-group mb-2" }, React.createElement("span", { className: "input-group-text" }, key), React.createElement("input", { type: "text", className: "form-control", value: value, onChange: (e) => handleAttributeChange(key, e.target.value) }))))))); }; return (React.createElement(GenericXMLEditorPage, { initialTree: initialTree, renderPreview: renderBootstrapPreview, attributeEditor: renderBootstrapAttributeEditor, nodeTypes: bootstrapNodeTypes, onAddNode: createBootstrapNode })); };