varunm2004 commited on
Commit
82c6e5a
ยท
verified ยท
1 Parent(s): d515b45

Update src/components/Timeline.tsx

Browse files
Files changed (1) hide show
  1. src/components/Timeline.tsx +27 -29
src/components/Timeline.tsx CHANGED
@@ -12,9 +12,13 @@ const Timeline: React.FC = () => {
12
  addKeyframe, removeKeyframe,
13
  } = useStudioStore();
14
 
15
- const rafRef = useRef<number>(0);
16
- const lastTimeRef = useRef<number>(0);
17
- const rulerRef = useRef<HTMLDivElement>(null);
 
 
 
 
18
 
19
  // โ”€โ”€ Playback loop โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
20
  useEffect(() => {
@@ -25,14 +29,13 @@ const Timeline: React.FC = () => {
25
  const tick = (now: number) => {
26
  const delta = (now - lastTimeRef.current) / 1000;
27
  lastTimeRef.current = now;
28
- setPlayhead((prev: number) => {
29
- const next = prev + delta;
30
- if (next >= timelineDuration) {
31
- setTimelinePlaying(false);
32
- return timelineDuration;
33
- }
34
- return next;
35
- });
36
  rafRef.current = requestAnimationFrame(tick);
37
  };
38
  lastTimeRef.current = performance.now();
@@ -47,14 +50,14 @@ const Timeline: React.FC = () => {
47
  if (!obj) return;
48
  const kf: Keyframe = {
49
  id: Math.random().toString(36).slice(2),
50
- time: parseFloat(playhead.toFixed(3)),
51
- position: [...obj.position] as [number,number,number],
52
- rotation: [...obj.rotation] as [number,number,number],
53
- scale: [...obj.scale] as [number,number,number],
54
  easing: 'ease-in-out',
55
  };
56
  addKeyframe(selectedId, kf);
57
- }, [selectedId, objects, playhead, addKeyframe]);
58
 
59
  // โ”€โ”€ Keyboard shortcut: I = insert keyframe โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
60
  useEffect(() => {
@@ -76,7 +79,7 @@ const Timeline: React.FC = () => {
76
  const fmt = (t: number) => {
77
  const s = Math.floor(t);
78
  const f = Math.floor((t % 1) * 30);
79
- return `${String(s).padStart(2,'0')}:${String(f).padStart(2,'0')}`;
80
  };
81
 
82
  const playheadPct = (playhead / timelineDuration) * 100;
@@ -153,14 +156,13 @@ const Timeline: React.FC = () => {
153
  <span>{i}s</span>
154
  </div>
155
  ))}
156
- {/* Playhead needle */}
157
  <div className="tl-playhead" style={{ left: `${playheadPct}%` }} />
158
  </div>
159
 
160
  {/* Keyframe tracks */}
161
  <div className="tl-tracks">
162
  {objects.map(obj => {
163
- const track = tracks.find(t => t.objectId === obj.id);
164
  const sorted = track
165
  ? [...track.keyframes].sort((a, b) => a.time - b.time)
166
  : [];
@@ -168,11 +170,11 @@ const Timeline: React.FC = () => {
168
  <div key={obj.id}
169
  className={`tl-track ${selectedId === obj.id ? 'active' : ''}`}>
170
 
171
- {/* Connection lines between consecutive keyframes */}
172
  {sorted.map((kf, i) => {
173
  if (i === sorted.length - 1) return null;
174
- const x1 = (kf.time / timelineDuration) * 100;
175
- const x2 = (sorted[i+1].time / timelineDuration) * 100;
176
  return (
177
  <div key={kf.id + '-line'} className="tl-kf-line"
178
  style={{ left: `${x1}%`, width: `${x2 - x1}%` }} />
@@ -181,19 +183,15 @@ const Timeline: React.FC = () => {
181
 
182
  {/* Keyframe diamonds */}
183
  {sorted.map(kf => (
184
- <div
185
- key={kf.id}
186
  className="tl-kf-diamond"
187
  style={{ left: `${(kf.time / timelineDuration) * 100}%` }}
188
  title={`${kf.time.toFixed(2)}s โ€ข click to delete`}
189
- onClick={(e) => {
190
- e.stopPropagation();
191
- removeKeyframe(obj.id, kf.id);
192
- }}
193
  >โ—†</div>
194
  ))}
195
 
196
- {/* Playhead ghost line on each track */}
197
  <div className="tl-track-playhead"
198
  style={{ left: `${playheadPct}%` }} />
199
  </div>
 
12
  addKeyframe, removeKeyframe,
13
  } = useStudioStore();
14
 
15
+ const rafRef = useRef<number>(0);
16
+ const lastTimeRef = useRef<number>(0);
17
+ const playheadRef = useRef<number>(playhead); // โ† ref so tick closure stays fresh
18
+ const rulerRef = useRef<HTMLDivElement>(null);
19
+
20
+ // Keep ref in sync with store value
21
+ useEffect(() => { playheadRef.current = playhead; }, [playhead]);
22
 
23
  // โ”€โ”€ Playback loop โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
24
  useEffect(() => {
 
29
  const tick = (now: number) => {
30
  const delta = (now - lastTimeRef.current) / 1000;
31
  lastTimeRef.current = now;
32
+ const next = playheadRef.current + delta;
33
+ if (next >= timelineDuration) {
34
+ setPlayhead(timelineDuration);
35
+ setTimelinePlaying(false);
36
+ return;
37
+ }
38
+ setPlayhead(next);
 
39
  rafRef.current = requestAnimationFrame(tick);
40
  };
41
  lastTimeRef.current = performance.now();
 
50
  if (!obj) return;
51
  const kf: Keyframe = {
52
  id: Math.random().toString(36).slice(2),
53
+ time: parseFloat(playheadRef.current.toFixed(3)),
54
+ position: [...obj.position] as [number, number, number],
55
+ rotation: [...obj.rotation] as [number, number, number],
56
+ scale: [...obj.scale] as [number, number, number],
57
  easing: 'ease-in-out',
58
  };
59
  addKeyframe(selectedId, kf);
60
+ }, [selectedId, objects, addKeyframe]);
61
 
62
  // โ”€โ”€ Keyboard shortcut: I = insert keyframe โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
63
  useEffect(() => {
 
79
  const fmt = (t: number) => {
80
  const s = Math.floor(t);
81
  const f = Math.floor((t % 1) * 30);
82
+ return `${String(s).padStart(2, '0')}:${String(f).padStart(2, '0')}`;
83
  };
84
 
85
  const playheadPct = (playhead / timelineDuration) * 100;
 
156
  <span>{i}s</span>
157
  </div>
158
  ))}
 
159
  <div className="tl-playhead" style={{ left: `${playheadPct}%` }} />
160
  </div>
161
 
162
  {/* Keyframe tracks */}
163
  <div className="tl-tracks">
164
  {objects.map(obj => {
165
+ const track = tracks.find(t => t.objectId === obj.id);
166
  const sorted = track
167
  ? [...track.keyframes].sort((a, b) => a.time - b.time)
168
  : [];
 
170
  <div key={obj.id}
171
  className={`tl-track ${selectedId === obj.id ? 'active' : ''}`}>
172
 
173
+ {/* Connection lines */}
174
  {sorted.map((kf, i) => {
175
  if (i === sorted.length - 1) return null;
176
+ const x1 = (kf.time / timelineDuration) * 100;
177
+ const x2 = (sorted[i+1].time / timelineDuration) * 100;
178
  return (
179
  <div key={kf.id + '-line'} className="tl-kf-line"
180
  style={{ left: `${x1}%`, width: `${x2 - x1}%` }} />
 
183
 
184
  {/* Keyframe diamonds */}
185
  {sorted.map(kf => (
186
+ <div key={kf.id}
 
187
  className="tl-kf-diamond"
188
  style={{ left: `${(kf.time / timelineDuration) * 100}%` }}
189
  title={`${kf.time.toFixed(2)}s โ€ข click to delete`}
190
+ onClick={(e) => { e.stopPropagation(); removeKeyframe(obj.id, kf.id); }}
 
 
 
191
  >โ—†</div>
192
  ))}
193
 
194
+ {/* Playhead ghost line */}
195
  <div className="tl-track-playhead"
196
  style={{ left: `${playheadPct}%` }} />
197
  </div>