📜
workspace_copy2.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
/* ---------- Workspace Data Model ---------- */ /* workspace schema: workspace = { objects: [ { id, name, attributes:[{key,value}], lines:[ {id, name, attributes:[{key,value}], items:[{x,y,w,h,row,col,badge,thumbDataURL}]} ], activeLineId } ], activeObjectId, counters: { objects: 0, lines: 0 } } */ const workspace = { objects: [], activeObjectId: null, counters: { objects: 0, lines: 0 } }; /* ---------- DOM Elements ---------- */ let objectsBar, objectPills, addObjectBtn, renameObjectBtn, removeObjectBtn; let linesBar, linePills, addLineBtn, renameLineBtn, removeLineBtn, clearLineBtn; let tankStrip, tankCount, workspaceGridEl; /* ---------- ID Management ---------- */ function renumberObjectIds(obj) { let counter = 1; // Set object ID let objIdAttr = obj.attributes.find(attr => attr.key === 'id'); if (objIdAttr) { objIdAttr.value = counter.toString(); } counter++; // Renumber lines and their tiles sequentially obj.lines.forEach(line => { let lineIdAttr = line.attributes.find(attr => attr.key === 'id'); if (lineIdAttr) { lineIdAttr.value = counter.toString(); } counter++; line.items.forEach(tile => { tile.id = counter; counter++; }); }); // Redraw grid to update ID previews if (window.CoreAPI && typeof window.CoreAPI.drawGrid === 'function') { window.CoreAPI.drawGrid(); } } /* ---------- Active Getters ---------- */ function activeObject() { return workspace.objects[0] || null; } function activeLine() { const o = activeObject(); if (!o) return null; return o.lines.find(l => l.id === o.activeLineId) || null; } /* ---------- Object Management ---------- */ function renderObjects() { try { objectPills.innerHTML = ''; const o = activeObject(); if (o) { const b = document.createElement('button'); b.className = 'obj-pill active'; b.textContent = o.name; b.ondblclick = () => { if (window.AttributesAPI && typeof window.AttributesAPI.openObjectView === 'function') { window.AttributesAPI.openObjectView(); } }; objectPills.appendChild(b); } } catch (error) { alert(`Error rendering objects: ${error.message}`); } } function addObject() { try { if (workspace.objects.length >= 1) { alert('Only one object is allowed in the workspace.'); return; } const obj = { id: window.CoreAPI.rid(), name: 'Main Object', attributes: [ { key: 'id', value: '1' } ], lines: [{ id: window.CoreAPI.rid(), name: 'Line 1', attributes: [ { key: 'id', value: '2' }, { key: 'url', value: '' } ], items: [] }], activeLineId: null }; obj.activeLineId = obj.lines[0].id; workspace.objects.push(obj); workspace.activeObjectId = obj.id; renumberObjectIds(obj); renderObjects(); renderLines(); renderActiveTank(); } catch (error) { alert(`Error adding object: ${error.message}`); } } function renameObject() { try { const o = activeObject(); if (!o) return; const name = prompt('Rename object:', o.name); if (name && name.trim()) { o.name = name.trim(); renderObjects(); } } catch (error) { alert(`Error renaming object: ${error.message}`); } } function removeObject() { alert('Cannot remove the only object in the workspace.'); } /* ---------- Line Management ---------- */ function renderLines() { try { const o = activeObject(); linePills.innerHTML = ''; if (!o) return; for (const l of o.lines) { const b = document.createElement('button'); b.className = 'line-pill' + (l.id === o.activeLineId ? ' active' : ''); b.textContent = l.name; b.onclick = () => { o.activeLineId = l.id; renderLines(); renderActiveTank(); }; linePills.appendChild(b); } } catch (error) { alert(`Error rendering lines: ${error.message}`); } } function addLine() { try { const o = activeObject(); if (!o) return; const idx = o.lines.length + 1; const line = { id: window.CoreAPI.rid(), name: `Line ${idx}`, attributes: [ { key: 'id', value: '0' }, { key: 'url', value: '' } ], items: [] }; o.lines.push(line); o.activeLineId = line.id; renumberObjectIds(o); renderLines(); renderActiveTank(); } catch (error) { alert(`Error adding line: ${error.message}`); } } function renameLine() { try { const o = activeObject(); if (!o) return; const l = activeLine(); if (!l) return; const name = prompt('Rename line:', l.name); if (name && name.trim()) { l.name = name.trim(); renderLines(); } } catch (error) { alert(`Error renaming line: ${error.message}`); } } function removeLine() { try { const o = activeObject(); if (!o) return; if (o.lines.length === 1) { alert('At least one line is required.'); return; } const idx = o.lines.findIndex(l => l.id === o.activeLineId); if (idx >= 0) { o.lines.splice(idx, 1); const next = o.lines[Math.max(0, idx - 1)]; o.activeLineId = next.id; renumberObjectIds(o); renderLines(); renderActiveTank(); } } catch (error) { alert(`Error removing line: ${error.message}`); } } function clearLine() { try { const l = activeLine(); if (!l) return; l.items = []; const o = activeObject(); if (o) renumberObjectIds(o); renderActiveTank(); } catch (error) { alert(`Error clearing line: ${error.message}`); } } /* ---------- Tank Rendering ---------- */ function renderActiveTank() { try { const l = activeLine(); tankStrip.innerHTML = ''; if (!l) { tankCount.textContent = '0 items'; return; } l.items.forEach((t, i) => { const item = document.createElement('div'); item.className = 'tank-item'; item.title = `Index: ${t.index || 'N/A'}, Frame: ${t.frameKey || 'N/A'}\nSize: ${t.w}×${t.h} | Center: (${t.centerX || t.x},${t.centerY || t.y})\nType: ${t.type || 'static'}`; const img = document.createElement('img'); img.src = t.thumbDataURL; const badge = document.createElement('div'); badge.className = 'badge'; badge.textContent = t.id !== undefined ? `#${t.id}` : t.badge; const remove = document.createElement('button'); remove.className = 'remove'; remove.textContent = '×'; remove.title = 'Remove'; remove.onclick = () => { l.items.splice(i, 1); const o = activeObject(); if (o) renumberObjectIds(o); renderActiveTank(); }; item.appendChild(img); item.appendChild(badge); item.appendChild(remove); tankStrip.appendChild(item); }); const totalFrames = l.items.reduce((sum, item) => sum + (item.frameCount || 1), 0); tankCount.textContent = `${l.items.length} tile${l.items.length === 1 ? '' : 's'} (${totalFrames} frame${totalFrames === 1 ? '' : 's'})`; } catch (error) { alert(`Error rendering tank: ${error.message}`); } } /* ---------- Tile Capture ---------- */ function initTileCapture() { try { workspaceGridEl.addEventListener('click', (e) => { try { const tile = e.target.closest('.tile'); if (!tile) return; if (!window.CoreAPI.imgEl) { alert('Load an image first from the gallery'); return; } e.stopPropagation(); const x = parseInt(tile.dataset.x, 10); const y = parseInt(tile.dataset.y, 10); const w = parseInt(tile.dataset.w, 10); const h = parseInt(tile.dataset.h, 10); let row = parseInt(tile.dataset.row, 10); let col = parseInt(tile.dataset.col, 10); if (window.CoreAPI.ONE_BASED) { row += 1; col += 1; } const badgeText = `r${row}c${col}`; const cv = document.createElement('canvas'); cv.width = w; cv.height = h; const cctx = cv.getContext('2d'); cctx.imageSmoothingEnabled = false; cctx.drawImage(window.CoreAPI.imgEl, x, y, w, h, 0, 0, w, h); const dataURL = cv.toDataURL('image/png'); const l = activeLine(); if (!l) { alert('No active line selected'); return; } const currentObject = activeObject(); if (!currentObject) { alert('No active object'); return; } const currentImageUrl = window.CoreAPI.imgEl?.src || ''; const imgWidth = window.CoreAPI.imgW; const imgHeight = window.CoreAPI.imgH; const tileWidth = w; const tileHeight = h; if (l.items.length > 0) { const lineUrlAttr = l.attributes.find(attr => attr.key === 'url'); const lineImageUrl = lineUrlAttr ? lineUrlAttr.value : ''; if (lineImageUrl && lineImageUrl !== currentImageUrl) { alert(`This line already contains tiles from a different image.\nCreate a new line for tiles from this image.`); return; } } const cols = Math.floor(imgWidth / tileWidth); const rows = Math.floor(imgHeight / tileHeight); const index = row * cols + col; const centerX = x + (tileWidth / 2); const centerY = y + (tileHeight / 2); const imageBaseName = currentImageUrl.split('/').pop().split('.')[0] || 'sprite'; const atlasKey = `${imageBaseName}_atlas`; const frameKey = `${imageBaseName}_${index}`; const tileData = { x, y, w: tileWidth, h: tileHeight, row, col, badge: badgeText, thumbDataURL: dataURL, id: 0, url: currentImageUrl, imgWidth, imgHeight, tileWidth, tileHeight, rows, cols, index, frames: [index], frameCount: 1, centerX, centerY, atlasKey, frameKey, type: 'static' }; l.items.push(tileData); if (l.items.length === 1) { const autoAttrs = { url: currentImageUrl, imgWidth: imgWidth.toString(), imgHeight: imgHeight.toString(), tileWidth: tileWidth.toString(), tileHeight: tileHeight.toString(), rows: rows.toString(), cols: cols.toString() }; Object.entries(autoAttrs).forEach(([key, value]) => { let attr = l.attributes.find(a => a.key === key); if (attr) { attr.value = value; } else { l.attributes.push({ key, value }); } }); const defaultChangeableAttrs = { frameRate: '10', playMode: 'loop', 'origin.x': '0.5', 'origin.y': '0.5', 'physics.bodyType': 'static', collides: 'false', tags: '', blockType: 'decor', license: 'CC0' }; Object.entries(defaultChangeableAttrs).forEach(([key, value]) => { if (!l.attributes.find(a => a.key === key)) { l.attributes.push({ key, value }); } }); } renumberObjectIds(currentObject); renderActiveTank(); } catch (error) { alert(`Error capturing tile: ${error.message}`); } }); } catch (error) { alert(`Failed to initialize tile capture: ${error.message}`); throw error; } } /* ---------- Event Handlers ---------- */ function initWorkspaceEvents() { try { addObjectBtn.addEventListener('click', addObject); renameObjectBtn.addEventListener('click', renameObject); // removeObjectBtn event not needed since removal is disabled addLineBtn.addEventListener('click', addLine); renameLineBtn.addEventListener('click', renameLine); removeLineBtn.addEventListener('click', removeLine); clearLineBtn.addEventListener('click', clearLine); } catch (error) { alert(`Failed to initialize workspace events: ${error.message}`); throw error; } } /* ---------- Initialization ---------- */ function initializeWorkspace() { try { // Get DOM elements objectsBar = document.getElementById('objectsBar'); objectPills = document.getElementById('objectPills'); addObjectBtn = document.getElementById('addObjectBtn'); renameObjectBtn = document.getElementById('renameObjectBtn'); removeObjectBtn = document.getElementById('removeObjectBtn'); linesBar = document.getElementById('linesBar'); linePills = document.getElementById('linePills'); addLineBtn = document.getElementById('addLineBtn'); renameLineBtn = document.getElementById('renameLineBtn'); removeLineBtn = document.getElementById('removeLineBtn'); clearLineBtn = document.getElementById('clearLineBtn'); tankStrip = document.getElementById('tankStrip'); tankCount = document.getElementById('tankCount'); workspaceGridEl = document.getElementById('grid'); // Check required DOM elements and dependencies if (!objectsBar || !tankStrip || !workspaceGridEl) { throw new Error('Required DOM elements not found'); } if (!window.CoreAPI || typeof window.CoreAPI.rid !== 'function') { throw new Error('CoreAPI not available'); } // Create single object const initialObject = { id: window.CoreAPI.rid(), name: 'Main Object', attributes: [ { key: 'id', value: '1' } ], lines: [{ id: window.CoreAPI.rid(), name: 'Line 1', attributes: [ { key: 'id', value: '2' }, { key: 'url', value: '' } ], items: [] }], activeLineId: null }; initialObject.activeLineId = initialObject.lines[0].id; workspace.objects.push(initialObject); workspace.activeObjectId = initialObject.id; renumberObjectIds(initialObject); initWorkspaceEvents(); initTileCapture(); renderObjects(); renderLines(); renderActiveTank(); } catch (error) { alert(`Workspace module failed to initialize: ${error.message}`); throw error; } } // Export API for other modules window.WorkspaceAPI = { workspace, activeObject, activeLine, renderObjects, renderLines, renderActiveTank };