|
|
|
|
@@ -385,16 +385,20 @@ function TutorialPlayerContent({
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert bead diff results to StepBeadHighlight format expected by AbacusReact
|
|
|
|
|
const stepBeadHighlights: StepBeadHighlight[] = beadDiff.changes.map((change, _index) => ({
|
|
|
|
|
placeValue: change.placeValue,
|
|
|
|
|
beadType: change.beadType,
|
|
|
|
|
position: change.position,
|
|
|
|
|
direction: change.direction,
|
|
|
|
|
stepIndex: currentMultiStep, // Use current multi-step index to match AbacusReact filtering
|
|
|
|
|
order: change.order,
|
|
|
|
|
}))
|
|
|
|
|
// Filter to only include beads from columns that exist
|
|
|
|
|
const minValidPlaceValue = Math.max(0, 5 - abacusColumns)
|
|
|
|
|
const stepBeadHighlights: StepBeadHighlight[] = beadDiff.changes
|
|
|
|
|
.filter((change) => change.placeValue < abacusColumns)
|
|
|
|
|
.map((change, _index) => ({
|
|
|
|
|
placeValue: change.placeValue,
|
|
|
|
|
beadType: change.beadType,
|
|
|
|
|
position: change.position,
|
|
|
|
|
direction: change.direction,
|
|
|
|
|
stepIndex: currentMultiStep, // Use current multi-step index to match AbacusReact filtering
|
|
|
|
|
order: change.order,
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
return stepBeadHighlights
|
|
|
|
|
return stepBeadHighlights.length > 0 ? stepBeadHighlights : undefined
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error generating step beads with bead diff:', error)
|
|
|
|
|
return undefined
|
|
|
|
|
@@ -405,6 +409,7 @@ function TutorialPlayerContent({
|
|
|
|
|
expectedSteps,
|
|
|
|
|
currentMultiStep,
|
|
|
|
|
currentStep.stepBeadHighlights,
|
|
|
|
|
abacusColumns,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Get the current step's bead diff summary for real-time user feedback
|
|
|
|
|
@@ -428,6 +433,15 @@ function TutorialPlayerContent({
|
|
|
|
|
// Get current step summary for real-time user feedback
|
|
|
|
|
const currentStepSummary = getCurrentStepSummary()
|
|
|
|
|
|
|
|
|
|
// Filter highlightBeads to only include valid columns
|
|
|
|
|
const filteredHighlightBeads = useMemo(() => {
|
|
|
|
|
if (!currentStep.highlightBeads) return undefined
|
|
|
|
|
return currentStep.highlightBeads.filter((highlight) => {
|
|
|
|
|
const placeValue = highlight.placeValue ?? 4 - (highlight.columnIndex ?? 0)
|
|
|
|
|
return placeValue < abacusColumns
|
|
|
|
|
})
|
|
|
|
|
}, [currentStep.highlightBeads, abacusColumns])
|
|
|
|
|
|
|
|
|
|
// Helper function to highlight the current mathematical term in the full decomposition
|
|
|
|
|
const renderHighlightedDecomposition = useCallback(() => {
|
|
|
|
|
if (!fullDecomposition || expectedSteps.length === 0) return null
|
|
|
|
|
@@ -525,16 +539,27 @@ function TutorialPlayerContent({
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate that the bead is from a column that exists
|
|
|
|
|
if (topmostBead.placeValue >= abacusColumns) {
|
|
|
|
|
// Bead is from an invalid column, skip tooltip
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Smart positioning logic: avoid covering active beads
|
|
|
|
|
const targetColumnIndex = 4 - topmostBead.placeValue // Convert placeValue to columnIndex (5 columns: 0-4)
|
|
|
|
|
// Convert placeValue to columnIndex based on actual number of columns
|
|
|
|
|
const targetColumnIndex = abacusColumns - 1 - topmostBead.placeValue
|
|
|
|
|
|
|
|
|
|
// Check if there are any active beads (against reckoning bar OR with arrows) in columns to the left
|
|
|
|
|
const hasActiveBeadsToLeft = (() => {
|
|
|
|
|
// Get current abacus state - we need to check which beads are against the reckoning bar
|
|
|
|
|
const abacusDigits = currentValue.toString().padStart(5, '0').split('').map(Number)
|
|
|
|
|
const abacusDigits = currentValue
|
|
|
|
|
.toString()
|
|
|
|
|
.padStart(abacusColumns, '0')
|
|
|
|
|
.split('')
|
|
|
|
|
.map(Number)
|
|
|
|
|
|
|
|
|
|
for (let col = 0; col < targetColumnIndex; col++) {
|
|
|
|
|
const _placeValue = 4 - col // Convert columnIndex back to placeValue
|
|
|
|
|
const placeValue = abacusColumns - 1 - col // Convert columnIndex back to placeValue
|
|
|
|
|
const digitValue = abacusDigits[col]
|
|
|
|
|
|
|
|
|
|
// Check if any beads are active (against reckoning bar) in this column
|
|
|
|
|
@@ -550,7 +575,7 @@ function TutorialPlayerContent({
|
|
|
|
|
// Also check if this column has beads with direction arrows (from current step)
|
|
|
|
|
const hasArrowsInColumn =
|
|
|
|
|
currentStepBeads?.some((bead) => {
|
|
|
|
|
const beadColumnIndex = 4 - bead.placeValue
|
|
|
|
|
const beadColumnIndex = abacusColumns - 1 - bead.placeValue
|
|
|
|
|
return beadColumnIndex === col && bead.direction && bead.direction !== 'none'
|
|
|
|
|
}) ?? false
|
|
|
|
|
if (hasArrowsInColumn) {
|
|
|
|
|
@@ -676,6 +701,7 @@ function TutorialPlayerContent({
|
|
|
|
|
currentValue,
|
|
|
|
|
currentStep,
|
|
|
|
|
isMeaningfulDecomposition,
|
|
|
|
|
abacusColumns,
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// Timer for smart help detection
|
|
|
|
|
@@ -1009,6 +1035,9 @@ function TutorialPlayerContent({
|
|
|
|
|
|
|
|
|
|
// Memoize custom styles calculation to avoid expensive recalculation on every render
|
|
|
|
|
const customStyles = useMemo(() => {
|
|
|
|
|
// Calculate valid column range based on abacusColumns
|
|
|
|
|
const minValidColumn = 5 - abacusColumns
|
|
|
|
|
|
|
|
|
|
// Start with static highlights from step configuration
|
|
|
|
|
const staticHighlights: Record<number, any> = {}
|
|
|
|
|
|
|
|
|
|
@@ -1018,6 +1047,11 @@ function TutorialPlayerContent({
|
|
|
|
|
const columnIndex =
|
|
|
|
|
highlight.placeValue !== undefined ? 4 - highlight.placeValue : highlight.columnIndex
|
|
|
|
|
|
|
|
|
|
// Skip highlights for columns that don't exist
|
|
|
|
|
if (columnIndex < minValidColumn) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize column if it doesn't exist
|
|
|
|
|
if (!staticHighlights[columnIndex]) {
|
|
|
|
|
staticHighlights[columnIndex] = {}
|
|
|
|
|
@@ -1047,6 +1081,12 @@ function TutorialPlayerContent({
|
|
|
|
|
const mergedHighlights = { ...staticHighlights }
|
|
|
|
|
Object.keys(dynamicColumnHighlights).forEach((columnIndexStr) => {
|
|
|
|
|
const columnIndex = parseInt(columnIndexStr, 10)
|
|
|
|
|
|
|
|
|
|
// Skip highlights for columns that don't exist
|
|
|
|
|
if (columnIndex < minValidColumn) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!mergedHighlights[columnIndex]) {
|
|
|
|
|
mergedHighlights[columnIndex] = {}
|
|
|
|
|
}
|
|
|
|
|
@@ -1055,7 +1095,7 @@ function TutorialPlayerContent({
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return Object.keys(mergedHighlights).length > 0 ? { columns: mergedHighlights } : undefined
|
|
|
|
|
}, [currentStep.highlightBeads, dynamicColumnHighlights])
|
|
|
|
|
}, [currentStep.highlightBeads, dynamicColumnHighlights, abacusColumns])
|
|
|
|
|
|
|
|
|
|
if (!currentStep) {
|
|
|
|
|
return <div>No steps available</div>
|
|
|
|
|
@@ -1535,7 +1575,7 @@ function TutorialPlayerContent({
|
|
|
|
|
hideInactiveBeads={abacusConfig.hideInactiveBeads}
|
|
|
|
|
soundEnabled={abacusConfig.soundEnabled}
|
|
|
|
|
soundVolume={abacusConfig.soundVolume}
|
|
|
|
|
highlightBeads={currentStep.highlightBeads}
|
|
|
|
|
highlightBeads={filteredHighlightBeads}
|
|
|
|
|
stepBeadHighlights={currentStepBeads}
|
|
|
|
|
currentStep={currentMultiStep}
|
|
|
|
|
showDirectionIndicators={true}
|
|
|
|
|
|