@ichigo_san/graphing
Version:
A lightweight UML-style diagram editor built with React Flow and Tailwind CSS
293 lines (287 loc) • 10.7 kB
JavaScript
"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;