feat(worksheets): enhance addition worksheets with ten-frames and refinements
- Add ten-frames visualization for regrouping concepts - Stacked vertical layout (top frame = carry, bottom frame = current place) - Square cells with differentiated borders (0.8pt outer, 0.4pt internal at 30% opacity) - Place value color coding matches answer boxes - Ten-frames sized at 90% of cell width for proper alignment - 2.5pt gap between tens and ones ten-frames for visual clarity - Add "for all place values" option for ten-frames - Show ten-frames only for regrouping (default) or for all problems - Dynamic label updates based on checkbox state - Indented sub-option UI in config panel - Improve typography with "New Computer Modern Math" font - Fix layout calculations - Remove gutter-based spacing for consistent problem sizing - Dynamic cell size based on ten-frames option - Proper height allocation for ten-frames row - Add cache busting for ten-frames settings in preview 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@ export async function POST(request: NextRequest) {
|
||||
if (!validation.isValid || !validation.config) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid configuration', errors: validation.errors },
|
||||
{ status: 400 },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export async function POST(request: NextRequest) {
|
||||
config.pAnyStart,
|
||||
config.pAllStart,
|
||||
config.interpolate,
|
||||
config.seed,
|
||||
config.seed
|
||||
)
|
||||
|
||||
// Generate Typst sources (one per page)
|
||||
@@ -63,7 +63,7 @@ export async function POST(request: NextRequest) {
|
||||
error: `Failed to compile preview (page ${i + 1})`,
|
||||
details: stderr,
|
||||
},
|
||||
{ status: 500 },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ export async function POST(request: NextRequest) {
|
||||
error: 'Failed to generate preview',
|
||||
message: errorMessage,
|
||||
},
|
||||
{ status: 500 },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function POST(request: NextRequest) {
|
||||
typstSource: typstSource.split('\n').slice(0, 20).join('\n') + '\n...',
|
||||
}),
|
||||
},
|
||||
{ status: 500 },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,16 +53,26 @@ function PreviewContent({ formState }: WorksheetPreviewProps) {
|
||||
const { data: pages } = useSuspenseQuery({
|
||||
queryKey: [
|
||||
'worksheet-preview',
|
||||
formState.total,
|
||||
// PRIMARY state
|
||||
formState.problemsPerPage,
|
||||
formState.cols,
|
||||
formState.rows,
|
||||
formState.pages,
|
||||
formState.orientation,
|
||||
// Other settings that affect appearance
|
||||
formState.name,
|
||||
formState.pAnyStart,
|
||||
formState.pAllStart,
|
||||
formState.interpolate,
|
||||
formState.showCarryBoxes,
|
||||
formState.showAnswerBoxes,
|
||||
formState.showPlaceValueColors,
|
||||
formState.showProblemNumbers,
|
||||
formState.showCellBorder,
|
||||
// Note: seed, fontSize, and date intentionally excluded
|
||||
formState.showTenFrames,
|
||||
formState.showTenFramesForAll,
|
||||
formState.seed, // Include seed to bust cache when problem set regenerates
|
||||
// Note: fontSize, date, rows, total intentionally excluded
|
||||
// (rows and total are derived from primary state)
|
||||
],
|
||||
queryFn: () => fetchWorksheetPreview(formState),
|
||||
})
|
||||
|
||||
@@ -30,20 +30,32 @@ export default function AdditionWorksheetPage() {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
// Immediate form state (for controls - updates instantly)
|
||||
// PRIMARY state: problemsPerPage, cols, pages (what user controls)
|
||||
// DERIVED state: rows, total (calculated from primary)
|
||||
const [formState, setFormState] = useState<WorksheetFormState>({
|
||||
total: 20,
|
||||
// Primary state
|
||||
problemsPerPage: 20,
|
||||
cols: 5,
|
||||
rows: 4,
|
||||
pages: 1,
|
||||
orientation: 'landscape',
|
||||
// Derived state
|
||||
rows: 4, // (20 / 5) * 1 = 4
|
||||
total: 20, // 20 * 1 = 20
|
||||
// Other settings
|
||||
name: '',
|
||||
date: '', // Will be set at generation time
|
||||
pAnyStart: 0.75,
|
||||
pAllStart: 0.25,
|
||||
interpolate: true,
|
||||
showCarryBoxes: true,
|
||||
showAnswerBoxes: true,
|
||||
showPlaceValueColors: true,
|
||||
showProblemNumbers: true,
|
||||
showCellBorder: true,
|
||||
showTenFrames: false,
|
||||
showTenFramesForAll: false,
|
||||
fontSize: 16,
|
||||
seed: Date.now() % 2147483647,
|
||||
orientation: 'landscape',
|
||||
})
|
||||
|
||||
// Debounced form state (for preview - updates after delay)
|
||||
@@ -64,9 +76,9 @@ export default function AdditionWorksheetPage() {
|
||||
|
||||
// Generate new seed when problem settings change
|
||||
const affectsProblems =
|
||||
updates.total !== undefined ||
|
||||
updates.problemsPerPage !== undefined ||
|
||||
updates.cols !== undefined ||
|
||||
updates.rows !== undefined ||
|
||||
updates.pages !== undefined ||
|
||||
updates.orientation !== undefined ||
|
||||
updates.pAnyStart !== undefined ||
|
||||
updates.pAllStart !== undefined ||
|
||||
|
||||
@@ -5,10 +5,14 @@
|
||||
* All fields have concrete values (no undefined/null)
|
||||
*/
|
||||
export interface WorksheetConfig {
|
||||
// Problem set
|
||||
total: number
|
||||
cols: number
|
||||
rows: number
|
||||
// Problem set - PRIMARY state
|
||||
problemsPerPage: number // Number of problems per page (6, 8, 10, 12, 15, 16, 20)
|
||||
cols: number // Column count
|
||||
pages: number // Number of pages
|
||||
|
||||
// Problem set - DERIVED state
|
||||
total: number // total = problemsPerPage * pages
|
||||
rows: number // rows = (problemsPerPage / cols) * pages
|
||||
|
||||
// Personalization
|
||||
name: string
|
||||
@@ -33,28 +37,47 @@ export interface WorksheetConfig {
|
||||
|
||||
// Display options
|
||||
showCarryBoxes: boolean
|
||||
showAnswerBoxes: boolean
|
||||
showPlaceValueColors: boolean
|
||||
showProblemNumbers: boolean
|
||||
showCellBorder: boolean
|
||||
showTenFrames: boolean // Show empty ten-frames
|
||||
showTenFramesForAll: boolean // Show ten-frames for all place values (not just regrouping)
|
||||
fontSize: number
|
||||
seed: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Partial form state - user may be editing, fields optional
|
||||
* PRIMARY state: problemsPerPage, cols, pages (what user controls)
|
||||
* DERIVED state: rows, total (calculated from primary)
|
||||
*/
|
||||
export interface WorksheetFormState {
|
||||
total?: number
|
||||
cols?: number
|
||||
// PRIMARY state (what user selects in UI)
|
||||
problemsPerPage?: number // 6, 8, 10, 12, 15, 16, 20
|
||||
cols?: number // 2, 3, 4, 5 - column count for layout
|
||||
pages?: number // 1, 2, 3, 4
|
||||
orientation?: 'portrait' | 'landscape'
|
||||
|
||||
// DERIVED state (calculated: rows = (problemsPerPage / cols) * pages, total = problemsPerPage * pages)
|
||||
rows?: number
|
||||
total?: number
|
||||
|
||||
// Other settings
|
||||
name?: string
|
||||
date?: string
|
||||
pAnyStart?: number
|
||||
pAllStart?: number
|
||||
interpolate?: boolean
|
||||
showCarryBoxes?: boolean
|
||||
showAnswerBoxes?: boolean
|
||||
showPlaceValueColors?: boolean
|
||||
showProblemNumbers?: boolean
|
||||
showCellBorder?: boolean
|
||||
showTenFrames?: boolean
|
||||
showTenFramesForAll?: boolean
|
||||
fontSize?: number
|
||||
seed?: number
|
||||
orientation?: 'portrait' | 'landscape'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@ function generatePageTypst(
|
||||
config: WorksheetConfig,
|
||||
pageProblems: AdditionProblem[],
|
||||
problemOffset: number,
|
||||
rowsPerPage: number,
|
||||
rowsPerPage: number
|
||||
): string {
|
||||
const problemsTypst = pageProblems.map((p) => ` (a: ${p.a}, b: ${p.b}),`).join('\n')
|
||||
|
||||
@@ -35,20 +35,14 @@ function generatePageTypst(
|
||||
// Calculate grid spacing based on ACTUAL rows on this page
|
||||
const headerHeight = 0.35 // inches for header
|
||||
const availableHeight = contentHeight - headerHeight
|
||||
const gutterSize = 0.15 // inches between items
|
||||
const gutterHeightTotal = gutterSize * (actualRows - 1)
|
||||
const problemBoxHeight = (availableHeight - gutterHeightTotal) / actualRows
|
||||
const problemBoxHeight = availableHeight / actualRows
|
||||
const problemBoxWidth = contentWidth / config.cols
|
||||
|
||||
const gutterWidthTotal = gutterSize * (config.cols - 1)
|
||||
const problemBoxWidth = (contentWidth - gutterWidthTotal) / config.cols
|
||||
|
||||
// Calculate cell size to fit within problem box
|
||||
// Problem has 5 rows: carry boxes, first number, second number, line, answer boxes
|
||||
// Reserve space for problem number and insets
|
||||
const problemNumberHeight = 0.15
|
||||
const insetTotal = 0.05 * 2
|
||||
const availableCellHeight = problemBoxHeight - problemNumberHeight - insetTotal
|
||||
const cellSize = availableCellHeight / 5 // 5 rows in the grid, no max cap
|
||||
// Calculate cell size to fill the entire problem box
|
||||
// Without ten-frames: 5 rows (carry, first number, second number, line, answer)
|
||||
// With ten-frames: 5 rows + ten-frames row (0.8 * cellSize for square cells)
|
||||
// Total with ten-frames: 5.8 rows, use 6.4 for breathing room
|
||||
const cellSize = config.showTenFrames ? problemBoxHeight / 6.4 : problemBoxHeight / 5
|
||||
|
||||
return String.raw`
|
||||
// addition-worksheet-page.typ (auto-generated)
|
||||
@@ -59,14 +53,71 @@ function generatePageTypst(
|
||||
margin: ${margin}in,
|
||||
fill: white
|
||||
)
|
||||
#set text(size: ${config.fontSize}pt)
|
||||
#set text(size: ${config.fontSize}pt, font: "New Computer Modern Math")
|
||||
|
||||
// Single non-breakable block to ensure one page
|
||||
#block(breakable: false)[
|
||||
|
||||
#let cell-outline = ${config.showCellBorder ? '0.6pt' : 'none'}
|
||||
#let grid-stroke = ${config.showCellBorder ? '(thickness: 1pt, dash: "dashed", paint: gray.darken(20%))' : 'none'}
|
||||
#let heavy-stroke = 0.8pt
|
||||
#let show-carries = ${config.showCarryBoxes ? 'true' : 'false'}
|
||||
#let show-answers = ${config.showAnswerBoxes ? 'true' : 'false'}
|
||||
#let show-colors = ${config.showPlaceValueColors ? 'true' : 'false'}
|
||||
#let show-numbers = ${config.showProblemNumbers ? 'true' : 'false'}
|
||||
#let show-ten-frames = ${config.showTenFrames ? 'true' : 'false'}
|
||||
#let show-ten-frames-for-all = ${config.showTenFramesForAll ? 'true' : 'false'}
|
||||
|
||||
// Place value colors (light pastels)
|
||||
#let color-ones = rgb(227, 242, 253) // Light blue
|
||||
#let color-tens = rgb(232, 245, 233) // Light green
|
||||
#let color-hundreds = rgb(255, 249, 196) // Light yellow
|
||||
#let color-none = white // No color
|
||||
|
||||
// Ten-frame helper - stacked 2 frames vertically, sized to fit cell width
|
||||
// top-color: background for top frame (represents carry to next place value)
|
||||
// bottom-color: background for bottom frame (represents current place value)
|
||||
#let ten-frame-spacing = 0pt // No gap between frames
|
||||
#let ten-frame-cell-stroke = 0.4pt // Internal cell strokes - slightly thinner
|
||||
#let ten-frame-cell-color = rgb(0, 0, 0, 30%) // Light gray for internal lines
|
||||
#let ten-frame-outer-stroke = 0.8pt // Dark outer border for frame visibility
|
||||
#let ten-frames-stacked(cell-width, top-color, bottom-color) = {
|
||||
let cell-w = cell-width / 5
|
||||
let cell-h = cell-w // Square cells
|
||||
stack(
|
||||
dir: ttb,
|
||||
spacing: ten-frame-spacing,
|
||||
// Top ten-frame (carry to next place value) - wrapped with outer border
|
||||
box(
|
||||
stroke: ten-frame-outer-stroke + black,
|
||||
inset: 0pt
|
||||
)[
|
||||
#grid(
|
||||
columns: 5,
|
||||
rows: 2,
|
||||
gutter: 0pt,
|
||||
stroke: none,
|
||||
..for i in range(0, 10) {
|
||||
(box(width: cell-w, height: cell-h, fill: top-color, stroke: ten-frame-cell-stroke + ten-frame-cell-color)[],)
|
||||
}
|
||||
)
|
||||
],
|
||||
// Bottom ten-frame (current place value overflow) - wrapped with outer border
|
||||
box(
|
||||
stroke: ten-frame-outer-stroke + black,
|
||||
inset: 0pt
|
||||
)[
|
||||
#grid(
|
||||
columns: 5,
|
||||
rows: 2,
|
||||
gutter: 0pt,
|
||||
stroke: none,
|
||||
..for i in range(0, 10) {
|
||||
(box(width: cell-w, height: cell-h, fill: bottom-color, stroke: ten-frame-cell-stroke + ten-frame-cell-color)[],)
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
#let problem-box(problem, index) = {
|
||||
let a = problem.a
|
||||
@@ -77,47 +128,92 @@ function generatePageTypst(
|
||||
let bO = calc.rem(b, 10)
|
||||
|
||||
box(
|
||||
stroke: cell-outline,
|
||||
inset: 0.05in,
|
||||
inset: 0pt,
|
||||
width: ${problemBoxWidth}in,
|
||||
height: ${problemBoxHeight}in
|
||||
)[
|
||||
#align(center + horizon)[
|
||||
#block[
|
||||
#align(top + left)[
|
||||
#text(size: 0.5em, weight: "bold")[\##(index + 1).]
|
||||
]
|
||||
|
||||
#grid(
|
||||
#stack(
|
||||
dir: ttb,
|
||||
spacing: 0pt,
|
||||
if show-numbers {
|
||||
align(top + left)[
|
||||
#box(inset: (left: 0.08in, top: 0.05in))[
|
||||
#text(size: ${(cellSize * 0.6 * 72).toFixed(1)}pt, weight: "bold", font: "New Computer Modern Math")[\##(index + 1).]
|
||||
]
|
||||
]
|
||||
},
|
||||
grid(
|
||||
columns: (0.5em, ${cellSize}in, ${cellSize}in, ${cellSize}in),
|
||||
gutter: 0pt,
|
||||
|
||||
[],
|
||||
if show-carries { box(width: ${cellSize}in, height: ${cellSize}in, stroke: 0.5pt)[] } else { v(${cellSize}in) },
|
||||
if show-carries { box(width: ${cellSize}in, height: ${cellSize}in, stroke: 0.5pt)[] } else { v(${cellSize}in) },
|
||||
if show-carries { box(width: ${cellSize}in, height: ${cellSize}in, stroke: 0.5pt, fill: if show-colors { color-tens } else { color-none })[] } else { v(${cellSize}in) },
|
||||
if show-carries { box(width: ${cellSize}in, height: ${cellSize}in, stroke: 0.5pt, fill: if show-colors { color-ones } else { color-none })[] } else { v(${cellSize}in) },
|
||||
[],
|
||||
|
||||
[],
|
||||
[],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in)[#align(center + horizon)[#text(size: 1em)[#str(aT)]]],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in)[#align(center + horizon)[#text(size: 1em)[#str(aO)]]],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in, fill: if show-colors { color-tens } else { color-none })[#align(center + horizon)[#text(size: ${(cellSize * 0.8 * 72).toFixed(1)}pt)[#str(aT)]]],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in, fill: if show-colors { color-ones } else { color-none })[#align(center + horizon)[#text(size: ${(cellSize * 0.8 * 72).toFixed(1)}pt)[#str(aO)]]],
|
||||
|
||||
box(width: ${cellSize}in, height: ${cellSize}in)[#align(center + horizon)[#text(size: 1em)[+]]],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in)[#align(center + horizon)[#text(size: ${(cellSize * 0.8 * 72).toFixed(1)}pt)[+]]],
|
||||
[],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in)[#align(center + horizon)[#text(size: 1em)[#str(bT)]]],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in)[#align(center + horizon)[#text(size: 1em)[#str(bO)]]],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in, fill: if show-colors { color-tens } else { color-none })[#align(center + horizon)[#text(size: ${(cellSize * 0.8 * 72).toFixed(1)}pt)[#str(bT)]]],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in, fill: if show-colors { color-ones } else { color-none })[#align(center + horizon)[#text(size: ${(cellSize * 0.8 * 72).toFixed(1)}pt)[#str(bO)]]],
|
||||
|
||||
// Line row
|
||||
[],
|
||||
line(length: ${cellSize}in, stroke: heavy-stroke),
|
||||
line(length: ${cellSize}in, stroke: heavy-stroke),
|
||||
line(length: ${cellSize}in, stroke: heavy-stroke),
|
||||
|
||||
// Ten-frames row with overlaid line on top
|
||||
// Height calculation: each frame has 2 rows, cell-h = (cell-width/5) [square cells]
|
||||
// Total: 4 * cell-h + spacing = 4 * (cell-width/5) = cell-width * 0.8
|
||||
[],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in, stroke: 0.5pt)[],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in, stroke: 0.5pt)[],
|
||||
box(width: ${cellSize}in, height: ${cellSize}in, stroke: 0.5pt)[],
|
||||
[], // Empty cell for hundreds column
|
||||
if show-ten-frames {
|
||||
let carry = if (aO + bO) >= 10 { 1 } else { 0 }
|
||||
let tens-regroup = (aT + bT + carry) >= 10
|
||||
if show-ten-frames-for-all or tens-regroup {
|
||||
// Top frame (carry to hundreds) = color-hundreds, Bottom frame (tens) = color-tens
|
||||
// Use place() to overlay the line on top
|
||||
// Add small right margin to create gap between tens and ones ten-frames
|
||||
box(width: ${cellSize}in, height: ${cellSize}in * 0.8)[
|
||||
#align(center + top)[#ten-frames-stacked(${cellSize}in * 0.90, if show-colors { color-hundreds } else { color-none }, if show-colors { color-tens } else { color-none })]
|
||||
#place(top, line(length: ${cellSize}in * 0.90, stroke: heavy-stroke))
|
||||
]
|
||||
h(2.5pt) // Small horizontal gap between tens and ones ten-frames
|
||||
} else {
|
||||
v(${cellSize}in * 0.8)
|
||||
}
|
||||
} else {
|
||||
v(${cellSize}in * 0.8)
|
||||
},
|
||||
if show-ten-frames {
|
||||
let ones-regroup = (aO + bO) >= 10
|
||||
if show-ten-frames-for-all or ones-regroup {
|
||||
// Top frame (carry to tens) = color-tens, Bottom frame (ones) = color-ones
|
||||
// Use place() to overlay the line on top
|
||||
box(width: ${cellSize}in, height: ${cellSize}in * 0.8)[
|
||||
#align(center + top)[#ten-frames-stacked(${cellSize}in * 0.90, if show-colors { color-tens } else { color-none }, if show-colors { color-ones } else { color-none })]
|
||||
#place(top, line(length: ${cellSize}in * 0.90, stroke: heavy-stroke))
|
||||
]
|
||||
} else {
|
||||
v(${cellSize}in * 0.8)
|
||||
}
|
||||
} else {
|
||||
v(${cellSize}in * 0.8)
|
||||
},
|
||||
|
||||
// Answer boxes
|
||||
[],
|
||||
if show-answers { box(width: ${cellSize}in, height: ${cellSize}in, stroke: 0.5pt, fill: if show-colors { color-hundreds } else { color-none })[] } else { v(${cellSize}in) },
|
||||
if show-answers { box(width: ${cellSize}in, height: ${cellSize}in, stroke: 0.5pt, fill: if show-colors { color-tens } else { color-none })[] } else { v(${cellSize}in) },
|
||||
if show-answers { box(width: ${cellSize}in, height: ${cellSize}in, stroke: 0.5pt, fill: if show-colors { color-ones } else { color-none })[] } else { v(${cellSize}in) },
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
}
|
||||
@@ -138,8 +234,9 @@ ${problemsTypst}
|
||||
// Problem grid - exactly ${actualRows} rows × ${config.cols} columns
|
||||
#grid(
|
||||
columns: ${config.cols},
|
||||
column-gutter: ${gutterSize}in,
|
||||
row-gutter: ${gutterSize}in,
|
||||
column-gutter: 0pt,
|
||||
row-gutter: 0pt,
|
||||
stroke: grid-stroke,
|
||||
..for r in range(0, ${actualRows}) {
|
||||
for c in range(0, ${config.cols}) {
|
||||
let idx = r * ${config.cols} + c
|
||||
@@ -159,17 +256,19 @@ ${problemsTypst}
|
||||
/**
|
||||
* Generate Typst source code for the worksheet (returns array of page sources)
|
||||
*/
|
||||
export function generateTypstSource(config: WorksheetConfig, problems: AdditionProblem[]): string[] {
|
||||
// Determine rows per page based on orientation (portrait = tall, landscape = wide)
|
||||
const isPortrait = config.page.hIn > config.page.wIn
|
||||
const rowsPerPage = isPortrait ? 5 : 2
|
||||
const problemsPerPage = config.cols * rowsPerPage
|
||||
export function generateTypstSource(
|
||||
config: WorksheetConfig,
|
||||
problems: AdditionProblem[]
|
||||
): string[] {
|
||||
// Use the problemsPerPage directly from config (primary state)
|
||||
const problemsPerPage = config.problemsPerPage
|
||||
const rowsPerPage = problemsPerPage / config.cols
|
||||
|
||||
// Chunk problems into discrete pages
|
||||
const pages = chunkProblems(problems, problemsPerPage)
|
||||
|
||||
// Generate separate Typst source for each page
|
||||
return pages.map((pageProblems, pageIndex) =>
|
||||
generatePageTypst(config, pageProblems, pageIndex * problemsPerPage, rowsPerPage),
|
||||
generatePageTypst(config, pageProblems, pageIndex * problemsPerPage, rowsPerPage)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -67,11 +67,20 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
|
||||
// Determine orientation based on columns (portrait = 2-3 cols, landscape = 4-5 cols)
|
||||
const orientation = formState.orientation || (cols <= 3 ? 'portrait' : 'landscape')
|
||||
|
||||
// Get primary state values
|
||||
const problemsPerPage = formState.problemsPerPage ?? total
|
||||
const pages = formState.pages ?? 1
|
||||
|
||||
// Build complete config with defaults
|
||||
const config: WorksheetConfig = {
|
||||
total,
|
||||
// Primary state
|
||||
problemsPerPage,
|
||||
cols,
|
||||
pages,
|
||||
// Derived state
|
||||
total,
|
||||
rows,
|
||||
// Other fields
|
||||
name: formState.name?.trim() || 'Student',
|
||||
date: formState.date?.trim() || getDefaultDate(),
|
||||
pAnyStart,
|
||||
@@ -88,7 +97,12 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
|
||||
bottom: 0.7,
|
||||
},
|
||||
showCarryBoxes: formState.showCarryBoxes ?? true,
|
||||
showAnswerBoxes: formState.showAnswerBoxes ?? true,
|
||||
showPlaceValueColors: formState.showPlaceValueColors ?? true,
|
||||
showProblemNumbers: formState.showProblemNumbers ?? true,
|
||||
showCellBorder: formState.showCellBorder ?? true,
|
||||
showTenFrames: formState.showTenFrames ?? false,
|
||||
showTenFramesForAll: formState.showTenFramesForAll ?? false,
|
||||
fontSize,
|
||||
seed,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user