Skip to content

Commit b4aff80

Browse files
committed
Add a narration description and adjust when narration is displayed for each item
1 parent f282e67 commit b4aff80

File tree

1 file changed

+156
-57
lines changed

1 file changed

+156
-57
lines changed

src/components/ArrayEvaluationDemo.tsx

Lines changed: 156 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ interface Step {
2121
from: number;
2222
to: number;
2323
progress: number;
24-
narration: string;
24+
narrationTitle: string;
25+
narrationDescription: string;
2526
explode?: boolean;
2627
skipRemaining?: boolean;
2728
untaken?: boolean;
@@ -36,21 +37,35 @@ interface ExplosionParticle {
3637
startTime: number;
3738
}
3839

39-
const ArrayEvaluationDemo: React.FC = () => {
40+
interface Narration {
41+
title: string;
42+
description: string;
43+
}
44+
45+
type ArrayEvaluationDemoProps = {
46+
demoType?: EvaluationType | undefined;
47+
};
48+
49+
const ArrayEvaluationDemo: React.FC = ({
50+
demoType,
51+
}: ArrayEvaluationDemoProps) => {
4052
const [progress, setProgress] = useState(0);
4153
const [playing, setPlaying] = useState(false);
42-
const [type, setType] = useState<EvaluationType>('eager');
54+
const [type, setType] = useState<EvaluationType>(demoType || 'eager');
4355
const [explosions, setExplosions] = useState<ExplosionParticle[]>([]);
4456
const [positions, setPositions] = useState<Position[]>([]);
4557
const processedStepsRef = useRef<Set<string>>(new Set());
4658
const animationRef = useRef<number | null>(null);
4759

4860
const EXPLOSION_DURATION = 500;
49-
const ANIMATION_STEP = 0.2;
61+
// const ANIMATION_STEP = 0.2;
62+
const ANIMATION_STEP = 0.15; // Reduced from 0.2 to slow down animation
63+
const FRAME_DURATION = 16; // roughly 60fps
64+
5065
const MAX_PROGRESS = 150;
5166
const isEven = (n: number): boolean => n % 2 === 0;
5267

53-
const columns = ['Array', 'map', 'select', 'take(3)'];
68+
const columns = ['[ ]', 'map', 'select', 'take'];
5469
const dots: Dot[] = Array(7)
5570
.fill(null)
5671
.map((_, i) => ({ pos: i, column: 0 }));
@@ -64,7 +79,8 @@ const ArrayEvaluationDemo: React.FC = () => {
6479
from: 0,
6580
to: 1,
6681
progress: i * 8 + 8,
67-
narration: `Map to blue: Item ${i}`,
82+
narrationTitle: 'Map to blue',
83+
narrationDescription: `Item ${i}`,
6884
})),
6985
...Array(7)
7086
.fill(null)
@@ -74,108 +90,150 @@ const ArrayEvaluationDemo: React.FC = () => {
7490
to: 2,
7591
progress: 64 + i * 8,
7692
explode: !isEven(i),
77-
narration: `Select even? Item ${i}: ${isEven(i) ? 'yes' : 'no'}`,
93+
narrationTitle: `Select even?`,
94+
narrationDescription: `Item ${i}: ${isEven(i) ? 'yes' : 'no'}`,
7895
})),
7996
...[0, 2, 4].map((i, idx) => ({
8097
pos: i,
8198
from: 2,
8299
to: 3,
83100
progress: 128 + idx * 8,
84-
narration: `Take ${idx + 1}: Item ${i}`,
101+
narrationTitle: `Take 3`,
102+
narrationDescription: `Item ${i}`,
85103
})),
86104
{
87105
pos: 6,
88106
from: 2,
89107
to: 2,
90108
progress: 144,
91109
untaken: true,
92-
narration: 'Done - took 3 items',
110+
narrationTitle: 'Done!',
111+
narrationDescription: '3 items taken',
93112
},
94113
],
95114
lazy: [
96-
{ pos: 0, from: 0, to: 1, progress: 8, narration: 'Map to blue: Item 0' },
115+
{
116+
pos: 0,
117+
from: 0,
118+
to: 1,
119+
progress: 8,
120+
narrationTitle: 'Map to blue',
121+
narrationDescription: 'Item 0',
122+
},
97123
{
98124
pos: 0,
99125
from: 1,
100126
to: 2,
101127
progress: 16,
102-
narration: 'Select even? Item 0: yes',
128+
narrationTitle: 'Select even?',
129+
narrationDescription: 'Item 0: yes',
130+
},
131+
{
132+
pos: 0,
133+
from: 2,
134+
to: 3,
135+
progress: 24,
136+
narrationTitle: 'Take 1',
137+
narrationDescription: 'Item 0',
103138
},
104-
{ pos: 0, from: 2, to: 3, progress: 24, narration: 'Take 1: Item 0' },
105139
{
106140
pos: 1,
107141
from: 0,
108142
to: 1,
109143
progress: 32,
110-
narration: 'Map to blue: Item 1',
144+
narrationTitle: 'Map to blue',
145+
narrationDescription: 'Item 1',
111146
},
112147
{
113148
pos: 1,
114149
from: 1,
115150
to: 2,
116151
progress: 40,
117152
explode: true,
118-
narration: 'Select even? Item 1: no',
153+
narrationTitle: 'Select even?',
154+
narrationDescription: 'Item 1: no',
119155
},
120156
{
121157
pos: 2,
122158
from: 0,
123159
to: 1,
124160
progress: 56,
125-
narration: 'Map to blue: Item 2',
161+
narrationTitle: 'Map to blue',
162+
narrationDescription: 'Item 2',
126163
},
127164
{
128165
pos: 2,
129166
from: 1,
130167
to: 2,
131168
progress: 64,
132-
narration: 'Select even? Item 2: yes',
169+
narrationTitle: 'Select even?',
170+
narrationDescription: 'Item 2: yes',
171+
},
172+
{
173+
pos: 2,
174+
from: 2,
175+
to: 3,
176+
progress: 72,
177+
narrationTitle: 'Take 3',
178+
narrationDescription: 'Item 2',
133179
},
134-
{ pos: 2, from: 2, to: 3, progress: 72, narration: 'Take 2: Item 2' },
135180
{
136181
pos: 3,
137182
from: 0,
138183
to: 1,
139184
progress: 84,
140-
narration: 'Map to blue: Item 3',
185+
narrationTitle: 'Map to blue',
186+
narrationDescription: 'Item 3',
141187
},
142188
{
143189
pos: 3,
144190
from: 1,
145191
to: 2,
146192
progress: 92,
147193
explode: true,
148-
narration: 'Select even? Item 3: no',
194+
narrationTitle: 'Select even?',
195+
narrationDescription: 'Item 3: no',
149196
},
150197
{
151198
pos: 4,
152199
from: 0,
153200
to: 1,
154201
progress: 108,
155-
narration: 'Map to blue: Item 4',
202+
narrationTitle: 'Map to blue',
203+
narrationDescription: 'Item 4',
156204
},
157205
{
158206
pos: 4,
159207
from: 1,
160208
to: 2,
161209
progress: 116,
162-
narration: 'Select even? Item 4: yes',
210+
narrationTitle: 'Select even?',
211+
narrationDescription: 'Item 4: yes',
212+
},
213+
{
214+
pos: 4,
215+
from: 2,
216+
to: 3,
217+
progress: 124,
218+
narrationTitle: 'Take 3',
219+
narrationDescription: 'Item 4',
163220
},
164-
{ pos: 4, from: 2, to: 3, progress: 124, narration: 'Take 3: Item 4' },
165221
{
166222
pos: 4,
167223
from: 3,
168224
to: 3,
169225
progress: 132,
170-
narration: 'Done - took 3 items',
226+
narrationTitle: 'Done!',
227+
narrationDescription: '3 items taken',
171228
},
172229
{
173230
pos: 5,
174231
from: 0,
175232
to: 0,
176233
progress: 140,
177234
skipRemaining: true,
178-
narration: 'Remaining items skipped',
235+
narrationTitle: 'Done!',
236+
narrationDescription: 'Remaining items skipped',
179237
},
180238
],
181239
};
@@ -208,14 +266,37 @@ const ArrayEvaluationDemo: React.FC = () => {
208266
}
209267
}, []);
210268

