No frames entered. Use the builder tab to start charting the action.
';
return;
}
let tableHtml = `
| SCENE: ${metadata.scene} |
ANIMATOR: ${metadata.animator} |
FPS: ${metadata.fps} |
| # |
ACTION / DRAWING (A) |
DIALOGUE (B) |
SOUND / FX (C) |
CAMERA / NOTES |
`;
frameData.forEach(row => {
tableHtml += `
| ${row.frame} |
${row.action} |
${row.dialogue} |
${row.sound} |
${row.camera} |
`;
});
tableHtml += `
`;
preview.innerHTML = tableHtml;
}
// Debounced render wrapper for input fields
let xscRenderTimeout;
const xscDebouncedRender = () => {
clearTimeout(xscRenderTimeout);
xscRenderTimeout = setTimeout(xscRenderSheet, 500);
};
function xscLoadExample() {
if(!confirm("Overwrite current data with an example walk cycle (24 frames)?")) return;
document.getElementById('inp-scene').value = "S02 - Simple Walk Cycle";
document.getElementById('inp-animator').value = "B. Chuck";
const rowsContainer = document.getElementById('xsc-frame-rows');
rowsContainer.innerHTML = '';
xscTotalFrames = 0;
const data = [
{ frame: 1, action: "Key Pose: Contact (R foot forward)", dialogue: "", sound: "START: Theme Music Cue", camera: "Pan Right (Slow)" },
{ frame: 2, action: "", dialogue: "", sound: "" },
{ frame: 3, action: "Breakdown", dialogue: "", sound: "" },
{ frame: 4, action: "", dialogue: "LINE 1", sound: "" },
{ frame: 5, action: "Key Pose: Passing", dialogue: "", sound: "" },
{ frame: 6, action: "", dialogue: "", sound: "" },
{ frame: 7, action: "Breakdown", dialogue: "", sound: "" },
{ frame: 8, action: "Key Pose: Down (Low point)", dialogue: "LINE 2", sound: "" },
{ frame: 9, action: "", dialogue: "", sound: "" },
{ frame: 10, action: "Inbetween", dialogue: "", sound: "" },
{ frame: 11, action: "Breakdown", dialogue: "", sound: "" },
{ frame: 12, action: "Key Pose: Contact (L foot forward)", dialogue: "LINE 3", sound: "Footstep FX" },
];
data.forEach(d => xscAddFrameRow(d.frame, 1));
// Since the frames were added sequentially, we now update their content
const inputs = document.querySelectorAll('.xsc-frame-row');
data.forEach((d, i) => {
if(inputs[i]) {
inputs[i].querySelector('.inp-action').value = d.action;
inputs[i].querySelector('.inp-dialogue').value = d.dialogue;
inputs[i].querySelector('.inp-sound').value = d.sound;
inputs[i].querySelector('.inp-camera').value = d.camera;
}
});
xscRenderSheet();
xscSwitchTab('preview');
}
/* --- PDF Generation --- */
async function xscGeneratePDF() {
xscRenderSheet(); // Final render update
const metadata = {
scene: document.getElementById('inp-scene').value,
animator: document.getElementById('inp-animator').value,
fps: document.getElementById('inp-fps').value
};
const frameData = xscGetFrameData();
if (frameData.length === 0) {
alert("Please enter frame data before generating the PDF.");
return;
}
const { jsPDF } = window.jspdf;
const doc = new jsPDF('l', 'mm', 'a4'); // LANDSCAPE Orientation
// Prepare table data
const tableBody = frameData.map(row => [
row.frame,
row.action,
row.dialogue,
row.sound,
row.camera
]);
doc.autoTable({
startY: 20,
head: [
[
{ content: `X-SHEET | SCENE: ${metadata.scene}`, colSpan: 3 },
{ content: `ANIMATOR: ${metadata.animator}`, colSpan: 1 },
{ content: `FPS: ${metadata.fps}`, colSpan: 1 }
],
[
"FRAME #",
"ACTION / DRAWING (A)",
"DIALOGUE (B)",
"SOUND / FX (C)",
"CAMERA / NOTES"
]
],
body: tableBody,
theme: 'grid',
headStyles: { fillColor: [0, 128, 128], textColor: 255, fontSize: 10, fontStyle: 'bold' },
styles: { fontSize: 8, cellPadding: 1 },
columnStyles: {
0: { cellWidth: 12, fontStyle: 'bold' }, // Frame #
1: { cellWidth: 50, overflow: 'linebreak' }, // Action
2: { cellWidth: 50, overflow: 'linebreak' }, // Dialogue
3: { cellWidth: 50, overflow: 'linebreak' }, // Sound
4: { cellWidth: 'auto', overflow: 'linebreak' } // Camera
}
});
doc.save(`X_Sheet_${metadata.scene.replace(/\s+/g, '_')}.pdf`);
}