Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { type FC, type MouseEvent, useCallback, useContext, useMemo } from "react";
import { IconInterpolationAdd, IconInterpolationRemove, IconKeypointAdd, IconKeypointDelete } from "@humansignal/ui";
import { TimelineContext } from "../../Context";
import { ControlButton } from "../../Controls";
import type { TimelineExtraControls } from "../../Types";
 
type Actions = "keypoint_add" | "keypoint_remove" | "lifespan_add" | "lifespan_remove";
type DataType = {
  frame: number;
};
 
export const Controls: FC<TimelineExtraControls<Actions, DataType>> = ({ onAction }) => {
  const { position, regions, readonly } = useContext(TimelineContext);
  const hasSelectedRegion = regions.some(({ selected, timeline }) => selected && !timeline);
  const closestKeypoint = useMemo(() => {
    const region = regions.find((r) => r.selected && !r.timeline);
 
    return region?.sequence.filter(({ frame }) => frame <= position).slice(-1)[0];
  }, [regions, position]);
 
  const canAddKeypoint = closestKeypoint?.frame !== position;
  const canAddLifespan = closestKeypoint?.enabled === false;
 
  const onKeypointToggle = useCallback(
    (e: MouseEvent) => {
      if (canAddKeypoint) {
        onAction?.(e, "keypoint_add", {
          frame: position,
        });
      } else {
        onAction?.(e, "keypoint_remove", {
          frame: closestKeypoint!.frame,
        });
      }
    },
    [onAction, canAddKeypoint, position, closestKeypoint?.frame],
  );
 
  const onLifespanToggle = useCallback(
    (e: MouseEvent) => {
      if (canAddLifespan) {
        onAction?.(e, "lifespan_add", {
          frame: closestKeypoint!.frame,
        });
      } else {
        onAction?.(e, "lifespan_remove", {
          frame: closestKeypoint!.frame,
        });
      }
    },
    [onAction, canAddLifespan, closestKeypoint?.frame],
  );
 
  const keypointIcon = useMemo(() => {
    if (canAddKeypoint) {
      return <IconKeypointAdd />;
    }
 
    return <IconKeypointDelete />;
  }, [canAddKeypoint, closestKeypoint]);
 
  const interpolationIcon = useMemo(() => {
    if (canAddLifespan) {
      return <IconInterpolationAdd />;
    }
 
    return <IconInterpolationRemove />;
  }, [closestKeypoint, canAddLifespan]);
 
  return (
    <>
      <ControlButton onClick={onKeypointToggle} disabled={!hasSelectedRegion || readonly} tooltip="Toggle Keypoint">
        {keypointIcon}
      </ControlButton>
 
      <ControlButton onClick={onLifespanToggle} disabled={!closestKeypoint || readonly} tooltip="Toggle Interpolation">
        {interpolationIcon}
      </ControlButton>
    </>
  );
};