211-
const getNarration = useCallback(() => {
269+
const getNarration = useCallback((): Narration => {
212270
const currentSteps = stepsRef.current[type];
271+
272+
// Find any currently animating step
273+
const animatingStep = currentSteps.find((step) => {
274+
const stepStart = step.progress - 8;
275+
const stepEnd = step.progress;
276+
return progress >= stepStart && progress <= stepEnd;
277+
});
278+
279+
if (animatingStep) {
280+
return {
281+
title: animatingStep.narrationTitle,
282+
description: animatingStep.narrationDescription,
283+
};
284+
}
285+
286+
// If no step is currently animating, find the last completed step
213287
for (let i = currentSteps.length - 1; i >= 0; i--) {
214-
if (progress >= currentSteps[i].progress) {
215-
return currentSteps[i].narration;
288+
if (progress > currentSteps[i].progress) {
289+
return {
290+
title: currentSteps[i].narrationTitle,
291+
description: currentSteps[i].narrationDescription,
292+
};
216293
}
217294
}
218-
return 'Ready to start';
295+
296+
return {
297+
title: 'Ready to start',
298+
description: 'Press play to start the animation',
299+
};
219300
}, [progress, type]);
220301

221302
const getLatestStepForItem = useCallback(
@@ -282,7 +363,7 @@ const ArrayEvaluationDemo: React.FC = () => {
282363
}
283364
} else if (latestStep) {
284365
column = latestStep.to;
285-
exploded = latestStep.explode;
366+
exploded = latestStep.explode || false;
286367
}
287368

288369
return {
@@ -291,7 +372,7 @@ const ArrayEvaluationDemo: React.FC = () => {
291372
transformed: column >= 1,
292373
exploded,
293374
skipped: skipRemainingItems || (latestStep?.skipRemaining && i !== 4),
294-
};
375+
} as Position;
295376
});
296377

297378
setPositions(newPositions);
@@ -337,22 +418,36 @@ const ArrayEvaluationDemo: React.FC = () => {
337418
useEffect(() => {
338419
cleanup();
339420
if (playing) {
340-
const animate = () => {
341-
setProgress((prev) => {
342-
const next = Math.min(prev + ANIMATION_STEP, MAX_PROGRESS);
343-
if (next >= MAX_PROGRESS) {
344-
setPlaying(false);
421+
let lastTime = performance.now();
422+
423+
const animate = (currentTime: number) => {
424+
const deltaTime = currentTime - lastTime;
425+
426+
if (deltaTime >= FRAME_DURATION) {
427+
// Only update if enough time has passed
428+
setProgress((prev) => {
429+
const next = Math.min(prev + ANIMATION_STEP, MAX_PROGRESS);
430+
if (next >= MAX_PROGRESS) {
431+
setPlaying(false);
432+
return next;
433+
}
345434
return next;
346-
}
435+
});
436+
lastTime = currentTime;
437+
}
438+
439+
if (playing) {
347440
animationRef.current = requestAnimationFrame(animate);
348-
return next;
349-
});
441+
}
350442
};
443+
351444
animationRef.current = requestAnimationFrame(animate);
352445
}
353446
return cleanup;
354447
}, [playing, cleanup]);
355448

449+
const narration = getNarration();
450+
356451
return (
357452
<Card className='w-full max-w-2xl'>
358453
<CardHeader>
@@ -366,10 +461,12 @@ const ArrayEvaluationDemo: React.FC = () => {
366461
</CardTitle>
367462
</CardHeader>
368463
<CardContent>
369-
<div className='h-12 mb-4 flex items-center justify-center text-lg font-medium'>
370-
{getNarration()}
464+
<div className='h-12 mb-4 text-lg font-medium text-center'>
465+
{narration.title}
466+
<div className='text-sm text-gray-500 font-normal'>
467+
{narration.description}
468+
</div>
371469
</div>
372-
373470
<svg viewBox='0 0 400 400' className='w-full h-80 mb-4'>
374471
{columns.map((col, i) => (
375472
<text
@@ -440,23 +537,25 @@ const ArrayEvaluationDemo: React.FC = () => {
440537
</svg>
441538

442539
<div className='space-y-4'>
443-
<div className='flex items-center justify-between mb-4'>
444-
<div className='flex items-center gap-2'>
445-
<span>Eager</span>
446-
<Switch
447-
checked={type === 'lazy'}
448-
onCheckedChange={(checked: boolean) => {
449-
cleanup();
450-
setPlaying(false);
451-
setProgress(0);
452-
setType(checked ? 'lazy' : 'eager');
453-
setExplosions([]);
454-
processedStepsRef.current.clear();
455-
}}
456-
/>
457-
<span>Lazy</span>
540+
{demoType || (
541+
<div className='flex items-center justify-between mb-4'>
542+
<div className='flex items-center gap-2'>
543+
<span>Eager</span>
544+
<Switch
545+
checked={type === 'lazy'}
546+
onCheckedChange={(checked: boolean) => {
547+
cleanup();
548+
setPlaying(false);
549+
setProgress(0);
550+
setType(checked ? 'lazy' : 'eager');
551+
setExplosions([]);
552+
processedStepsRef.current.clear();
553+
}}
554+
/>
555+
<span>Lazy</span>
556+
</div>
458557
</div>
459-
</div>
558+
)}
460559

461560
<Slider
462561
value={[progress]}

0 commit comments

Comments
 (0)