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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import { Button, IconChevronLeft, IconChevronRight } from "@humansignal/ui";
import { observer } from "mobx-react";
import { useCallback, useEffect, useRef, useState } from "react";
import { cn } from "../../utils/bem";
import { clamp, sortAnnotations } from "../../utils/utilities";
import { AnnotationButton } from "./AnnotationButton";
import "./AnnotationsCarousel.scss";
 
interface AnnotationsCarouselInterface {
  store: any;
  annotationStore: any;
  commentStore?: any;
}
 
export const AnnotationsCarousel = observer(({ store, annotationStore }: AnnotationsCarouselInterface) => {
  const [entities, setEntities] = useState<any[]>([]);
  const enableAnnotations = store.hasInterface("annotations:tabs");
  const enablePredictions = store.hasInterface("predictions:tabs");
  const enableCreateAnnotation = store.hasInterface("annotations:add-new");
  const groundTruthEnabled = store.hasInterface("ground-truth");
  const enableAnnotationDelete = store.hasInterface("annotations:delete");
  const carouselRef = useRef<HTMLElement>();
  const containerRef = useRef<HTMLElement>();
  const [currentPosition, setCurrentPosition] = useState(0);
  const [isLeftDisabled, setIsLeftDisabled] = useState(false);
  const [isRightDisabled, setIsRightDisabled] = useState(false);
 
  const updatePosition = useCallback(
    (_e: React.MouseEvent, goLeft = true) => {
      if (containerRef.current && carouselRef.current) {
        const step = containerRef.current.clientWidth;
        const carouselWidth = carouselRef.current.clientWidth;
        const newPos = clamp(goLeft ? currentPosition - step : currentPosition + step, 0, carouselWidth - step);
 
        setCurrentPosition(newPos);
      }
    },
    [containerRef, carouselRef, currentPosition],
  );
 
  useEffect(() => {
    setIsLeftDisabled(currentPosition <= 0);
    setIsRightDisabled(
      currentPosition >= (carouselRef.current?.clientWidth ?? 0) - (containerRef.current?.clientWidth ?? 0),
    );
  }, [
    entities.length,
    containerRef.current,
    carouselRef.current,
    currentPosition,
    window.innerWidth,
    window.innerHeight,
  ]);
 
  useEffect(() => {
    const newEntities = [];
 
    if (enablePredictions) newEntities.push(...annotationStore.predictions);
 
    if (enableAnnotations) newEntities.push(...annotationStore.annotations);
    setEntities(newEntities);
  }, [annotationStore, JSON.stringify(annotationStore.predictions), JSON.stringify(annotationStore.annotations)]);
 
  return enableAnnotations || enablePredictions || enableCreateAnnotation ? (
    <div
      className={cn("annotations-carousel")
        .mod({ scrolled: currentPosition > 0 })
        .toClassName()}
      style={{ "--carousel-left": `${currentPosition}px` } as any}
    >
      <div ref={containerRef as any} className={cn("annotations-carousel").elem("container").toClassName()}>
        <div ref={carouselRef as any} className={cn("annotations-carousel").elem("carosel").toClassName()}>
          {sortAnnotations(entities).map((entity) => (
            <AnnotationButton
              key={entity?.id}
              entity={entity}
              capabilities={{
                enablePredictions,
                enableCreateAnnotation,
                groundTruthEnabled,
                enableAnnotations,
                enableAnnotationDelete,
              }}
              annotationStore={annotationStore}
            />
          ))}
        </div>
      </div>
      {(!isLeftDisabled || !isRightDisabled) && (
        <div className={cn("annotations-carousel").elem("carousel-controls").toClassName()}>
          <Button
            disabled={isLeftDisabled}
            aria-label="Carousel left"
            size="small"
            variant="neutral"
            onClick={(e) => !isLeftDisabled && updatePosition(e, true)}
          >
            <IconChevronLeft />
          </Button>
          <Button
            disabled={isRightDisabled}
            aria-label="Carousel right"
            size="small"
            variant="neutral"
            onClick={(e) => !isRightDisabled && updatePosition(e, false)}
          >
            <IconChevronRight />
          </Button>
        </div>
      )}
    </div>
  ) : null;
});