feat(appliance): initial release of MERICA standalone appliance v1.0.0
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import React, { useState } from 'react';
|
||||
import { EdgeLabelRenderer, BaseEdge } from 'reactflow';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
export default function RemovableEdge({
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
style = {},
|
||||
markerEnd,
|
||||
data,
|
||||
}) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
// Cubic Bezier with horizontal control points max(50, |dx|/2)
|
||||
const dx = Math.max(50, Math.abs(targetX - sourceX) / 2);
|
||||
const edgePath = `M${sourceX},${sourceY} C${sourceX + dx},${sourceY} ${targetX - dx},${targetY} ${targetX},${targetY}`;
|
||||
|
||||
const midX = (sourceX + targetX) / 2;
|
||||
const midY = (sourceY + targetY) / 2;
|
||||
|
||||
const handleDelete = (evt) => {
|
||||
if (evt) evt.stopPropagation();
|
||||
if (data && data.onDeleteEdge) {
|
||||
data.onDeleteEdge(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDoubleClick = (evt) => {
|
||||
evt.stopPropagation();
|
||||
handleDelete(evt);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* VISIBLE BEZIER EDGE */}
|
||||
<BaseEdge
|
||||
path={edgePath}
|
||||
markerEnd={markerEnd}
|
||||
style={{
|
||||
...style,
|
||||
stroke: isHovered ? 'var(--acc)' : style.stroke || 'var(--muted)',
|
||||
strokeWidth: isHovered ? 2.5 : style.strokeWidth || 2,
|
||||
transition: 'stroke 0.15s ease, stroke-width 0.15s ease',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* INVISIBLE WIDE HIT AREA FOR HOVER AND DOUBLE CLICK TO DELETE */}
|
||||
<path
|
||||
d={edgePath}
|
||||
fill="none"
|
||||
stroke="transparent"
|
||||
strokeWidth={22}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
/>
|
||||
|
||||
{/* OPTIONAL HOVER DELETE BUTTON IN CENTER */}
|
||||
{isHovered && (
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${midX}px,${midY}px)`,
|
||||
fontSize: 12,
|
||||
pointerEvents: 'all',
|
||||
zIndex: 10,
|
||||
}}
|
||||
className="nodrag nopan"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
title="Click or double-click line to delete connection"
|
||||
aria-label="Delete connection"
|
||||
style={{
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
borderRadius: '50%',
|
||||
background: 'var(--surface)',
|
||||
color: 'var(--muted)',
|
||||
border: '1px solid var(--line)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
boxShadow: 'var(--shadow)',
|
||||
padding: 0,
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.color = '#ffffff';
|
||||
e.currentTarget.style.background = 'var(--err)';
|
||||
e.currentTarget.style.borderColor = 'var(--err)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.color = 'var(--muted)';
|
||||
e.currentTarget.style.background = 'var(--surface)';
|
||||
e.currentTarget.style.borderColor = 'var(--line)';
|
||||
}}
|
||||
>
|
||||
<X size={10} strokeWidth={2.5} />
|
||||
</button>
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user