- 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>
81 lines
2.3 KiB
TypeScript
81 lines
2.3 KiB
TypeScript
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,
|
|
};
|
|
}
|