UNPKG

@ichigo_san/graphing

Version:

A lightweight UML-style diagram editor built with React Flow and Tailwind CSS

293 lines (287 loc) 10.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactflow = require("reactflow"); var _ShapeDefinitions = require("../../config/ShapeDefinitions"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } // Draw.io-style connection point styles (consistent with other nodes) const drawioHandleStyle = { background: '#3b82f6', border: '2px solid #ffffff', width: 12, height: 12, borderRadius: '50%', zIndex: 10, cursor: 'crosshair', transition: 'all 0.2s ease', opacity: 0, transform: 'scale(0.8)' }; const drawioHandleHoverStyle = { ...drawioHandleStyle, opacity: 1, transform: 'scale(1.2)', boxShadow: 'none' }; const UniversalShapeNode = ({ data, id, selected, isConnectable }) => { const [isEditing, setIsEditing] = (0, _react.useState)(false); const [label, setLabel] = (0, _react.useState)(data.label || ''); const [hoveredHandle, setHoveredHandle] = (0, _react.useState)(null); const shapeDefinition = (0, _ShapeDefinitions.getShapeDefinition)(data.shapeType); const inputRef = (0, _react.useRef)(null); // All hooks must be called before any conditional returns const handleDoubleClick = (0, _react.useCallback)(e => { e.stopPropagation(); setIsEditing(true); }, []); const handleLabelSubmit = (0, _react.useCallback)(() => { setIsEditing(false); if (data.onLabelChange && label !== data.label) { data.onLabelChange(id, label); } }, [id, label, data]); const handleKeyDown = (0, _react.useCallback)(e => { if (e.key === 'Enter') { handleLabelSubmit(); } if (e.key === 'Escape') { setLabel(data.label || (shapeDefinition === null || shapeDefinition === void 0 ? void 0 : shapeDefinition.name) || 'Shape'); setIsEditing(false); } }, [handleLabelSubmit, data.label, shapeDefinition === null || shapeDefinition === void 0 ? void 0 : shapeDefinition.name]); const handleConnectionPointMouseEnter = (0, _react.useCallback)(position => { setHoveredHandle(position); }, []); const handleConnectionPointMouseLeave = (0, _react.useCallback)(() => { setHoveredHandle(null); }, []); (0, _react.useEffect)(() => { setLabel(data.label || (shapeDefinition === null || shapeDefinition === void 0 ? void 0 : shapeDefinition.name) || 'Shape'); }, [data.label, shapeDefinition === null || shapeDefinition === void 0 ? void 0 : shapeDefinition.name]); // If shape definition not found, render error state if (!shapeDefinition) { console.warn(`Unknown shape type: ${data.shapeType}`); return /*#__PURE__*/_react.default.createElement("div", { style: { width: '100%', height: '100%', backgroundColor: '#ffebee', border: '2px solid #f44336', borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#d32f2f', fontSize: '12px', textAlign: 'center', padding: '8px' } }, "Unknown Shape: ", data.shapeType); } // Render shape based on renderType const renderShape = () => { const mergedStyle = { ...shapeDefinition.style, ...data.style }; switch (shapeDefinition.renderType) { case 'svg': return renderSVGShape(mergedStyle); case 'icon': return renderIconShape(mergedStyle); case 'custom': return renderCustomShape(mergedStyle); default: return renderDefaultShape(mergedStyle); } }; const renderSVGShape = style => /*#__PURE__*/_react.default.createElement("svg", { width: "100%", height: "100%", viewBox: `0 0 ${shapeDefinition.defaultSize.width} ${shapeDefinition.defaultSize.height}`, preserveAspectRatio: "none", style: { overflow: 'visible' } }, /*#__PURE__*/_react.default.createElement("path", { d: shapeDefinition.svgPath, fill: style.fill, stroke: style.stroke, strokeWidth: style.strokeWidth, style: { vectorEffect: 'non-scaling-stroke' } })); const renderIconShape = style => /*#__PURE__*/_react.default.createElement("div", { style: { width: '100%', height: '100%', backgroundColor: style.fill, border: `${style.strokeWidth}px solid ${style.stroke}`, borderRadius: style.borderRadius || '8px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', position: 'relative' } }, shapeDefinition.icon && /*#__PURE__*/_react.default.createElement("span", { style: { fontSize: Math.min(data.width || 80, data.height || 80) * 0.4 + 'px', lineHeight: '1' } }, shapeDefinition.icon)); const renderCustomShape = style => { // Handle special custom shapes like UML class if (shapeDefinition.id === 'uml-class') { return /*#__PURE__*/_react.default.createElement("div", { style: { width: '100%', height: '100%', backgroundColor: style.fill, border: `${style.strokeWidth}px solid ${style.stroke}`, borderRadius: style.borderRadius || '4px', display: 'flex', flexDirection: 'column' } }, /*#__PURE__*/_react.default.createElement("div", { style: { padding: '8px', borderBottom: `1px solid ${style.stroke}`, textAlign: 'center', fontWeight: 'bold', fontSize: '12px' } }, label || 'ClassName'), /*#__PURE__*/_react.default.createElement("div", { style: { padding: '4px 8px', borderBottom: `1px solid ${style.stroke}`, fontSize: '10px', minHeight: '20px' } }), /*#__PURE__*/_react.default.createElement("div", { style: { padding: '4px 8px', fontSize: '10px', flex: 1 } })); } // Default custom rendering return renderDefaultShape(style); }; const renderDefaultShape = style => /*#__PURE__*/_react.default.createElement("div", { style: { width: '100%', height: '100%', backgroundColor: style.fill, border: `${style.strokeWidth}px solid ${style.stroke}`, borderRadius: style.borderRadius || '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative' } }, shapeDefinition.icon && !label && /*#__PURE__*/_react.default.createElement("span", { style: { fontSize: '24px' } }, shapeDefinition.icon)); // Render connection points const renderConnectionPoints = () => { return shapeDefinition.connectionPoints.map(position => /*#__PURE__*/_react.default.createElement(_react.default.Fragment, { key: position }, /*#__PURE__*/_react.default.createElement(_reactflow.Handle, { type: "source", position: _reactflow.Position[position.charAt(0).toUpperCase() + position.slice(1)], id: `${position}-source`, style: hoveredHandle === position ? drawioHandleHoverStyle : drawioHandleStyle, isConnectable: isConnectable, className: "drawio-connection-point", onMouseEnter: () => handleConnectionPointMouseEnter(position), onMouseLeave: handleConnectionPointMouseLeave }), /*#__PURE__*/_react.default.createElement(_reactflow.Handle, { type: "target", position: _reactflow.Position[position.charAt(0).toUpperCase() + position.slice(1)], id: `${position}-target`, style: hoveredHandle === position ? drawioHandleHoverStyle : drawioHandleStyle, isConnectable: isConnectable, className: "drawio-connection-point", onMouseEnter: () => handleConnectionPointMouseEnter(position), onMouseLeave: handleConnectionPointMouseLeave }))); }; return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_reactflow.NodeResizer, { isVisible: selected, minWidth: shapeDefinition.defaultSize.width / 2, minHeight: shapeDefinition.defaultSize.height / 2, handleClassName: "w-2.5 h-2.5 bg-gray-500 rounded-full border-2 border-white", lineClassName: "border-2 border-dashed border-indigo-500 opacity-60", nodeWidth: data.width, nodeHeight: data.height }), /*#__PURE__*/_react.default.createElement("div", { style: { width: '100%', height: '100%', position: 'relative', cursor: 'move', transition: 'all 0.2s ease', boxShadow: selected ? '0 0 0 2px #2196F3' : 'none' }, onMouseEnter: () => setHoveredHandle('node'), onMouseLeave: () => setHoveredHandle(null), onDoubleClick: handleDoubleClick }, renderShape(), label && shapeDefinition.renderType !== 'custom' && /*#__PURE__*/_react.default.createElement("div", { style: { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', pointerEvents: isEditing ? 'auto' : 'none', zIndex: 10, maxWidth: '90%', textAlign: 'center' } }, isEditing ? /*#__PURE__*/_react.default.createElement("input", { ref: inputRef, value: label, onChange: e => setLabel(e.target.value), onBlur: handleLabelSubmit, onKeyDown: handleKeyDown, autoFocus: true, style: { background: 'rgba(255, 255, 255, 0.9)', border: '2px solid #2196F3', borderRadius: '4px', outline: 'none', padding: '4px 8px', fontSize: '12px', fontWeight: 'bold', textAlign: 'center', minWidth: '60px' } }) : /*#__PURE__*/_react.default.createElement("span", { style: { fontSize: '12px', fontWeight: 'bold', color: '#333', background: 'rgba(255, 255, 255, 0.8)', padding: '2px 6px', borderRadius: '4px', boxShadow: 'none', wordWrap: 'break-word', overflowWrap: 'break-word', hyphens: 'auto', lineHeight: '1.2' } }, label)), renderConnectionPoints())); }; var _default = exports.default = UniversalShapeNode;