Phase 1: Core hex engine, Leaflet overlay, terrain painting UI
- core/: Pure TS hex engine (axial coords, hex grid, terrain types, edge connectivity with constraint solver, HexMap state model) - src/map/: Leaflet L.CRS.Simple map init, Canvas-based hex overlay layer (L.GridLayer), click/edge interaction detection - src/ui/: Sidebar with toolbar (Select/Paint/Feature modes), terrain picker, hex inspector, map settings (hex size, grid, opacity) - pipeline/: Tile pyramid generator (sharp, from source image) - tests/: 32 passing tests for coords, hex-grid, edge-connectivity - Uses Kiepenkerl tiles (symlinked) for development Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
80
src/ui/terrain-picker.ts
Normal file
80
src/ui/terrain-picker.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type { TerrainType } from '../../core/types.js';
|
||||
import { getAreaTerrains, getLinearTerrains } from '../../core/terrain.js';
|
||||
import type { ToolMode } from './toolbar.js';
|
||||
|
||||
export function createTerrainPicker(
|
||||
container: HTMLElement,
|
||||
onChange: (terrain: TerrainType) => void,
|
||||
): {
|
||||
setMode: (mode: ToolMode) => void;
|
||||
getSelected: () => TerrainType | null;
|
||||
} {
|
||||
let selected: TerrainType | null = null;
|
||||
let currentMode: ToolMode = 'select';
|
||||
|
||||
function render() {
|
||||
container.innerHTML = '';
|
||||
|
||||
if (currentMode === 'select') {
|
||||
container.innerHTML = '<div style="color:#666;font-size:12px">Click a hex to inspect it</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const terrains = currentMode === 'paint' ? getAreaTerrains() : getLinearTerrains();
|
||||
const label = currentMode === 'paint' ? 'Area Terrain' : 'Linear Features';
|
||||
|
||||
const sectionLabel = document.createElement('div');
|
||||
sectionLabel.className = 'terrain-section-label';
|
||||
sectionLabel.textContent = label;
|
||||
container.appendChild(sectionLabel);
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'terrain-grid';
|
||||
|
||||
for (const terrain of terrains) {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'terrain-btn';
|
||||
if (selected?.id === terrain.id) btn.classList.add('selected');
|
||||
|
||||
const swatch = document.createElement('span');
|
||||
swatch.className = 'terrain-swatch';
|
||||
swatch.style.backgroundColor = terrain.color;
|
||||
|
||||
const name = document.createElement('span');
|
||||
name.textContent = terrain.name;
|
||||
|
||||
btn.appendChild(swatch);
|
||||
btn.appendChild(name);
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
selected = terrain;
|
||||
onChange(terrain);
|
||||
render();
|
||||
});
|
||||
|
||||
grid.appendChild(btn);
|
||||
}
|
||||
|
||||
container.appendChild(grid);
|
||||
|
||||
// Auto-select first if nothing selected
|
||||
if (!selected || !terrains.find(t => t.id === selected!.id)) {
|
||||
selected = terrains[0] ?? null;
|
||||
if (selected) onChange(selected);
|
||||
// Re-render to show selection
|
||||
const firstBtn = grid.querySelector('.terrain-btn');
|
||||
firstBtn?.classList.add('selected');
|
||||
}
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
return {
|
||||
setMode(mode: ToolMode) {
|
||||
currentMode = mode;
|
||||
selected = null;
|
||||
render();
|
||||
},
|
||||
getSelected: () => selected,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user