// cutout.js - Sprite sheet cutting tool
(function(){
let selectedImage = null;
let selectedFolder = null;
let tileSize = 32; // default
// Listen for image selection from Files module
window.addEventListener('imageSelected', (e) => {
selectedImage = e.detail.url;
selectedFolder = e.detail.folder;
// Try to parse folder name as grid size (16, 32, 64, 128)
const folderSize = parseInt(selectedFolder);
if ([16, 32, 64, 128].includes(folderSize)) {
tileSize = folderSize;
}
console.log('Image selected for cutting:', selectedImage, 'Folder:', selectedFolder, 'Grid:', tileSize);
// Immediately refresh (safe no-op if view isn't mounted yet)
updateContent(selectedImage, tileSize);
});
function renderTileSizeSelector(currentSize) {
const sizes = [16, 32, 64, 128];
return `
<div style="margin-bottom: 1rem; padding: 1rem; background: rgba(30, 41, 59, 0.6); border-radius: 0.75rem; border: 1px solid rgba(71, 85, 105, 0.4);">
<label style="display: block; color: #94a3b8; font-size: 0.875rem; margin-bottom: 0.5rem; text-transform: uppercase; letter-spacing: 0.05em;">
Grid Size
</label>
<select
id="grid-size-select"
style="
width: 100%;
background: rgba(15, 23, 42, 0.8);
border: 1px solid rgba(71, 85, 105, 0.4);
color: #f1f5f9;
padding: 0.75rem;
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
"
onmouseover="this.style.borderColor='rgba(59, 130, 246, 0.6)'"
onmouseout="this.style.borderColor='rgba(71, 85, 105, 0.4)'"
>
${sizes.map(size => `
<option value="${size}" ${size === currentSize ? 'selected' : ''}>
${size}×${size} pixels
</option>
`).join('')}
</select>
</div>
`;
}
function renderCuttingCanvas(imageSrc, gridSize) {
if (!imageSrc) {
return `
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 300px; color: #94a3b8; text-align: center; padding: 2rem;">
<div style="font-size: 3rem; margin-bottom: 1rem;">✂️</div>
<p>No image selected</p>
<p style="font-size: 0.875rem; margin-top: 0.5rem;">Select an image from the Files tab to start cutting</p>
</div>
`;
}
return `
<div style="position: relative; overflow: auto; max-height: 60vh; background: #0a0f1c; border-radius: 0.75rem; border: 1px solid rgba(71, 85, 105, 0.4);">
<div id="canvas-container" style="position: relative; display: inline-block;">
<img
id="sprite-sheet"
src="${imageSrc}"
style="display: block; max-width: none; image-rendering: pixelated;"
crossorigin="anonymous"
onload="window.cutoutDrawGrid && window.cutoutDrawGrid()"
>
<canvas
id="grid-overlay"
style="position: absolute; top: 0; left: 0; pointer-events: none;"
></canvas>
</div>
</div>
<div style="margin-top: 1rem; padding: 0.75rem; background: rgba(30, 41, 59, 0.4); border-radius: 0.5rem; font-size: 0.875rem; color: #94a3b8;">
<strong style="color: #f1f5f9;">Tip:</strong> Grid automatically adjusts to your spritesheet. Each cell is ${gridSize}×${gridSize}px.
</div>
`;
}
function drawGrid() {
const img = document.getElementById('sprite-sheet');
const canvas = document.getElementById('grid-overlay');
if (!canvas || !img) return;
const ctx = canvas.getContext('2d');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
// Position canvas over image
canvas.style.width = img.width + 'px';
canvas.style.height = img.height + 'px';
// Clear previous grid
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw grid with simple path
ctx.strokeStyle = 'rgba(59, 130, 246, 0.6)';
ctx.lineWidth = 1;
ctx.beginPath();
// All vertical lines
for (let x = 0; x <= canvas.width; x += tileSize) {
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
}
// All horizontal lines
for (let y = 0; y <= canvas.height; y += tileSize) {
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
}
ctx.stroke();
}
// Expose drawGrid globally for inline onload
window.cutoutDrawGrid = drawGrid;
function updateContent(imageSrc = selectedImage, gridSize = tileSize) {
const container = document.getElementById('cutout-content');
if (!container) return;
tileSize = gridSize;
container.innerHTML = `
${renderTileSizeSelector(gridSize)}
${renderCuttingCanvas(imageSrc, gridSize)}
`;
wireUpControls();
}
function wireUpControls() {
// Dropdown
const select = document.getElementById('grid-size-select');
if (select) {
select.addEventListener('change', () => {
const newSize = parseInt(select.value);
tileSize = newSize;
updateContent(selectedImage, newSize);
});
}
// Draw grid if image already loaded
const img = document.getElementById('sprite-sheet');
if (img && img.complete) {
drawGrid();
}
}
// Initial HTML
const initialHtml = `
<div id="cutout-content" style="height: 100%; overflow-y: auto; padding: 1rem;">
${renderTileSizeSelector(tileSize)}
${renderCuttingCanvas(selectedImage, tileSize)}
</div>
`;
// Register with onRender callback
window.AppItems = window.AppItems || [];
window.AppItems.push({
title: '✂️ Cut',
html: initialHtml,
onRender: function(slideEl) {
// Ensure current image/grid render whenever the overlay opens
updateContent(selectedImage, tileSize);
}
});
})();