";
}
ramsConfig.actions.forEach((action) => {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "rams-btn rams-btn-secondary";
btn.textContent = action.name;
btn.setAttribute(
"onclick",
`ramsExecuteAction('${action.id}')`
);
actionButtonsContainer.appendChild(btn);
});
}
/**
* Renders the log messages.
*/
function renderLog() {
if (!logOutput) return;
logOutput.innerHTML = ramsState.log
.map(
(entry) => `
${entry}
`
)
.join("");
}
/**
* Applies CSS transforms to the visual arm elements.
*/
function updateArmVisuals(base, arm1, arm2, gripperStatus) {
if (!armBase || !armSeg1 || !armSeg2 || !gripper)
return;
armBase.style.transform = `translateX(-50%) rotate(${base}deg)`;
armSeg1.style.transform = `rotate(${arm1}deg)`;
armSeg2.style.transform = `rotate(${arm2}deg)`;
if (gripperStatus === "closed") {
gripper.classList.add("rams-closed");
} else {
gripper.classList.remove("rams-closed");
}
}
/**
* Renders the list of configured actions.
*/
function renderConfigList() {
if (!configListContainer) return;
configListContainer.innerHTML = "";
if (ramsConfig.actions.length === 0) {
configListContainer.innerHTML = "
No actions configured.
";
return;
}
ramsConfig.actions.forEach((action) => {
const item = document.createElement("div");
item.className = "rams-list-item";
item.innerHTML = `
${action.name}
Base: ${formatDeg(
action.base
)}
Arm1: ${formatDeg(
action.arm1
)}
Arm2: ${formatDeg(
action.arm2
)}
`;
configListContainer.appendChild(item);
});
}
/**
* Update navigation button states.
*/
function updateNavButtons() {
if (!nextBtn || !prevBtn) return;
if (currentTabId === "rams-tab-dashboard") {
prevBtn.disabled = true;
nextBtn.disabled = false;
} else if (currentTabId === "rams-tab-config") {
prevBtn.disabled = false;
nextBtn.disabled = true;
}
}
/**
* Re-renders all UI components.
*/
function fullRefreshUI() {
renderDashboardControls();
renderConfigList();
renderLog();
updateArmVisuals(
ramsState.currentPosition.base,
ramsState.currentPosition.arm1,
ramsState.currentPosition.arm2,
ramsState.currentPosition.gripper
);
updateNavButtons();
}
// === EVENT HANDLERS (Must be global for onclick) ===
window.ramsShowTab = (tabId, element) => {
// Hide all content
container
.querySelectorAll(".rams-tab-content")
.forEach((tab) => (tab.style.display = "none"));
// Deactivate all links
container
.querySelectorAll(".rams-tab-link")
.forEach((link) => link.classList.remove("rams-active"));
// Show selected content
const tabToShow = container.querySelector("#" + tabId);
if (tabToShow) {
tabToShow.style.display = "block";
}
// Activate selected link
if (element) {
element.classList.add("rams-active");
}
currentTabId = tabId;
updateNavButtons();
};
window.ramsNavigateTabs = (isNext) => {
const tabs = Array.from(
container.querySelectorAll(".rams-tab-link")
);
const currentIdx = tabs.findIndex((tab) =>
tab.classList.contains("rams-active")
);
let newIdx = isNext ? currentIdx + 1 : currentIdx - 1;
if (newIdx >= 0 && newIdx < tabs.length) {
tabs[newIdx].click();
}
};
// --- Config Tab Handlers ---
window.ramsAddAction = () => {
if (
!newNameInput ||
!newBaseInput ||
!newArm1Input ||
!newArm2Input ||
!newGripperSelect
)
return;
const name = newNameInput.value.trim();
const base = parseFloat(newBaseInput.value);
const arm1 = parseFloat(newArm1Input.value);
const arm2 = parseFloat(newArm2Input.value);
const gripper = newGripperSelect.value;
if (!name) {
alert("Please enter an action name.");
return;
}
if (
isNaN(base) ||
isNaN(arm1) ||
isNaN(arm2)
) {
alert("Please enter valid numbers for all angles.");
return;
}
const newAction = {
id: "a" + Date.now(),
name,
base,
arm1,
arm2,
gripper,
};
ramsConfig.actions.push(newAction);
newNameInput.value = "";
newBaseInput.value = "0";
newArm1Input.value = "45";
newArm2Input.value = "-30";
renderConfigList();
renderDashboardControls();
};
window.ramsRemoveAction = (id) => {
if (
!confirm("Are you sure you want to remove this action?")
) {
return;
}
ramsConfig.actions = ramsConfig.actions.filter(
(a) => a.id !== id
);
renderConfigList();
renderDashboardControls();
};
// --- Dashboard Tab Handlers ---
window.ramsExecuteAction = (id) => {
const action = ramsConfig.actions.find(
(a) => a.id === id
);
if (!action) {
logAction(`Error: Action ID ${id} not found.`);
return;
}
// Update state
ramsState.currentPosition = { ...action };
// Update visuals
updateArmVisuals(
action.base,
action.arm1,
action.arm2,
action.gripper
);
// Log it
logAction(`Executed: ${action.name}`);
};
// --- PDF Download ---
window.ramsDownloadPDF = () => {
if (!jsPDF) {
alert("PDF library is not loaded. Please try again.");
return;
}
try {
const doc = new jsPDF();
const pageMargin = 15;
const pageWidth = doc.internal.pageSize.getWidth();
let y = 20;
// 1. Title
doc.setFontSize(20);
doc.text(
"Robot Arm Simulator Report",
pageWidth / 2,
y, {
align: "center"
}
);
y += 20;
// 2. Defined Actions Table
doc.setFontSize(16);
doc.text("Defined Action Library", pageMargin, y);
y += 10;
const tableHead = [
"Action Name",
"Base Rotation",
"Arm 1 Angle",
"Arm 2 Angle",
"Gripper",
];
const tableBody = ramsConfig.actions.map((a) => [
a.name,
formatDeg(a.base),
formatDeg(a.arm1),
formatDeg(a.arm2),
formatCap(a.gripper),
]);
doc.autoTable({
startY: y,
head: [tableHead],
body: tableBody,
theme: "striped",
headStyles: {
fillColor: [0, 95, 204]
},
});
y = doc.autoTable.previous.finalY + 15;
// 3. Action Log
doc.setFontSize(16);
doc.text("Execution Log", pageMargin, y);
y += 10;
doc.setFontSize(10);
doc.setFont("courier");
if (ramsState.log.length === 0) {
doc.text("No actions executed.", pageMargin, y);
} else {
ramsState.log.forEach((entry) => {
if (y > 270) {
doc.addPage();
y = pageMargin;
}
doc.text(entry, pageMargin, y);
y += 5;
});
}
doc.save("Robot_Arm_Simulator_Report.pdf");
} catch (e) {
console.error("Error generating PDF:", e);
alert(
"An error occurred while generating the PDF."
);
}
};
// === INITIALIZATION ===
fullRefreshUI();
logAction("Simulator initialized. Ready.");
});