๐Ÿ“œ
tilemap_copy3.js
โ† Back
๐Ÿ“ Javascript โšก Executable Ctrl+S: Save โ€ข Ctrl+R: Run โ€ข Ctrl+F: Find
/* ---------- Tile Map Management ---------- */ /* DOM Elements - only declare tilemap-specific elements */ let tilemapBtn; /* State */ let isTilemapMode = false; let tileMapGrid = []; let gridWidth = 32; // Default grid size let gridHeight = 24; let baseTileSize = 64; // Base tile size - all tiles scale from this let selectedTile = null; // Currently selected tile for painting let activeTab = 0; // Track the active tab, default to first line (index 0) /* ---------- Tile Map Mode Controls ---------- */ function enableTilemapMode() { try { // Get workspace elements from DOM directly const objectsBar = document.getElementById('objectsBar'); const linesBar = document.getElementById('linesBar'); const tankBar = document.getElementById('tankBar'); const metaEl = document.getElementById('meta'); // Hide workspace elements if (objectsBar) objectsBar.style.display = 'none'; if (linesBar) linesBar.style.display = 'none'; if (tankBar) tankBar.style.display = 'none'; // Update meta text if (metaEl) { metaEl.textContent = 'Tile Map Mode: Click a tile to select it, then click grid squares to place it. Right-click or use trash can to erase.'; } // Update button appearance if (tilemapBtn) { tilemapBtn.style.background = 'var(--accent)'; tilemapBtn.style.color = '#000'; } isTilemapMode = true; // Create blank tile map createBlankTileMap(); // Show tank for dragging tiles showTileMapTank(); } catch (error) { alert('Error enabling tilemap mode: ' + error.message); } } function disableTilemapMode() { try { // Get workspace elements from DOM directly const objectsBar = document.getElementById('objectsBar'); const linesBar = document.getElementById('linesBar'); const tankBar = document.getElementById('tankBar'); const metaEl = document.getElementById('meta'); // Show workspace elements if (objectsBar) objectsBar.style.display = ''; if (linesBar) linesBar.style.display = ''; if (tankBar) tankBar.style.display = ''; // Restore meta text if (metaEl) { if (window.CoreAPI && window.CoreAPI.imgW && window.CoreAPI.imgH) { metaEl.textContent = `Image: ${window.CoreAPI.imgW}ร—${window.CoreAPI.imgH} โ€” tap tiles to add to the selected line`; } else { metaEl.textContent = 'Open an image, then tap grid tiles to add thumbnails to the selected line.'; } } // Reset button appearance if (tilemapBtn) { tilemapBtn.style.background = ''; tilemapBtn.style.color = ''; } isTilemapMode = false; // Hide tile map hideTileMap(); // Hide tile map tank hideTileMapTank(); } catch (error) { alert('Error disabling tilemap mode: ' + error.message); } } function toggleTilemapMode() { if (isTilemapMode) { disableTilemapMode(); } else { enableTilemapMode(); } } /* ---------- Tile Scaling Utilities ---------- */ function getTileScale(tileData) { // Calculate how many grid squares this tile should occupy const scaleX = Math.ceil(tileData.w / baseTileSize); const scaleY = Math.ceil(tileData.h / baseTileSize); return { scaleX, scaleY }; } function canPlaceTileAt(gridX, gridY, scaleX, scaleY) { // Check if there's room for a tile of this size at this position if (gridX + scaleX > gridWidth || gridY + scaleY > gridHeight) { return false; // Doesn't fit in grid } // Check if all required squares are empty for (let y = gridY; y < gridY + scaleY; y++) { for (let x = gridX; x < gridX + scaleX; x++) { if (tileMapGrid[y][x]) { return false; // Square is occupied } } } return true; } function clearTileArea(gridX, gridY, scaleX, scaleY) { // Clear all squares in the specified area for (let y = gridY; y < gridY + scaleY; y++) { for (let x = gridX; x < gridX + scaleX; x++) { if (tileMapGrid[y][x]) { removeTileFromGrid(x, y); } } } } /* ---------- Div-Based Tile Map Creation ---------- */ let tileMapContainer = null; let gridCells = []; // Array of div elements for each grid square function createBlankTileMap() { try { // Remove existing tile map if any if (tileMapContainer) { tileMapContainer.remove(); } // Create tile map container tileMapContainer = document.createElement('div'); tileMapContainer.id = 'tileMapContainer'; tileMapContainer.style.position = 'absolute'; tileMapContainer.style.left = '50px'; tileMapContainer.style.top = '50px'; tileMapContainer.style.width = (gridWidth * baseTileSize) + 'px'; tileMapContainer.style.height = (gridHeight * baseTileSize) + 'px'; tileMapContainer.style.background = '#1a1a1a'; tileMapContainer.style.border = '2px solid #4fc3f7'; tileMapContainer.style.zIndex = '15'; tileMapContainer.style.position = 'relative'; // Important for absolute positioning of children // Initialize empty grid tileMapGrid = Array(gridHeight).fill().map(() => Array(gridWidth).fill(null)); gridCells = []; // Create individual grid cells as divs for (let y = 0; y < gridHeight; y++) { gridCells[y] = []; for (let x = 0; x < gridWidth; x++) { const cell = document.createElement('div'); cell.className = 'grid-cell'; cell.style.position = 'absolute'; cell.style.left = (x * baseTileSize) + 'px'; cell.style.top = (y * baseTileSize) + 'px'; cell.style.width = baseTileSize + 'px'; cell.style.height = baseTileSize + 'px'; cell.style.border = '1px solid rgba(79, 195, 247, 0.2)'; cell.style.boxSizing = 'border-box'; cell.style.cursor = 'pointer'; cell.style.backgroundColor = 'transparent'; // Store grid coordinates on the element cell.dataset.gridX = x; cell.dataset.gridY = y; // Add hover effect cell.addEventListener('mouseenter', () => { if (selectedTile) { const { scaleX, scaleY } = getTileScale(selectedTile); highlightPlacementArea(x, y, scaleX, scaleY); } else { cell.style.backgroundColor = 'rgba(255, 68, 68, 0.3)'; // Red for eraser } }); cell.addEventListener('mouseleave', () => { clearHighlights(); }); // Add click handlers cell.addEventListener('click', handleCellClick); cell.addEventListener('contextmenu', handleCellRightClick); tileMapContainer.appendChild(cell); gridCells[y][x] = cell; } } const viewport = document.getElementById('viewport'); if (viewport) { viewport.appendChild(tileMapContainer); } else { alert('ERROR: Viewport not found!'); return; } } catch (error) { alert('Error creating blank tile map: ' + error.message); } } function highlightPlacementArea(startX, startY, scaleX, scaleY) { clearHighlights(); const canPlace = canPlaceTileAt(startX, startY, scaleX, scaleY); const color = canPlace ? 'rgba(79, 195, 247, 0.4)' : 'rgba(255, 68, 68, 0.4)'; for (let y = startY; y < startY + scaleY && y < gridHeight; y++) { for (let x = startX; x < startX + scaleX && x < gridWidth; x++) { if (gridCells[y] && gridCells[y][x]) { gridCells[y][x].style.backgroundColor = color; } } } } function clearHighlights() { for (let y = 0; y < gridHeight; y++) { for (let x = 0; x < gridWidth; x++) { if (gridCells[y] && gridCells[y][x] && !tileMapGrid[y][x]) { gridCells[y][x].style.backgroundColor = 'transparent'; } } } } function hideTileMap() { if (tileMapContainer) { tileMapContainer.style.display = 'none'; } } /* ---------- Click Handlers for Grid Cells ---------- */ function handleCellClick(e) { e.preventDefault(); e.stopPropagation(); const gridX = parseInt(e.target.dataset.gridX); const gridY = parseInt(e.target.dataset.gridY); if (selectedTile === null) { // Eraser mode - remove any tile that occupies this square removeTileFromGrid(gridX, gridY); } else { // Paint selected tile const { scaleX, scaleY } = getTileScale(selectedTile); // Check if tile fits at this position if (canPlaceTileAt(gridX, gridY, scaleX, scaleY)) { placeTileOnGrid(gridX, gridY, selectedTile); } else { // Show message about why it can't be placed if (gridX + scaleX > gridWidth || gridY + scaleY > gridHeight) { alert(`Tile (${selectedTile.w}ร—${selectedTile.h}) doesn't fit here. Need ${scaleX}ร—${scaleY} free squares.`); } else { alert('Cannot place tile here - area is occupied.'); } } } } function handleCellRightClick(e) { e.preventDefault(); e.stopPropagation(); const gridX = parseInt(e.target.dataset.gridX); const gridY = parseInt(e.target.dataset.gridY); removeTileFromGrid(gridX, gridY); } /* ---------- Tile Placement and Removal ---------- */ function placeTileOnGrid(gridX, gridY, tileData) { try { const { scaleX, scaleY } = getTileScale(tileData); // Create tile element that spans multiple grid squares const tileEl = document.createElement('div'); tileEl.className = 'placed-tile'; tileEl.style.position = 'absolute'; tileEl.style.left = (gridX * baseTileSize) + 'px'; tileEl.style.top = (gridY * baseTileSize) + 'px'; tileEl.style.width = (scaleX * baseTileSize) + 'px'; tileEl.style.height = (scaleY * baseTileSize) + 'px'; tileEl.style.backgroundImage = `url(${tileData.thumbDataURL})`; tileEl.style.backgroundSize = 'contain'; tileEl.style.backgroundRepeat = 'no-repeat'; tileEl.style.backgroundPosition = 'center'; tileEl.style.imageRendering = 'pixelated'; tileEl.style.border = '2px solid #4fc3f7'; tileEl.style.boxSizing = 'border-box'; tileEl.style.zIndex = '10'; // Above grid cells tileEl.style.pointerEvents = 'none'; // Allow clicks to pass through to grid cells // Add ID badge const idBadge = document.createElement('div'); idBadge.style.position = 'absolute'; idBadge.style.top = '2px'; idBadge.style.left = '2px'; idBadge.style.background = '#4fc3f7'; idBadge.style.color = '#000'; idBadge.style.padding = '1px 4px'; idBadge.style.borderRadius = '3px'; idBadge.style.fontSize = '10px'; idBadge.style.fontWeight = 'bold'; idBadge.style.lineHeight = '1'; idBadge.textContent = `#${tileData.id}`; tileEl.appendChild(idBadge); // Add size indicator for multi-square tiles if (scaleX > 1 || scaleY > 1) { const sizeIndicator = document.createElement('div'); sizeIndicator.style.position = 'absolute'; sizeIndicator.style.top = '2px'; sizeIndicator.style.right = '2px'; sizeIndicator.style.background = 'rgba(0,0,0,0.8)'; sizeIndicator.style.color = '#ffff00'; sizeIndicator.style.padding = '1px 4px'; sizeIndicator.style.borderRadius = '3px'; sizeIndicator.style.fontSize = '10px'; sizeIndicator.style.fontWeight = 'bold'; sizeIndicator.style.lineHeight = '1'; sizeIndicator.textContent = `${scaleX}ร—${scaleY}`; tileEl.appendChild(sizeIndicator); } tileMapContainer.appendChild(tileEl); // Create tile reference object const tileRef = { element: tileEl, data: tileData, scaleX, scaleY, originX: gridX, originY: gridY }; // Mark all squares this tile occupies and update their background for (let y = gridY; y < gridY + scaleY; y++) { for (let x = gridX; x < gridX + scaleX; x++) { tileMapGrid[y][x] = tileRef; // Mark grid cell as occupied if (gridCells[y] && gridCells[y][x]) { gridCells[y][x].style.backgroundColor = 'rgba(79, 195, 247, 0.1)'; } } } } catch (error) { alert('Error placing tile on grid: ' + error.message); } } function removeTileFromGrid(gridX, gridY) { try { const tileRef = tileMapGrid[gridY][gridX]; if (tileRef) { // Remove the visual element tileRef.element.remove(); // Clear all grid squares this tile occupied and restore their appearance for (let y = tileRef.originY; y < tileRef.originY + tileRef.scaleY; y++) { for (let x = tileRef.originX; x < tileRef.originX + tileRef.scaleX; x++) { if (y >= 0 && y < gridHeight && x >= 0 && x < gridWidth) { tileMapGrid[y][x] = null; // Restore grid cell appearance if (gridCells[y] && gridCells[y][x]) { gridCells[y][x].style.backgroundColor = 'transparent'; } } } } } } catch (error) { alert('Error removing tile from grid: ' + error.message); } } /* ---------- Grid Resizing with Div Updates ---------- */ function resizeGrid(newWidth, newHeight) { try { // Minimum size constraints if (newWidth < 1 || newHeight < 1) { alert('Grid size must be at least 1x1'); return; } // Maximum size constraints to prevent performance issues if (newWidth > 100 || newHeight > 100) { alert('Grid size cannot exceed 100x100'); return; } const oldWidth = gridWidth; const oldHeight = gridHeight; // Clear existing tiles that would be outside new bounds if (newWidth < oldWidth || newHeight < oldHeight) { for (let y = 0; y < oldHeight; y++) { for (let x = 0; x < oldWidth; x++) { if (tileMapGrid[y] && tileMapGrid[y][x] && (x >= newWidth || y >= newHeight)) { removeTileFromGrid(x, y); } } } } // Update grid dimensions gridWidth = newWidth; gridHeight = newHeight; // Update container size if (tileMapContainer) { tileMapContainer.style.width = (gridWidth * baseTileSize) + 'px'; tileMapContainer.style.height = (gridHeight * baseTileSize) + 'px'; } // Recreate the entire grid (simpler than trying to add/remove cells) createBlankTileMap(); // Update display updateTileMapTank(); } catch (error) { alert('Error resizing grid: ' + error.message); } } /* ---------- Base Size Control with Grid Rebuild ---------- */ function changeBaseSize(newSize) { try { // Validate size (powers of 2 between 8 and 128) if (newSize < 8 || newSize > 128 || !Number.isInteger(Math.log2(newSize))) { alert('Base size must be 8, 16, 32, 64, or 128 pixels'); return; } // Confirm if there are tiles placed (since it will clear the map) const hasTiles = tileMapGrid.some(row => row.some(cell => cell !== null)); if (hasTiles) { if (!confirm(`Changing base size will clear the current tilemap. Continue?`)) { return; } } // Update base size baseTileSize = newSize; // Recreate the grid with new base size createBlankTileMap(); // Update the tank display updateTileMapTank(); } catch (error) { alert('Error changing base size: ' + error.message); } } /* ---------- Tile Map Tank (shows available tiles) ---------- */ let tileMapTank = null; function showTileMapTank() { try { // Create or show tile map tank if (!tileMapTank) { tileMapTank = document.createElement('div'); tileMapTank.id = 'tileMapTank'; tileMapTank.style.position = 'fixed'; tileMapTank.style.top = '80px'; // Adjusted for better visibility tileMapTank.style.left = '10px'; tileMapTank.style.right = '10px'; tileMapTank.style.maxHeight = '180px'; // Reduced for mobile tileMapTank.style.background = '#161616'; tileMapTank.style.border = '1px solid #2a2a2a'; tileMapTank.style.borderRadius = '.6rem'; tileMapTank.style.padding = '.5rem'; tileMapTank.style.zIndex = '20'; tileMapTank.style.display = 'flex'; tileMapTank.style.flexDirection = 'column'; tileMapTank.style.gap = '.3rem'; tileMapTank.style.overflow = 'hidden'; // Add responsive CSS const style = document.createElement('style'); style.textContent = ` @media (max-width: 600px) { #tileMapTank { left: 5px; right: 5px; max-height: 150px; } .tilemap-controls-row { flex-wrap: wrap; gap: 0.5rem; } .tilemap-controls-row > div, .tilemap-controls-row > button { font-size: 0.8rem; padding: 0.15rem 0.4rem; } .tilemap-controls-row > div > button, .tilemap-controls-row > div > span { font-size: 0.8rem; padding: 0.15rem 0.4rem; } .tilemap-controls-row > span { font-size: 0.8rem; } .tilemap-tabs-row { flex-wrap: wrap; gap: 0.3rem; } .tilemap-tabs-row > button { font-size: 0.8rem; padding: 0.15rem 0.4rem; } } `; document.head.appendChild(style); document.body.appendChild(tileMapTank); } tileMapTank.style.display = 'flex'; updateTileMapTank(); } catch (error) { console.error('Error showing tile map tank:', error); alert('Error showing tile map tank: ' + error.message); } } function hideTileMapTank() { if (tileMapTank) { tileMapTank.style.display = 'none'; } } function updateTileMapTank() { if (!tileMapTank) return; try { tileMapTank.innerHTML = ''; // Create controls row const controlsRow = document.createElement('div'); controlsRow.className = 'tilemap-controls-row'; controlsRow.style.display = 'flex'; controlsRow.style.alignItems = 'center'; controlsRow.style.gap = '0.5rem'; controlsRow.style.marginBottom = '.3rem'; const sizeLabel = document.createElement('div'); sizeLabel.textContent = 'Base Size:'; sizeLabel.style.color = '#4fc3f7'; sizeLabel.style.fontWeight = 'bold'; sizeLabel.style.fontSize = '.9rem'; const baseSizeControls = document.createElement('div'); baseSizeControls.style.display = 'flex'; baseSizeControls.style.alignItems = 'center'; baseSizeControls.style.gap = '.2rem'; const baseSizeMinus = document.createElement('button'); baseSizeMinus.textContent = '-'; baseSizeMinus.style.background = '#333'; baseSizeMinus.style.border = '1px solid #555'; baseSizeMinus.style.color = '#eee'; baseSizeMinus.style.padding = '.2rem .4rem'; baseSizeMinus.style.borderRadius = '.3rem'; baseSizeMinus.style.cursor = 'pointer'; baseSizeMinus.style.fontSize = '.9rem'; baseSizeMinus.disabled = baseTileSize <= 8; baseSizeMinus.addEventListener('click', () => changeBaseSize(baseTileSize / 2)); const baseSizeDisplay = document.createElement('span'); baseSizeDisplay.textContent = `${baseTileSize}px`; baseSizeDisplay.style.color = '#eee'; baseSizeDisplay.style.minWidth = '35px'; baseSizeDisplay.style.textAlign = 'center'; baseSizeDisplay.style.fontSize = '.9rem'; const baseSizePlus = document.createElement('button'); baseSizePlus.textContent = '+'; baseSizePlus.style.background = '#333'; baseSizePlus.style.border = '1px solid #555'; baseSizePlus.style.color = '#eee'; baseSizePlus.style.padding = '.2rem .4rem'; baseSizePlus.style.borderRadius = '.3rem'; baseSizePlus.style.cursor = 'pointer'; baseSizePlus.style.fontSize = '.9rem'; baseSizePlus.disabled = baseTileSize >= 128; baseSizePlus.addEventListener('click', () => changeBaseSize(baseTileSize * 2)); baseSizeControls.appendChild(baseSizeMinus); baseSizeControls.appendChild(baseSizeDisplay); baseSizeControls.appendChild(baseSizePlus); const gridSizeLabel = document.createElement('div'); gridSizeLabel.textContent = 'Grid:'; gridSizeLabel.style.color = '#4fc3f7'; gridSizeLabel.style.fontWeight = 'bold'; gridSizeLabel.style.fontSize = '.9rem'; const widthControls = document.createElement('div'); widthControls.style.display = 'flex'; widthControls.style.alignItems = 'center'; widthControls.style.gap = '.2rem'; const widthMinus = document.createElement('button'); widthMinus.textContent = '-'; widthMinus.style.background = '#333'; widthMinus.style.border = '1px solid #555'; widthMinus.style.color = '#eee'; widthMinus.style.padding = '.2rem .4rem'; widthMinus.style.borderRadius = '.3rem'; widthMinus.style.cursor = 'pointer'; widthMinus.style.fontSize = '.9rem'; widthMinus.addEventListener('click', () => resizeGrid(gridWidth - 1, gridHeight)); const widthDisplay = document.createElement('span'); widthDisplay.textContent = `${gridWidth}`; widthDisplay.style.color = '#eee'; widthDisplay.style.minWidth = '20px'; widthDisplay.style.textAlign = 'center'; widthDisplay.style.fontSize = '.9rem'; const widthPlus = document.createElement('button'); widthPlus.textContent = '+'; widthPlus.style.background = '#333'; widthPlus.style.border = '1px solid #555'; widthPlus.style.color = '#eee'; widthPlus.style.padding = '.2rem .4rem'; widthPlus.style.borderRadius = '.3rem'; widthPlus.style.cursor = 'pointer'; widthPlus.style.fontSize = '.9rem'; widthPlus.addEventListener('click', () => resizeGrid(gridWidth + 1, gridHeight)); const xLabel = document.createElement('span'); xLabel.textContent = 'ร—'; xLabel.style.color = '#bbb'; xLabel.style.fontSize = '.9rem'; const heightControls = document.createElement('div'); heightControls.style.display = 'flex'; heightControls.style.alignItems = 'center'; heightControls.style.gap = '.2rem'; const heightMinus = document.createElement('button'); heightMinus.textContent = '-'; heightMinus.style.background = '#333'; heightMinus.style.border = '1px solid #555'; heightMinus.style.color = '#eee'; heightMinus.style.padding = '.2rem .4rem'; heightMinus.style.borderRadius = '.3rem'; heightMinus.style.cursor = 'pointer'; heightMinus.style.fontSize = '.9rem'; heightMinus.addEventListener('click', () => resizeGrid(gridWidth, gridHeight - 1)); const heightDisplay = document.createElement('span'); heightDisplay.textContent = `${gridHeight}`; heightDisplay.style.color = '#eee'; heightDisplay.style.minWidth = '20px'; heightDisplay.style.textAlign = 'center'; heightDisplay.style.fontSize = '.9rem'; const heightPlus = document.createElement('button'); heightPlus.textContent = '+'; heightPlus.style.background = '#333'; heightPlus.style.border = '1px solid #555'; heightPlus.style.color = '#eee'; heightPlus.style.padding = '.2rem .4rem'; heightPlus.style.borderRadius = '.3rem'; heightPlus.style.cursor = 'pointer'; heightPlus.style.fontSize = '.9rem'; heightPlus.addEventListener('click', () => resizeGrid(gridWidth, gridHeight + 1)); widthControls.appendChild(widthMinus); widthControls.appendChild(widthDisplay); widthControls.appendChild(widthPlus); heightControls.appendChild(heightMinus); heightControls.appendChild(heightDisplay); heightControls.appendChild(heightPlus); controlsRow.appendChild(sizeLabel); controlsRow.appendChild(baseSizeControls); controlsRow.appendChild(gridSizeLabel); controlsRow.appendChild(widthControls); controlsRow.appendChild(xLabel); controlsRow.appendChild(heightControls); const clearBtn = document.createElement('button'); clearBtn.textContent = '๐Ÿงน Clear'; clearBtn.style.background = '#444'; clearBtn.style.border = '1px solid #666'; clearBtn.style.color = '#eee'; clearBtn.style.padding = '.3rem .5rem'; clearBtn.style.borderRadius = '.3rem'; clearBtn.style.cursor = 'pointer'; clearBtn.style.fontSize = '.9rem'; clearBtn.addEventListener('click', clearTileMap); controlsRow.appendChild(clearBtn); tileMapTank.appendChild(controlsRow); // Create tabbed interface const tabContainer = document.createElement('div'); tabContainer.style.display = 'flex'; tabContainer.style.flexDirection = 'column'; tabContainer.style.flex = '1'; tabContainer.style.overflow = 'hidden'; const tabsRow = document.createElement('div'); tabsRow.className = 'tilemap-tabs-row'; tabsRow.style.display = 'flex'; tabsRow.style.gap = '.3rem'; tabsRow.style.padding = '.2rem 0'; tabsRow.style.borderBottom = '1px solid #333'; const contentContainer = document.createElement('div'); contentContainer.style.flex = '1'; contentContainer.style.overflowY = 'auto'; // Get tiles from the single object's lines const lineTabs = []; const lineContents = []; if (window.WorkspaceAPI && window.WorkspaceAPI.workspace) { const workspace = window.WorkspaceAPI.workspace; const obj = workspace.objects[0]; if (obj) { // Ensure activeTab is valid if (activeTab >= obj.lines.length) { activeTab = 0; } obj.lines.forEach((line, index) => { // Create tab for each line const lineTab = document.createElement('button'); lineTab.textContent = line.name; lineTab.style.background = activeTab === index ? '#4fc3f7' : '#333'; lineTab.style.border = '1px solid #555'; lineTab.style.color = activeTab === index ? '#000' : '#eee'; lineTab.style.padding = '.2rem .4rem'; lineTab.style.borderRadius = '.3rem'; lineTab.style.cursor = 'pointer'; lineTab.style.fontSize = '.9rem'; lineTab.dataset.tabId = `line-${index}`; // Create content for each line const lineContainer = document.createElement('div'); lineContainer.id = `line-${index}-content`; lineContainer.style.display = activeTab === index ? 'block' : 'none'; lineContainer.style.padding = '.2rem'; const contentRow = document.createElement('div'); contentRow.style.display = 'flex'; contentRow.style.alignItems = 'center'; contentRow.style.gap = '.3rem'; // Add eraser to each line tab const eraser = document.createElement('div'); eraser.className = 'tilemap-source-tile eraser'; eraser.style.position = 'relative'; eraser.style.width = '50px'; eraser.style.height = '50px'; eraser.style.border = selectedTile === null ? '3px solid #ff4444' : '1px solid #333'; eraser.style.borderRadius = '.35rem'; eraser.style.background = '#333'; eraser.style.cursor = 'pointer'; eraser.style.flexShrink = '0'; eraser.style.display = 'flex'; eraser.style.alignItems = 'center'; eraser.style.justifyContent = 'center'; eraser.style.fontSize = '16px'; eraser.textContent = '๐Ÿ—‘๏ธ'; eraser.title = 'Click to select eraser'; eraser.addEventListener('click', () => { console.log('Eraser clicked'); selectedTile = null; updateTileMapTank(); }); contentRow.appendChild(eraser); // Tiles container const tilesRow = document.createElement('div'); tilesRow.style.display = 'flex'; tilesRow.style.gap = '.3rem'; tilesRow.style.overflowX = 'auto'; line.items.forEach((tile, tileIndex) => { const { scaleX, scaleY } = getTileScale(tile); const tileEl = document.createElement('div'); tileEl.className = 'tilemap-source-tile'; tileEl.style.position = 'relative'; tileEl.style.width = '50px'; tileEl.style.height = '50px'; tileEl.style.border = selectedTile && selectedTile.id === tile.id ? '3px solid #4fc3f7' : '1px solid #333'; tileEl.style.borderRadius = '.35rem'; tileEl.style.overflow = 'hidden'; tileEl.style.background = '#111'; tileEl.style.cursor = 'pointer'; tileEl.style.flexShrink = '0'; const img = document.createElement('img'); img.src = tile.thumbDataURL; img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'contain'; img.style.imageRendering = 'pixelated'; img.draggable = false; const badge = document.createElement('div'); badge.style.position = 'absolute'; badge.style.left = '2px'; badge.style.top = '2px'; badge.style.background = '#4fc3f7'; badge.style.color = '#000'; badge.style.borderRadius = '.2rem'; badge.style.padding = '.05rem .2rem'; badge.style.fontWeight = 'bold'; badge.style.fontSize = '.6rem'; badge.textContent = `#${tile.id}`; if (scaleX > 1 || scaleY > 1) { const sizeIndicator = document.createElement('div'); sizeIndicator.style.position = 'absolute'; sizeIndicator.style.bottom = '2px'; sizeIndicator.style.right = '2px'; sizeIndicator.style.background = 'rgba(0,0,0,0.8)'; sizeIndicator.style.color = '#ffff00'; sizeIndicator.style.padding = '.05rem .2rem'; sizeIndicator.style.borderRadius = '.2rem'; sizeIndicator.style.fontSize = '.6rem'; sizeIndicator.style.fontWeight = 'bold'; sizeIndicator.textContent = `${scaleX}ร—${scaleY}`; tileEl.appendChild(sizeIndicator); } tileEl.appendChild(img); tileEl.appendChild(badge); tileEl.tileData = tile; tileEl.title = `${tile.w}ร—${tile.h}px (${scaleX}ร—${scaleY} squares)`; tileEl.addEventListener('click', () => { console.log(`Tile ${tile.id} clicked in line ${index}`); selectedTile = tile; activeTab = index; updateTileMapTank(); }); tilesRow.appendChild(tileEl); }); contentRow.appendChild(tilesRow); lineContainer.appendChild(contentRow); lineTabs.push(lineTab); lineContents.push(lineContainer); tabsRow.appendChild(lineTab); contentContainer.appendChild(lineContainer); }); } else { // No lines available, show a message const noLinesMessage = document.createElement('div'); noLinesMessage.textContent = 'No lines available'; noLinesMessage.style.color = '#eee'; noLinesMessage.style.padding = '.5rem'; contentContainer.appendChild(noLinesMessage); } } else { // No workspace data, show a message const noDataMessage = document.createElement('div'); noDataMessage.textContent = 'No workspace data available'; noDataMessage.style.color = '#eee'; noDataMessage.style.padding = '.5rem'; contentContainer.appendChild(noDataMessage); } // Add tab click handlers lineTabs.forEach((tab, index) => { tab.addEventListener('click', () => { console.log(`Line tab ${index} clicked`); activeTab = index; updateTileMapTank(); }); }); tabContainer.appendChild(tabsRow); tabContainer.appendChild(contentContainer); tileMapTank.appendChild(tabContainer); console.log(`Active tab: ${activeTab}`); } catch (error) { console.error('Error updating tile map tank:', error); alert('Error updating tile map tank: ' + error.message); } } /* ---------- Export Functions ---------- */ function clearTileMap() { try { for (let y = 0; y < gridHeight; y++) { for (let x = 0; x < gridWidth; x++) { if (tileMapGrid[y][x]) { removeTileFromGrid(x, y); } } } } catch (error) { alert('Error clearing tile map: ' + error.message); } } function exportTileMap() { try { const mapData = []; for (let y = 0; y < gridHeight; y++) { const row = []; for (let x = 0; x < gridWidth; x++) { if (tileMapGrid[y][x]) { row.push(tileMapGrid[y][x].data.id); } else { row.push(0); // Empty cell } } mapData.push(row); } const exportData = { width: gridWidth, height: gridHeight, baseTileSize: baseTileSize, map: mapData, timestamp: new Date().toISOString() }; const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'tilemap.json'; a.click(); URL.revokeObjectURL(url); } catch (error) { alert('Error exporting tile map: ' + error.message); } } /* ---------- Event Handlers ---------- */ function initTilemapEvents() { try { if (tilemapBtn) { tilemapBtn.addEventListener('click', toggleTilemapMode); } // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (isTilemapMode) { if (e.key === 'c' && e.ctrlKey) { e.preventDefault(); clearTileMap(); } else if (e.key === 's' && e.ctrlKey) { e.preventDefault(); exportTileMap(); } else if (e.key === 'Escape') { disableTilemapMode(); } else if (e.key === 'e' || e.key === 'E') { // Quick eraser selection selectedTile = null; updateTileMapTank(); } } }); } catch (error) { alert('Failed to initialize tilemap events: ' + error.message); } } /* ---------- Initialization ---------- */ function initializeTilemap() { try { // Get DOM elements - only the ones this module owns tilemapBtn = document.getElementById('tilemapBtn'); // Check required DOM elements if (!tilemapBtn) { throw new Error('Tilemap button not found'); } initTilemapEvents(); } catch (error) { alert('Tilemap module failed to initialize: ' + error.message); // Don't throw here since tilemap is an optional feature } } // Export API for other modules window.TilemapAPI = { enableTilemapMode, disableTilemapMode, toggleTilemapMode, isTilemapMode: () => isTilemapMode, clearTileMap, exportTileMap, updateTileMapTank, tileMapGrid: () => [...tileMapGrid] // Return copy };