📜
core.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
/* ---------- Constants & State ---------- */ const ONE_BASED = false; // set true for r1c1 style labels // Settings defaults const defaults = { tileWidth:16, tileHeight:16, xOffset:0, yOffset:0, Hspacing:0, Vspacing:0 }; let settings = {...defaults}; // Image & viewport state let imgEl = null, imgW = 0, imgH = 0; let scale = 1, tx = 0, ty = 0; const MIN_SCALE = 0.25, MAX_SCALE = 16; // Touch/pointer tracking const pts = new Map(); let lastPan = null, pinchRef = null; /* ---------- DOM Elements ---------- */ let settingsBtn, imagesBtn, objectViewBtn, resetBtn, settingsPanel, imagesPanel; let imgLayer, gridEl, meta, stage, viewport; /* ---------- Utility Functions ---------- */ function rid() { return Math.random().toString(36).slice(2,10) + Math.random().toString(36).slice(2,10); } function dist(a, b) { const dx = a.x - b.x, dy = a.y - b.y; return Math.hypot(dx, dy); } function screenToWorld(x, y) { const r = stage.getBoundingClientRect(); const sx = x - r.left, sy = y - r.top; return { wx:(sx - tx)/scale, wy:(sy - ty)/scale }; } /* ---------- Panel Management ---------- */ function closeAllPanels() { settingsPanel.classList.remove('open'); imagesPanel.classList.remove('open'); } function togglePanel(p) { const isOpen = p.classList.contains('open'); closeAllPanels(); if(!isOpen) p.classList.add('open'); } /* ---------- Settings Management ---------- */ const stepperInputs = {}; function initSteppers() { try { alert('Core: Setting up steppers...'); document.querySelectorAll('.stepper').forEach(st => { const key = st.dataset.key; const input = st.querySelector('input'); const buttons = st.querySelectorAll('button'); if (!key) throw new Error(`Stepper missing data-key attribute`); if (!input) throw new Error(`Stepper for ${key} missing input element`); if (buttons.length < 2) throw new Error(`Stepper for ${key} missing buttons`); const [minus, plus] = buttons; stepperInputs[key] = input; input.value = settings[key]; const stepSize = (key === 'tileWidth' || key === 'tileHeight') ? 8 : 1; const commit = () => { try { let v = parseInt(input.value) || 0; if (key === 'tileWidth' || key === 'tileHeight') { v = Math.max(1, Math.round(v/8)*8); } else { v = Math.max(0, v); } settings[key] = v; input.value = v; drawGrid(); } catch (err) { alert(`Error updating ${key}: ${err.message}`); } }; minus.addEventListener('click', () => { try { input.value = (parseInt(input.value) || 0) - stepSize; commit(); } catch (err) { alert(`Error in minus button for ${key}: ${err.message}`); } }); plus.addEventListener('click', () => { try { input.value = (parseInt(input.value) || 0) + stepSize; commit(); } catch (err) { alert(`Error in plus button for ${key}: ${err.message}`); } }); input.addEventListener('change', commit); }); alert('Core: Steppers initialized successfully'); } catch (error) { alert(`Failed to initialize settings controls: ${error.message}`); throw error; } } function setTileSize(n) { if (!Number.isFinite(n) || n <= 0) return; settings.tileWidth = settings.tileHeight = n; if (stepperInputs.tileWidth) stepperInputs.tileWidth.value = n; if (stepperInputs.tileHeight) stepperInputs.tileHeight.value = n; drawGrid(); } /* ---------- Image Management ---------- */ function setImage(src) { const img = new Image(); img.decoding = 'async'; img.onload = () => { imgW = img.naturalWidth; imgH = img.naturalHeight; imgLayer.innerHTML = ''; img.style.position = 'absolute'; img.style.left = '0px'; img.style.top = '0px'; img.style.width = imgW + 'px'; img.style.height = imgH + 'px'; imgLayer.appendChild(img); gridEl.style.width = imgW + 'px'; gridEl.style.height = imgH + 'px'; meta.textContent = `Image: ${imgW}×${imgH} — tap tiles to add to the selected line`; // Set the imgEl variable after image loads imgEl = img; drawGrid(); centerView(); }; img.onerror = () => { alert(`Failed to load image: ${src}`); }; img.src = src; } /* ---------- Grid Rendering ---------- */ function drawGrid() { if (!imgW || !imgH) { gridEl.innerHTML = ''; return; } const tw = settings.tileWidth | 0, th = settings.tileHeight | 0; const ox = settings.xOffset | 0, oy = settings.yOffset | 0; const hs = settings.Hspacing | 0, vs = settings.Vspacing | 0; gridEl.innerHTML = ''; let row = 0; for (let y = oy; y + th <= imgH; y += th + vs, row++) { let col = 0; for (let x = ox; x + tw <= imgW; x += tw + hs, col++) { const d = document.createElement('div'); d.className = 'tile'; d.style.left = x + 'px'; d.style.top = y + 'px'; d.style.width = tw + 'px'; d.style.height = th + 'px'; d.dataset.x = x; d.dataset.y = y; d.dataset.w = tw; d.dataset.h = th; d.dataset.row = row; d.dataset.col = col; gridEl.appendChild(d); } } } /* ---------- Viewport Controls ---------- */ function applyTransform() { viewport.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`; } function centerView() { if(!imgW || !imgH) return; const r = stage.getBoundingClientRect(); scale = Math.min(Math.max(1, Math.min(r.width/imgW, r.height/imgH)), 2); tx = (r.width - imgW*scale)/2; ty = (r.height - imgH*scale)/2; applyTransform(); } function initViewportEvents() { try { alert('Core: Setting up viewport events...'); function endPointer(e) { try { stage.releasePointerCapture(e.pointerId); } catch {} pts.delete(e.pointerId); if(pts.size < 2) pinchRef = null; if(pts.size === 0) lastPan = null; } stage.addEventListener('pointerdown', e => { stage.setPointerCapture(e.pointerId); pts.set(e.pointerId, {x:e.clientX, y:e.clientY}); if (pts.size === 1) { lastPan = {x:e.clientX, y:e.clientY}; } else if (pts.size === 2) { const [a,b] = Array.from(pts.values()); pinchRef = { d0: dist(a,b), m0: {x:(a.x+b.x)/2, y:(a.y+b.y)/2}, s0: scale }; } }); stage.addEventListener('pointermove', e => { if(!pts.has(e.pointerId)) return; pts.set(e.pointerId, {x:e.clientX, y:e.clientY}); if (pts.size === 1 && lastPan) { const p = pts.get(e.pointerId); tx += p.x - lastPan.x; ty += p.y - lastPan.y; lastPan = {x:p.x, y:p.y}; applyTransform(); } else if (pts.size === 2 && pinchRef) { const [a,b] = Array.from(pts.values()); const d1 = dist(a,b); if(pinchRef.d0 > 0) { const world = screenToWorld(pinchRef.m0.x, pinchRef.m0.y); let newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, pinchRef.s0 * (d1/pinchRef.d0))); const r = stage.getBoundingClientRect(); const sx = pinchRef.m0.x - r.left; const sy = pinchRef.m0.y - r.top; tx = sx - world.wx * newScale; ty = sy - world.wy * newScale; scale = newScale; applyTransform(); } } }); stage.addEventListener('pointerup', endPointer); stage.addEventListener('pointercancel', endPointer); stage.addEventListener('pointerleave', e => { if(pts.has(e.pointerId)) endPointer(e); }); // Double tap to center let lastTap = 0; stage.addEventListener('pointerdown', e => { const now = performance.now(); if(now - lastTap < 300) centerView(); lastTap = now; }, {capture:true}); // Mouse wheel zoom stage.addEventListener('wheel', e => { if(!imgW || !imgH) return; e.preventDefault(); const k = Math.exp(-e.deltaY * 0.0015); const r = stage.getBoundingClientRect(); const sx = e.clientX - r.left; const sy = e.clientY - r.top; const world = screenToWorld(e.clientX, e.clientY); let newScale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, scale * k)); tx = sx - world.wx * newScale; ty = sy - world.wy * newScale; scale = newScale; applyTransform(); }, {passive:false}); alert('Core: Viewport events initialized'); } catch (error) { alert(`Failed to initialize viewport events: ${error.message}`); throw error; } } /* ---------- Event Handlers ---------- */ function initPanelEvents() { try { alert('Core: Setting up panel events...'); settingsBtn.addEventListener('click', () => togglePanel(settingsPanel)); imagesBtn.addEventListener('click', () => { togglePanel(imagesPanel); if (!imagesPanel.dataset.inited) { imagesPanel.dataset.inited = '1'; if (window.openSub) window.openSub(''); } }); resetBtn.addEventListener('click', centerView); alert('Core: Panel events initialized'); } catch (error) { alert(`Failed to initialize panel events: ${error.message}`); throw error; } } /* ---------- Initialization ---------- */ function initializeCore() { try { // Get DOM elements settingsBtn = document.getElementById('settingsBtn'); imagesBtn = document.getElementById('imagesBtn'); objectViewBtn = document.getElementById('objectViewBtn'); resetBtn = document.getElementById('resetBtn'); settingsPanel = document.getElementById('settingsPanel'); imagesPanel = document.getElementById('imagesPanel'); imgLayer = document.getElementById('imgLayer'); gridEl = document.getElementById('grid'); meta = document.getElementById('meta'); stage = document.getElementById('stage'); viewport = document.getElementById('viewport'); if (!settingsBtn || !stage) { throw new Error('Required DOM elements not found'); } initSteppers(); initViewportEvents(); initPanelEvents(); } catch (error) { alert(`Core module failed to initialize: ${error.message}`); throw error; } } // Export functions for other modules window.CoreAPI = { setImage, setTileSize, drawGrid, centerView, rid, ONE_BASED, get imgEl() { return imgEl; }, get imgW() { return imgW; }, get imgH() { return imgH; }, settings };