feat(worksheets): update validation and generation for V3 mode-aware schema
validation.ts: - Build mode-specific configs (Smart vs Manual) - Smart mode: uses difficultyProfile + displayRules - Manual mode: uses boolean flags for display options typstGenerator.ts: - Per-problem display enrichment based on mode - Smart mode: conditional scaffolding via analyzeProblem + resolveDisplayForProblem - Manual mode: uniform display across all problems - Fix showTenFramesForAll property access (Manual mode only) page.tsx: - Server-side settings loading with V3 schema - Auto-migration from V2→V3 via parseAdditionConfig
This commit is contained in:
@@ -45,7 +45,7 @@ async function loadWorksheetSettings(): Promise<
|
||||
return {
|
||||
...defaultAdditionConfig,
|
||||
seed: Date.now() % 2147483647,
|
||||
}
|
||||
} as unknown as Omit<WorksheetFormState, 'date' | 'rows' | 'total'>
|
||||
}
|
||||
|
||||
// Parse and validate config (auto-migrates to latest version)
|
||||
@@ -53,14 +53,14 @@ async function loadWorksheetSettings(): Promise<
|
||||
return {
|
||||
...config,
|
||||
seed: Date.now() % 2147483647,
|
||||
}
|
||||
} as unknown as Omit<WorksheetFormState, 'date' | 'rows' | 'total'>
|
||||
} catch (error) {
|
||||
console.error('Failed to load worksheet settings:', error)
|
||||
// Return defaults on error with a stable seed
|
||||
return {
|
||||
...defaultAdditionConfig,
|
||||
seed: Date.now() % 2147483647,
|
||||
}
|
||||
} as unknown as Omit<WorksheetFormState, 'date' | 'rows' | 'total'>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,13 +25,27 @@ function generatePageTypst(
|
||||
problemOffset: number,
|
||||
rowsPerPage: number
|
||||
): string {
|
||||
// Analyze each problem and resolve display options
|
||||
// Enrich problems with display options based on mode
|
||||
const enrichedProblems = pageProblems.map((p) => {
|
||||
const meta = analyzeProblem(p.a, p.b)
|
||||
const displayOptions = resolveDisplayForProblem(config.displayRules, meta)
|
||||
return {
|
||||
...p,
|
||||
...displayOptions,
|
||||
if (config.mode === 'smart') {
|
||||
// Smart mode: Per-problem conditional display based on problem complexity
|
||||
const meta = analyzeProblem(p.a, p.b)
|
||||
const displayOptions = resolveDisplayForProblem(config.displayRules, meta)
|
||||
return {
|
||||
...p,
|
||||
...displayOptions,
|
||||
}
|
||||
} else {
|
||||
// Manual mode: Uniform display across all problems
|
||||
return {
|
||||
...p,
|
||||
showCarryBoxes: config.showCarryBoxes,
|
||||
showAnswerBoxes: config.showAnswerBoxes,
|
||||
showPlaceValueColors: config.showPlaceValueColors,
|
||||
showTenFrames: config.showTenFrames,
|
||||
showProblemNumbers: config.showProblemNumbers,
|
||||
showCellBorder: config.showCellBorder,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -82,7 +96,7 @@ function generatePageTypst(
|
||||
#block(breakable: false)[
|
||||
|
||||
#let heavy-stroke = 0.8pt
|
||||
#let show-ten-frames-for-all = ${config.showTenFramesForAll ? 'true' : 'false'}
|
||||
#let show-ten-frames-for-all = ${config.mode === 'manual' && config.showTenFramesForAll ? 'true' : 'false'}
|
||||
|
||||
${generateTypstHelpers(cellSize)}
|
||||
|
||||
|
||||
@@ -72,51 +72,11 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
|
||||
const problemsPerPage = formState.problemsPerPage ?? total
|
||||
const pages = formState.pages ?? 1
|
||||
|
||||
// Handle V2 displayRules or V1 boolean flags
|
||||
let displayRules: DisplayRules
|
||||
let showCarryBoxes: boolean
|
||||
let showAnswerBoxes: boolean
|
||||
let showPlaceValueColors: boolean
|
||||
let showProblemNumbers: boolean
|
||||
let showCellBorder: boolean
|
||||
let showTenFrames: boolean
|
||||
|
||||
if (formState.displayRules) {
|
||||
// V2: Use displayRules from formState
|
||||
displayRules = formState.displayRules
|
||||
// Derive V1 compatibility flags (use 'always' as true, anything else as false for now)
|
||||
showCarryBoxes = displayRules.carryBoxes === 'always'
|
||||
showAnswerBoxes = displayRules.answerBoxes === 'always'
|
||||
showPlaceValueColors = displayRules.placeValueColors === 'always'
|
||||
showProblemNumbers = displayRules.problemNumbers === 'always'
|
||||
showCellBorder = displayRules.cellBorders === 'always'
|
||||
showTenFrames = displayRules.tenFrames === 'always'
|
||||
} else {
|
||||
// V1: Use individual boolean flags, convert to displayRules
|
||||
showCarryBoxes = formState.showCarryBoxes ?? true
|
||||
showAnswerBoxes = formState.showAnswerBoxes ?? true
|
||||
showPlaceValueColors = formState.showPlaceValueColors ?? true
|
||||
showProblemNumbers = formState.showProblemNumbers ?? true
|
||||
showCellBorder = formState.showCellBorder ?? true
|
||||
showTenFrames = formState.showTenFrames ?? false
|
||||
|
||||
displayRules = {
|
||||
carryBoxes: showCarryBoxes ? 'always' : 'never',
|
||||
answerBoxes: showAnswerBoxes ? 'always' : 'never',
|
||||
placeValueColors: showPlaceValueColors ? 'always' : 'never',
|
||||
problemNumbers: showProblemNumbers ? 'always' : 'never',
|
||||
cellBorders: showCellBorder ? 'always' : 'never',
|
||||
tenFrames: showTenFrames ? 'always' : 'never',
|
||||
}
|
||||
}
|
||||
|
||||
// Build complete config with defaults
|
||||
const config: WorksheetConfig = {
|
||||
// V2 fields
|
||||
version: 2,
|
||||
displayRules,
|
||||
difficultyProfile: formState.difficultyProfile,
|
||||
// Determine mode (default to 'smart' if not specified)
|
||||
const mode = formState.mode ?? 'smart'
|
||||
|
||||
// Shared fields for both modes
|
||||
const sharedFields = {
|
||||
// Primary state
|
||||
problemsPerPage,
|
||||
cols,
|
||||
@@ -146,18 +106,47 @@ export function validateWorksheetConfig(formState: WorksheetFormState): Validati
|
||||
bottom: 0.7,
|
||||
},
|
||||
|
||||
// V1 compatibility flags (derived from displayRules)
|
||||
showCarryBoxes,
|
||||
showAnswerBoxes,
|
||||
showPlaceValueColors,
|
||||
showProblemNumbers,
|
||||
showCellBorder,
|
||||
showTenFrames,
|
||||
showTenFramesForAll: formState.showTenFramesForAll ?? false,
|
||||
|
||||
fontSize,
|
||||
seed,
|
||||
}
|
||||
|
||||
// Build mode-specific config
|
||||
let config: WorksheetConfig
|
||||
|
||||
if (mode === 'smart') {
|
||||
// Smart mode: Use displayRules for conditional scaffolding
|
||||
const displayRules: DisplayRules = formState.displayRules ?? {
|
||||
carryBoxes: 'whenRegrouping',
|
||||
answerBoxes: 'always',
|
||||
placeValueColors: 'always',
|
||||
tenFrames: 'whenRegrouping',
|
||||
problemNumbers: 'always',
|
||||
cellBorders: 'always',
|
||||
}
|
||||
|
||||
config = {
|
||||
version: 3,
|
||||
mode: 'smart',
|
||||
displayRules,
|
||||
difficultyProfile: formState.difficultyProfile,
|
||||
...sharedFields,
|
||||
}
|
||||
} else {
|
||||
// Manual mode: Use boolean flags for uniform display
|
||||
config = {
|
||||
version: 3,
|
||||
mode: 'manual',
|
||||
showCarryBoxes: formState.showCarryBoxes ?? true,
|
||||
showAnswerBoxes: formState.showAnswerBoxes ?? true,
|
||||
showPlaceValueColors: formState.showPlaceValueColors ?? true,
|
||||
showTenFrames: formState.showTenFrames ?? false,
|
||||
showProblemNumbers: formState.showProblemNumbers ?? true,
|
||||
showCellBorder: formState.showCellBorder ?? true,
|
||||
showTenFramesForAll: formState.showTenFramesForAll ?? false,
|
||||
manualPreset: formState.manualPreset,
|
||||
...sharedFields,
|
||||
}
|
||||
}
|
||||
|
||||
return { isValid: true, config }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user