anyone-oslo/pages

View on GitHub
app/javascript/components/drag/useDragUploader.ts

Summary

Maintainability
D
1 day
Test Coverage
import { useEffect, useState } from "react";

import { Draggable, DragCollection, DragState, Position } from "./types";

function containsFiles(evt: Event) {
  if ("dataTransfer" in evt) {
    const dataTransfer: DataTransfer = evt.dataTransfer;
    if ("types" in dataTransfer) {
      const types = dataTransfer.types;
      for (let i = 0; i < types.length; i++) {
        if (types[i] === "Files" || types[i] === "application/x-moz-file") {
          return true;
        }
      }
    }
  }
  return false;
}

function getFiles(dt: DataTransfer): File[] {
  const files: File[] = [];
  if (dt.items) {
    for (let i = 0; i < dt.items.length; i++) {
      if (dt.items[i].kind == "file") {
        files.push(dt.items[i].getAsFile());
      }
    }
  } else {
    for (let i = 0; i < dt.files.length; i++) {
      files.push(dt.files[i]);
    }
  }
  return files;
}

function mousePosition(evt: TouchEvent | MouseEvent): Position {
  let x: number | null, y: number | null;
  if ("touches" in evt && evt.type == "touchmove") {
    x = evt.touches[0].clientX;
    y = evt.touches[0].clientY;
  } else if (evt instanceof MouseEvent) {
    x = evt.clientX;
    y = evt.clientY;
  }
  return { x: x, y: y };
}

export default function useDragUploader(
  collections: DragCollection[],
  onDragEnd: (dragState: DragState, files: File[]) => void
) {
  const initialState: DragState = {
    dragging: false,
    x: null,
    y: null
  };

  const [dragState, setDragState] = useState(initialState);

  const updatePositions = (dragging: Draggable | null) => {
    collections.forEach((c) => {
      c.dispatch({ type: "updatePositions", payload: dragging });
    });
  };

  const startDrag = (evt: Event, draggable: Draggable) => {
    updatePositions(draggable);
    setDragState({ dragging: draggable, ...mousePosition(evt) });
  };

  const drag = (evt: Event) => {
    if (dragState.dragging) {
      evt.stopPropagation();
      evt.preventDefault();
      setDragState({ ...dragState, ...mousePosition(evt) });
    } else {
      if (containsFiles(evt)) {
        startDrag(evt, "Files");
      }
    }
  };

  const dragEnd = (evt: Event) => {
    if (dragState.dragging) {
      const prevDragState = dragState;
      let files: File[] = [];
      evt.stopPropagation();
      evt.preventDefault();
      if ("dataTransfer" in evt && dragState.dragging == "Files") {
        files = getFiles(evt.dataTransfer);
      }
      setDragState({ dragging: false, x: null, y: null });
      onDragEnd(prevDragState, files);
      updatePositions();
    }
  };

  const dragLeave = (evt: Event) => {
    if (dragState.dragging === "Files") {
      evt.preventDefault();
      evt.stopPropagation();
      setDragState({ dragging: false, x: null, y: null });
    }
  };

  useEffect(() => {
    window.addEventListener("mousemove", drag);
    window.addEventListener("touchmove", drag);
    window.addEventListener("mouseup", dragEnd);
    window.addEventListener("touchend", dragEnd);
    window.addEventListener("mouseout", dragLeave);
    return function cleanup() {
      window.removeEventListener("mousemove", drag);
      window.removeEventListener("touchmove", drag);
      window.removeEventListener("mouseup", dragEnd);
      window.removeEventListener("touchend", dragEnd);
      window.removeEventListener("mouseout", dragLeave);
    };
  });

  return [dragState, startDrag, { onDragOver: drag, onDrop: dragEnd }];
}