export default function MovableRect()

in packages/annotated/bbox/src/MovableRect.js [11:368]


export default function MovableRect({
  id,
  preload,
  document,
  getFrame,
  getLabel,
  defaultBox,
  fps = 30,
}) {
  const { get, set, push, state } = useStore();
  const k = React.useMemo(() => keyframes(), []);

  // TODO: Don't hardcode,
  const FPS = fps; // kept for now for backwards compat with AVD task,
  // in the future, by adding `on{Next,Previous,First,Last}Keyframe` methods,
  // we can make this component unaware of the VideoPlayer layer id and fps.

  // const layerInfo = useContext(LayerContext);
  // id = id || layerInfo?.id || undefined;

  const path = dataPathBuilderFor(id);
  const vidData = dataPathBuilderFor("Video");

  const isSelected = state.selectedLayer && state.selectedLayer[0] === id;

  const movable = !get(vidData("playing"));
  //   const getFrame = (get) => {
  //     const movable = !get(vidData("playing"));
  //     const playedSeconds = get(vidData("playedSeconds"));
  //     const currentFrame = Math.round(playedSeconds * FPS);
  //     const ts = movable ? currentFrame : currentFrame + 1;
  //     return ts;
  //   };

  const ts = getFrame(get);

  const vidLength = get(vidData("duration"));

  const DEFAULT_BOX = defaultBox || [10, 10, 100, 100]; // [top, left, width, height]

  const [boxState, setState] = React.useState({
    top: DEFAULT_BOX[0],
    left: DEFAULT_BOX[1],
    width: DEFAULT_BOX[2],
    height: DEFAULT_BOX[3],
  });

  const [lastFrame, setLastFrame] = React.useState();

  React.useEffect(() => {
    if (preload) {
      const length = preload.length;
      preload.forEach(({ x, y, width, height, frameNumber }, idx) => {
        const frame = {
          time: frameNumber,
          value: [x, y, width, height],
        };
        k.add(frame);
        if (idx === preload.length - 1) {
          setLastFrame(frame);
        }
      });
      set(path("kf"), k.frames);
    }
  }, []);

  const requestQueue = get(requestsPathFor(id));
  React.useEffect(() => {
    if (!requestQueue || requestQueue.length === 0) return;
    requestQueue.forEach((request) => {
      process(request);
    });
    set(requestsPathFor(id), []);
  }, [requestQueue]);
  const process = React.useCallback(
    (req) => {
      if (req.type === "prevKf") {
        const ts = getFrame(get);
        const prevTs = k.previous(ts);
        if (prevTs) {
          push(requestsPathFor("Video"), {
            type: "seek",
            payload: prevTs.time / FPS,
            // TODO: allow a FPS/seconds toggle?^
          });
        }
      } else if (req.type === "nextKf") {
        const ts = getFrame(get);
        const nextTs = k.next(ts);
        if (nextTs) {
          push(requestsPathFor("Video"), {
            type: "seek",
            payload: nextTs.time / FPS,
            // TODO: allow a FPS/seconds toggle?^
          });
        }
      } else if (req.type === "firstKf") {
        push(requestsPathFor("Video"), {
          type: "seek",
          payload: k.frames[0].time / FPS,
          // TODO: allow a FPS/seconds toggle?^
        });
      } else if (req.type === "lastKf") {
        push(requestsPathFor("Video"), {
          type: "seek",
          payload: k.frames[k.frames.length - 1].time / FPS,
          // TODO: allow a FPS/seconds toggle?^
        });
      } else if (req.type === "addFrame" || req.type === "addFrameAt") {
        // 1. get interpolated values
        const ts = req.payload || getFrame(get);
        const val = k.value(ts);
        const isKeyframe = k.getIndex(ts) >= 0;
        const [left, top, width, height] =
          val === null ? DEFAULT_BOX : val.map((x) => Math.round(x));

        // 2. create/update keyframe here with interpolate values:
        let idx = k.getIndex(ts);
        while (idx >= 0) {
          k.splice(idx, 1);
          idx = k.getIndex(ts);
        }
        k.add({
          time: ts,
          value: [left, top, width, height],
        });
        set(path("kf"), k.frames);

        // if a last frame was set and the user intentionally set a keyframe after that, blow out the lastFrame designation
        if (lastFrame && ts > lastFrame.time) {
          // TODO: this may be incorrect, we may unintentionally trigger this due to resolution errors
          // setLastFrame(null)

          //TODO: alternatively, instead of blowing out the lastFrame designation, we could promote the current frame as that instead
          setLastFrame({ time: ts, value: [left, top, width, height] });
        }
      } else if (req.type === "removeFrame") {
        let idx = k.getIndex(ts);
        while (idx >= 0) {
          k.splice(idx, 1);
          idx = k.getIndex(ts);
        }
        set(path("kf"), k.frames);
      } else if (req.type === "setFirst") {
        // Summary: set a keyframe at current time with interpolated values, and delete all previous keyframes

        // 1. get interpolated values
        const ts = getFrame(get);
        const val = k.value(ts);
        const isKeyframe = k.getIndex(ts) >= 0;
        const [left, top, width, height] =
          val === null ? DEFAULT_BOX : val.map((x) => Math.round(x));

        // 2. create/update keyframe here with interpolate values:
        let idx = k.getIndex(ts);
        while (idx >= 0) {
          k.splice(idx, 1);
          idx = k.getIndex(ts);
        }
        k.add({
          time: ts,
          value: [left, top, width, height],
        });

        // 3. remove all keyframes before this one:
        k.splice(0, k.getIndex(ts));

        // 4. Update data state
        set(path("kf"), k.frames);
      } else if (req.type === "setLast") {
        // Summary: set a keyframe at current time with interpolated values, and delete all future keyframes

        // 1. get interpolated values
        const ts = getFrame(get);
        const val = k.value(ts);
        const isKeyframe = k.getIndex(ts) >= 0;
        const [left, top, width, height] =
          val === null ? DEFAULT_BOX : val.map((x) => Math.round(x));

        // 2. create/update keyframe here with interpolate values:
        let idx = k.getIndex(ts);
        while (idx >= 0) {
          k.splice(idx, 1);
          idx = k.getIndex(ts);
        }
        k.add({
          time: ts,
          value: [left, top, width, height],
        });

        // 3. remove all keyframes after this one:
        k.splice(k.getIndex(ts) + 1, k.frames.length - k.getIndex(ts) - 1);

        // 4. Update data state
        set(path("kf"), k.frames);
        setLastFrame({
          time: ts,
          value: [left, top, width, height],
        });
      }
    },
    [k, get]
  );

  React.useEffect(() => {
    if (vidLength) {
      const ts = getFrame(get);
      if (preload) return;
      k.add({ time: ts, value: DEFAULT_BOX });
      // k.add({ time: vidLength, value: DEFAULT_BOX });
      set(path("kf"), k.frames);
    }
  }, [vidLength]);

  const handleResize = (style, isShiftKey, type) => {
    // type is a string and it shows which resize-handler you clicked
    // e.g. if you clicked top-right handler, then type is 'tr'
    let { top, left, width, height } = style;
    top = Math.round(top);
    left = Math.round(left);
    width = Math.round(width);
    height = Math.round(height);
    setState({
      ...boxState,
      top,
      left,
      width,
      height,
    });
    if (!movable) return;
    let idx = k.getIndex(ts);
    while (idx >= 0) {
      k.splice(idx, 1);
      idx = k.getIndex(ts);
    }
    k.add({
      time: ts,
      value: [left, top, width, height],
    });
    set(path("kf"), k.frames);
    if (state.selectedLayer.join("|") !== id) {
      set(["selectedLayer"], [id]);
    }
  };

  // const handleRotate = (rotateAngle) => {
  //   setState({ ...state, rotateAngle });
  // };

  const handleDrag = (deltaX, deltaY) => {
    if (!movable) return;

    const val = k.value(ts);
    const [left, top, width, height] =
      val === null
        ? [boxState.top, boxState.left, boxState.height, boxState.width]
        : val.map((x) => Math.round(x));

    setState({
      left: left + deltaX,
      top: top + deltaY,
      width,
      height,
    });
    let idx = k.getIndex(ts);
    while (idx >= 0) {
      k.splice(idx, 1);
      idx = k.getIndex(ts);
    }
    k.add({
      time: ts,
      value: [left + deltaX, top + deltaY, width, height],
    });
    set(path("kf"), k.frames);
    set(["selectedLayer"], [id]);
  };

  const handleSelect = () => {
    set(["selectedLayer"], [id]);
  };

  const isKeyframe = k.getIndex(ts) >= 0;

  const val = k.value(ts);
  const [left, top, width, height] =
    val === null ? DEFAULT_BOX : val.map((x) => Math.round(x));

  React.useEffect(() => {
    if (movable) {
      // same logic as below, can probably extract into a fn:
      if (ts < k.frames[0] && k.frames[0].time) {
        set(path("dim"), null);
      } else if (lastFrame && ts > lastFrame.time) {
        set(path("dim"), null);
      } else {
        set(path("dim"), [left, top, width, height]);
      }
    }
  }, [left, top, width, height, ts]);

  if (k.frames.length > 0) {
    if (ts < k.frames[0].time) {
      return null;
    } else if (lastFrame && ts > lastFrame.time) {
      return null;
    } /* else if (ts > k.frames[k.frames.length - 1].time) {
        return null;
      } */
  }

  return (
    <div
      className={cx(
        "movable-rect-frame",
        { keyframe: isKeyframe },
        { immovable: !movable },
        { selected: isSelected }
      )}
    >
      {!getLabel ? null : (
        <div
          style={{
            position: "absolute",
            left: left,
            top: top,
            backgroundColor: "white",
            color: "black",
            zIndex: 99,
          }}
        >
          {getLabel(get)}
        </div>
      )}
      <ResizableRect
        document={document || window.document}
        left={left}
        top={top}
        width={width}
        height={height}
        rotateAngle={0}
        // aspectRatio={false}
        // minWidth={10}
        // minHeight={10}
        zoomable="n, w, s, e, nw, ne, se, sw"
        rotatable={false}
        // onRotateStart={handleRotateStart}
        // onRotate={handleRotate}
        // onRotateEnd={handleRotateEnd}
        // onResizeStart={handleResizeStart}
        onResize={handleResize}
        // onResizeEnd={handleUp}
        onDragStart={handleSelect}
        onDrag={handleDrag}
        // onDragEnd={handleDragEnd}
      />
    </div>
  );
}