function Canvas({ tegRef, currentTool }) {
const canvasRef = useRef();
const [showInput, setShowInput] = useState(false);
const [newName, setNewName] = useState("");
const [inputPosition, setInputPosition] = useState({ x: 0, y: 0 });
const [selectedElement, setSelectedElement] = useState(null);
const [blurTimeout, setBlurTimeout] = useState(null);
const [singleClickTimeout, setSingleClickTimeout] = useState(null);
const clearBlurTimeout = () => {
if (blurTimeout) {
clearTimeout(blurTimeout);
setBlurTimeout(null);
}
};
const handleNameChange = (event) => {
setNewName(event.target.value);
};
const handleElementUpdate = (event) => {
event.preventDefault();
if (selectedElement) {
selectedElement.elementData.id = selectedElement.id;
console.log(
"selectedElement.elementData.holdingTime = selectedElement.holdingTime",
selectedElement.elementData.holdingTime,
selectedElement.holdingTime
);
selectedElement.elementData.holdingTime[tegRef.current.mode] =
selectedElement.holdingTime;
tegRef.current.update();
setSelectedElement(null);
}
};
const handleElementNameChange = (event) => {
setSelectedElement({ ...selectedElement, id: event.target.value });
};
const handleElementHoldingTimeChange = (event) => {
setSelectedElement({
...selectedElement,
holdingTime: parseFloat(event.target.value)
});
};
const handlePlaceNameSubmit = (event) => {
event.preventDefault();
if (newName) {
// Create a new place and add it to the timedEventGraph
switch (currentTool) {
case "place":
tegRef.current.addPlace(
new Place(newName, 0, inputPosition.x, inputPosition.y)
);
break;
case "transition":
tegRef.current.addTransition(
new Transition(newName, 0, inputPosition.x, inputPosition.y)
);
break;
case "connect":
// Connect two elements and add the arc to the timedEventGraph
break;
default:
// Handle other cases or do nothing
break;
}
tegRef.current.update();
setNewName("");
setShowInput(false);
}
};
const handleCancelInput = () => {
setNewName("");
setShowInput(false);
};
const handleKeyDown = (event) => {
if (event.key === "Escape") {
handleCancelInput();
}
};
const handleElementUpdateBlur = () => {
setBlurTimeout(setTimeout(() => setSelectedElement(null), 100));
};
const handleElementUpdateKeyDown = (event) => {
if (event.key === "Escape") {
setSelectedElement(null);
}
};
useEffect(() => {
if (canvasRef.current) {
function tools(ref) {
//let startNode;
const svgPoint = ref.svg.node().createSVGPoint();
let arrow;
if (currentTool === "connect") {
ref.simulation.stop();
ref.animate = false;
}
function dragstarted(event, d) {
if (currentTool === "connect") {
ref.simulation.stop();
//startNode = d;
arrow = ref.svg
.append("line")
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("marker-end", "url(#arrowend)");
} else {
if (!event.active) {
ref.tickCount = 0;
ref.simulation.alphaTarget(0.3).restart();
}
d.fx = d.x;
d.fy = d.y;
}
}
function dragged(event, d) {
//console.log(arrow, currentTool === "connect", "dragged");
if (currentTool === "connect" && arrow) {
var x = event.x;
var y = event.y;
// Remove highlight class from all elements
d3.selectAll("g").classed("highlight", false);
let endNode = d3
.selectAll("g") // selecting both circles (places) and rectangles (transitions)
.filter((node) => node !== d)
.filter(function (node) {
const bbox = this.getBBox();
var res =
x >= bbox.x + node.x &&
x <= bbox.x + node.x + bbox.width &&
y >= bbox.y + node.y &&
y <= bbox.y + node.y + bbox.height;
return res;
});
endNode.classed("highlight", true);
console.log("hightlight added");
arrow
.attr("x1", d.x)
.attr("y1", d.y)
.attr("x2", event.x)
.attr("y2", event.y);
} else {
ref.tickCount = 0;
d.fx = event.x;
d.fy = event.y;
}
}
function dragended(event, d) {
if (currentTool === "connect" && arrow) {
var x = event.x;
var y = event.y;
// Remove highlight class from all elements
d3.selectAll("g").classed("highlight", false);
let endNode2 = d3
.selectAll("g") // selecting both circles (places) and rectangles (transitions)
.filter((node) => node !== d)
.each(function (node) {
// console.log("for all nides", node, this);
})
.filter(function (node) {
const bbox = this.getBBox();
var res =
x >= bbox.x + node.x &&
x <= bbox.x + node.x + bbox.width &&
y >= bbox.y + node.y &&
y <= bbox.y + node.y + bbox.height;
// console.log(res, node, bbox, "WHAt???", event, x, y);
return res;
});
arrow.remove();
if (endNode2.node()) {
var endNode = endNode2.datum();
if (d !== endNode) {
//what && d.type !== endNode.type
tegRef.current.addArc(new Arc(d, endNode));
tegRef.current.update();
}
} else {
console.log("NO ENDNODE found!!!", endNode2);
}
//console.log("CONNECT", d, endNode);
arrow = null;
} else {
ref.tickCount = 0;
if (!event.active) ref.simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
}
ref.svg.on("click", (event) => onClick(event, tegRef.current));
ref.svg.on("dblclick", (event) => onDoubleClick(event));
return { dragstarted, dragged, dragended };
}
//Thanks to d3 we have to recreate the whole graph because it's so hard to unset drag operators :/
drawTimedEventGraph(tegRef.current, canvasRef.current, tools);
}
}, [tegRef.current, currentTool]);
const onDoubleClick = (event) => {
// Clear the single click timeout to prevent it from executing
if (singleClickTimeout) {
clearTimeout(singleClickTimeout);
setSingleClickTimeout(null);
}
// Handle double-click event logic here
console.log("Double-click detected", currentTool);
switch (currentTool) {
case "cursor":
//console.log(event.target.__data__, tegRef, "AYY");
break;
case "transition":
break;
case "place":
break;
case "connect":
// Connect two elements and add the arc to the timedEventGraph
break;
default:
// Handle other cases or do nothing
break;
}
};
const onClick = (event, tegRef) => {
// If there's already a single click timeout set, clear it
if (singleClickTimeout) {
clearTimeout(singleClickTimeout);
setSingleClickTimeout(null);
}
const [x, y] = d3.pointer(event);
let elementData;
switch (currentTool) {
case "delete":
elementData = event.target.__data__;
tegRef.removeElement(elementData);
tegRef.update();
case "edit":
// Set a new single click timeout
// setSingleClickTimeout(
// setTimeout(() => {
elementData = event.target.__data__;
if (elementData) {
setInputPosition({ x, y });
if (elementData instanceof Place || elementData instanceof Transition)
setSelectedElement({
elementData,
x,
y,
id: elementData.id,
holdingTime: elementData.holdingTime
});
} else {
console.log("nothing to edit");
}
// }, 300)
// );
break;
case "transition":
case "place":
setInputPosition({ x, y });
setShowInput(true);
break;
case "connect":
// Connect two elements and add the arc to the timedEventGraph
break;
case "cursor":
tegRef.fireTransition(event.target.__data__);
highlightTransition(event.target);
break;
default:
// Handle other cases or do nothing
break;
}
};
function highlightTransition(transitionElement) {
d3.select(transitionElement).classed("highlight-transition", true);
setTimeout(() => {
d3.select(transitionElement).classed("highlight-transition", false);
}, 100); // 1000ms or 1 second highlight duration
}
console.log("selectedelement", selectedElement);
return jsx`
<div ref=${canvasRef}>
${
showInput &&
jsx`
<form
onSubmit=${handlePlaceNameSubmit}
style=${{
position: "absolute",
left: `${inputPosition.x}px`,
top: `${inputPosition.y}px`
}}
>
<input
type="text"
autoFocus
value=${newName}
onChange=${handleNameChange}
onBlur=${handleCancelInput}
onKeyDown=${handleKeyDown}
placeholder="Enter ${currentTool} name"
/>
</form>`
}
${
selectedElement &&
jsx`
<form
onSubmit=${handleElementUpdate}
onKeyDown=${handleElementUpdateKeyDown}
style=${{
position: "absolute",
left: `${inputPosition.x}px`,
top: `${inputPosition.y}px`
}}
>
<input
type="text"
value=${selectedElement.id}
onChange=${handleElementNameChange}
onBlur=${handleElementUpdateBlur}
onFocus=${clearBlurTimeout}
onKeyDown=${handleElementUpdateKeyDown}
placeholder="Enter name"
autoFocus
/>
<input
type="number"
step="0.01"
value=${selectedElement.holdingTime[tegRef.current.mode]}
onChange=${handleElementHoldingTimeChange}
onBlur=${handleElementUpdateBlur}
onFocus=${clearBlurTimeout}
onKeyDown=${handleElementUpdateKeyDown}
placeholder="Enter holding time"
/>
<button type="submit">Update</button>
</form>`
}
</div>
`;
}