Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | 'use client' import { useState, useEffect } from 'react' import { buildFinalState } from './buildFinalStates' import { renderConstruction } from './renderConstruction' import { renderEqualityMarks } from './renderEqualityMarks' import { getAllPoints, getAllCircles, getPoint, getRadius } from '../engine/constructionState' import { PROP_REGISTRY } from '../propositions/registry' import { IMPLEMENTED_PROPS } from '../data/propositionGraph' import type { ConstructionState, EuclidViewportState } from '../types' const PREVIEW_W = 260 const PREVIEW_H = 200 const PADDING_FRACTION = 0.15 /** * Compute a viewport that fits all geometric elements (points + circle extents) * into the given pixel dimensions with padding. */ function computeFitViewport(state: ConstructionState, w: number, h: number): EuclidViewportState { let minX = Infinity let maxX = -Infinity let minY = Infinity let maxY = -Infinity for (const pt of getAllPoints(state)) { minX = Math.min(minX, pt.x) maxX = Math.max(maxX, pt.x) minY = Math.min(minY, pt.y) maxY = Math.max(maxY, pt.y) } for (const circle of getAllCircles(state)) { const center = getPoint(state, circle.centerId) if (!center) continue const r = getRadius(state, circle.id) minX = Math.min(minX, center.x - r) maxX = Math.max(maxX, center.x + r) minY = Math.min(minY, center.y - r) maxY = Math.max(maxY, center.y + r) } // Add padding const rangeX = maxX - minX const rangeY = maxY - minY const padX = rangeX * PADDING_FRACTION const padY = rangeY * PADDING_FRACTION minX -= padX maxX += padX minY -= padY maxY += padY const cx = (minX + maxX) / 2 const cy = (minY + maxY) / 2 const scaleX = w / (maxX - minX) const scaleY = h / (maxY - minY) const pixelsPerUnit = Math.min(scaleX, scaleY) return { center: { x: cx, y: cy }, pixelsPerUnit } } /** * Renders the final construction state for each implemented proposition * to an offscreen canvas, returning a Map of propId → data URL. * Computed once on mount and cached. */ export function usePropPreviews(): Map<number, string> { const [previews, setPreviews] = useState<Map<number, string>>(() => new Map()) useEffect(() => { const map = new Map<number, string>() const idle = { tag: 'idle' as const } for (const propId of IMPLEMENTED_PROPS) { const result = buildFinalState(propId) if (!result) continue const prop = PROP_REGISTRY[propId] if (!prop) continue const viewport = computeFitViewport(result.state, PREVIEW_W, PREVIEW_H) const canvas = document.createElement('canvas') canvas.width = PREVIEW_W canvas.height = PREVIEW_H const ctx = canvas.getContext('2d') if (!ctx) continue renderConstruction( ctx, result.state, viewport, PREVIEW_W, PREVIEW_H, idle, // compassPhase: idle idle, // straightedgePhase: idle null, // pointerWorld null, // snappedPointId [], // candidates 0, // nextColorIndex null, // candidateFilter true, // isComplete prop.resultSegments, undefined, // hiddenElementIds true // transparentBg ) // Render equality tick marks on segments with proven equalities if (result.factStore.facts.length > 0) { renderEqualityMarks( ctx, result.state, viewport, PREVIEW_W, PREVIEW_H, result.factStore, undefined, prop.resultSegments ) } map.set(propId, canvas.toDataURL('image/png')) } setPreviews(map) }, []) return previews } |