testeranto
Version:
the AI powered BDD test framework for typescript projects
438 lines (437 loc) • 18.2 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SVGEditorPage = void 0;
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
const react_1 = __importStar(require("react"));
const SVGAttributesEditor_1 = require("./SVGEditor/SVGAttributesEditor");
const SVGElementForm_1 = require("./SVGEditor/SVGElementForm");
const GenericXMLEditorPage_1 = require("./GenericXMLEditorPage");
const SVGPreview_1 = require("./SVGEditor/SVGPreview");
const SVGEditorPage = () => {
const initialTree = {
id: "root",
type: "svg",
attributes: {
width: "400",
height: "400",
viewBox: "0 0 400 400",
xmlns: "http://www.w3.org/2000/svg",
},
children: [
{
id: "rect-1",
type: "rect",
attributes: {
x: "50",
y: "50",
width: "100",
height: "100",
fill: "blue",
stroke: "black",
"stroke-width": "2",
},
children: [],
}
],
};
const [svgTree, setSvgTree] = (0, react_1.useState)(initialTree);
const [selectedNodeId, setSelectedNodeId] = (0, react_1.useState)(null);
const [activeTab, setActiveTab] = (0, react_1.useState)("preview");
const [history, setHistory] = (0, react_1.useState)([initialTree]);
const [historyIndex, setHistoryIndex] = (0, react_1.useState)(0);
const [editMode, setEditMode] = (0, react_1.useState)("none");
const [showGrid, setShowGrid] = (0, react_1.useState)(false);
const [snapToGrid, setSnapToGrid] = (0, react_1.useState)(false);
const [hiddenNodes, setHiddenNodes] = (0, react_1.useState)(new Set());
const [dragInfo, setDragInfo] = (0, react_1.useState)({
isDragging: false,
nodeId: null,
});
// Find a node by ID
const findNode = (0, react_1.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 = (0, react_1.useCallback)((newTree) => {
setSvgTree(newTree);
// Add to history, truncating future history if we're not at the end
setHistory((prevHistory) => {
const newHistory = prevHistory.slice(0, historyIndex + 1);
newHistory.push(newTree);
return newHistory;
});
setHistoryIndex((prevIndex) => prevIndex + 1);
}, [historyIndex]);
// Update node attributes
const updateNodeAttributes = (0, react_1.useCallback)((nodeId, attributes) => {
setSvgTree((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 = (0, react_1.useCallback)((nodeId, textContent) => {
setSvgTree((prevTree) => {
const newTree = JSON.parse(JSON.stringify(prevTree));
const node = findNode(newTree, nodeId);
if (node) {
// For text and tspan nodes, we need to handle text content
// This is a simplified approach - in a real implementation, you'd track text content separately
// For now, we'll add a special attribute to track the text content
node.attributes['data-text-content'] = textContent;
}
updateTree(newTree);
return newTree;
});
}, [findNode, updateTree]);
// Toggle node visibility
const toggleNodeVisibility = (0, react_1.useCallback)((nodeId) => {
setHiddenNodes((prev) => {
const newHidden = new Set(prev);
if (newHidden.has(nodeId)) {
newHidden.delete(nodeId);
}
else {
newHidden.add(nodeId);
}
return newHidden;
});
}, []);
// Handle drag start
const handleDragStart = (0, react_1.useCallback)((nodeId) => {
setDragInfo({ isDragging: true, nodeId });
}, []);
// Handle drag end
const handleDragEnd = (0, react_1.useCallback)(() => {
setDragInfo({ isDragging: false, nodeId: null });
}, []);
// Move a node
const moveNode = (0, react_1.useCallback)((nodeId, newParentId, index) => {
setSvgTree((prevTree) => {
const newTree = JSON.parse(JSON.stringify(prevTree));
// Find the node to move and its original parent
let nodeToMove = null;
let oldParent = null;
const findNodeAndParent = (current, parent = null) => {
if (current.id === nodeId) {
nodeToMove = current;
oldParent = parent;
return true;
}
for (const child of current.children) {
if (findNodeAndParent(child, current))
return true;
}
return false;
};
findNodeAndParent(newTree);
if (!nodeToMove || !oldParent)
return prevTree;
// Remove from old parent
oldParent.children = oldParent.children.filter((child) => child.id !== nodeId);
// Find new parent
let targetParent = null;
if (newParentId === null) {
targetParent = newTree;
}
else {
const findParent = (current) => {
if (current.id === newParentId)
return current;
for (const child of current.children) {
const found = findParent(child);
if (found)
return found;
}
return null;
};
targetParent = findParent(newTree);
}
if (!targetParent)
return prevTree;
// Insert at specified index
targetParent.children.splice(index, 0, nodeToMove);
updateTree(newTree);
return newTree;
});
}, [updateTree]);
// Add a new child node
const addChildNode = (0, react_1.useCallback)((parentId, nodeType) => {
setSvgTree((prevTree) => {
const newTree = JSON.parse(JSON.stringify(prevTree));
const parent = findNode(newTree, parentId);
if (parent) {
// Default attributes based on node type to make them visible
let defaultAttributes = {};
switch (nodeType) {
case "rect":
defaultAttributes = {
x: "50",
y: "50",
width: "100",
height: "100",
fill: "blue",
stroke: "black",
"stroke-width": "2",
};
break;
case "circle":
defaultAttributes = {
cx: "100",
cy: "100",
r: "50",
fill: "red",
stroke: "black",
"stroke-width": "2",
};
break;
case "path":
defaultAttributes = {
d: "M50 50 L150 150",
stroke: "green",
"stroke-width": "3",
fill: "none",
};
break;
case "text":
defaultAttributes = {
x: "50",
y: "50",
fill: "black",
"font-size": "16",
};
break;
case "g":
defaultAttributes = {
transform: "translate(0,0)",
};
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 = (0, react_1.useCallback)((nodeId) => {
setSvgTree((prevTree) => {
const newTree = JSON.parse(JSON.stringify(prevTree));
// Function to remove node from its parent's children
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 = (0, react_1.useCallback)(() => {
if (historyIndex > 0) {
setHistoryIndex((prevIndex) => prevIndex - 1);
setSvgTree(history[historyIndex - 1]);
}
}, [history, historyIndex]);
const handleRedo = (0, react_1.useCallback)(() => {
if (historyIndex < history.length - 1) {
setHistoryIndex((prevIndex) => prevIndex + 1);
setSvgTree(history[historyIndex + 1]);
}
}, [history, historyIndex]);
// Move handler - for now, just log to console
const handleMove = (0, react_1.useCallback)(() => {
console.log("Move functionality to be implemented");
// This would typically involve setting up a mode where nodes can be dragged
}, []);
// Helper variables for edit modes
const isMoveMode = editMode === "move";
const isScaleMode = editMode === "scale";
const isRotateMode = editMode === "rotate";
const selectedNode = selectedNodeId
? findNode(svgTree, selectedNodeId)
: null;
// Define node types for SVG
const svgNodeTypes = [
{ label: 'Rectangle', type: 'rect' },
{ label: 'Circle', type: 'circle' },
{ label: 'Path', type: 'path' },
{ label: 'Text', type: 'text' },
{ label: 'Group', type: 'g' }
];
// Function to create new SVG nodes
const createSVGNode = (parentId, nodeType) => {
// Default attributes based on node type
let defaultAttributes = {};
switch (nodeType) {
case "rect":
defaultAttributes = {
x: "50",
y: "50",
width: "100",
height: "100",
fill: "blue",
stroke: "black",
"stroke-width": "2",
};
break;
case "circle":
defaultAttributes = {
cx: "100",
cy: "100",
r: "50",
fill: "red",
stroke: "black",
"stroke-width": "2",
};
break;
case "path":
defaultAttributes = {
d: "M50 50 L150 150",
stroke: "green",
"stroke-width": "3",
fill: "none",
};
break;
case "text":
defaultAttributes = {
x: "50",
y: "50",
fill: "black",
"font-size": "16",
};
break;
case "g":
defaultAttributes = {
transform: "translate(0,0)",
};
break;
default:
defaultAttributes = {};
}
return {
id: `${nodeType}-${Date.now()}`,
type: nodeType,
attributes: defaultAttributes,
children: [],
};
};
// Render preview for SVG
const renderSVGPreview = (node, isSelected, eventHandlers) => {
const { type, attributes, children, textContent } = node;
const style = isSelected ? { outline: '2px dashed blue' } : {};
// Convert children to React elements
const childElements = children.map(child => renderSVGPreview(child, selectedNodeId === child.id, {}));
// Handle text content for text elements
if (type === 'text' || type === 'tspan') {
return react_1.default.createElement(type, Object.assign(Object.assign(Object.assign({ key: node.id }, attributes), eventHandlers), { style }), textContent, ...childElements);
}
// For other SVG elements
// Note: React.createElement expects the type to be a string for HTML/SVG elements
// Make sure the type is always lowercase
const elementType = type.toLowerCase();
return react_1.default.createElement(elementType, Object.assign(Object.assign(Object.assign({ key: node.id }, attributes), eventHandlers), { style }), childElements);
};
// Attribute editor for SVG
const renderSVGAttributeEditor = (node, onUpdateAttributes, onUpdateTextContent) => {
// Use SVGElementForm for supported types, fallback to SVGAttributesEditor
if (["circle", "rect", "g"].includes(node.type)) {
return (react_1.default.createElement(SVGElementForm_1.SVGElementForm, { elementType: node.type, attributes: node.attributes, onChange: onUpdateAttributes }));
}
else {
return (react_1.default.createElement(SVGAttributesEditor_1.SVGAttributesEditor, { node: node, onUpdateAttributes: onUpdateAttributes, onUpdateTextContent: onUpdateTextContent }));
}
};
// Custom SVG preview that uses the original SVGPreview component
const SvgCustomPreview = () => {
// Convert XMLNode to SVGNode
const svgTree = {
id: xmlTree.id,
type: xmlTree.type,
attributes: xmlTree.attributes,
children: xmlTree.children
};
return (react_1.default.createElement(SVGPreview_1.SVGPreview, { svgTree: svgTree, editMode: editMode, showGrid: showGrid, selectedNodeId: selectedNodeId, onNodeInteraction: (nodeId, updates) => {
// Update node attributes based on interaction
updateNodeAttributes(nodeId, updates);
}, hiddenNodes: hiddenNodes }));
};
// Modify renderSVGPreview to use our custom preview
const renderSVGPreviewWithControls = (node, isSelected, eventHandlers) => {
// For the generic preview, we'll still use the basic rendering
// The SVG-specific interactions are handled by SVGPreview component
return renderSVGPreview(node, isSelected, eventHandlers);
};
// For the SVG editor, we'll need to create a custom implementation
// that properly integrates the SVGPreview component
// Let's use the GenericXMLEditorPage but override the preview when in SVG mode
return (react_1.default.createElement(GenericXMLEditorPage_1.GenericXMLEditorPage, { initialTree: initialTree, renderPreview: renderSVGPreviewWithControls, attributeEditor: renderSVGAttributeEditor, nodeTypes: svgNodeTypes, onAddNode: createSVGNode }));
};
exports.SVGEditorPage = SVGEditorPage;