anyone-oslo/pages

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

Summary

Maintainability
A
0 mins
Test Coverage
import { createRef, useEffect, useReducer, useRef } from "react";
import { uniqueId } from "lodash";

function getPosition<T>(draggable: Drag.Draggable<T>) {
  if (draggable && "ref" in draggable && draggable.ref.current) {
    return draggable.ref.current.getBoundingClientRect();
  } else {
    return null;
  }
}

function hideDraggable<T>(
  draggable: Drag.Draggable<T> | null,
  callback: () => Drag.Draggable<T>[]
) {
  if (draggable && "ref" in draggable && draggable.ref.current) {
    const prevDisplay = draggable.ref.current.style.display;
    draggable.ref.current.style.display = "none";
    const result = callback();
    draggable.ref.current.style.display = prevDisplay;
    return result;
  } else {
    return callback();
  }
}

function insertFiles<T>(
  state: Drag.Item<T>[],
  files: Drag.Item<T>[]
): Drag.Item<T>[] {
  const index = state.indexOf("Files");
  if (index === -1 || !files) {
    return state;
  } else {
    return [...state.slice(0, index), ...files, ...state.slice(index + 1)];
  }
}

function dragCollectionReducer<T = Drag.DraggableRecord>(
  state: Drag.Item<T>[],
  action: Drag.CollectionAction<T>
): Drag.Item<T>[] {
  switch (action.type) {
    case "append":
      return [...state, ...(action.payload as Drag.Item<T>[])];
    case "prepend":
      return [...(action.payload as Drag.Item<T>[]), ...state];
    case "insertFiles":
      return insertFiles(state, action.payload as Drag.Item<T>[]);
    case "update":
      return state.map((d: Drag.Draggable<T>) => {
        return d.handle === (action.payload as Drag.Draggable<T>).handle
          ? (action.payload as Drag.Item<T>)
          : d;
      });
    case "updatePositions":
      return hideDraggable(action.payload as Drag.Draggable<T>, () => {
        return state.map((d: Drag.Draggable<T>) => {
          return { ...d, rect: getPosition(d) };
        });
      });
    case "remove":
      return state.filter(
        (d: Drag.Draggable<T>) =>
          d.handle !== (action.payload as Drag.Draggable<T>).handle
      );
    case "replace":
      return action.payload as Drag.Item<T>[];
    case "reorder":
      return action.payload as Drag.Item<T>[];
    default:
      return state;
  }
}

export function createDraggable<T = Drag.DraggableRecord>(
  record: T
): Drag.Item<T> {
  return {
    record: record,
    rect: null,
    ref: createRef(),
    handle: uniqueId("draggable")
  };
}

export default function useDragCollection<T = Drag.DraggableRecord>(
  records: Array<T>
): Drag.Collection<T> {
  const containerRef = useRef<HTMLDivElement>(null);
  const [draggables, dispatch] = useReducer(dragCollectionReducer<T>, [], () =>
    records.map((r) => createDraggable(r))
  );

  useEffect(() => {
    dispatch({ type: "updatePositions" });
  }, []);

  return { ref: containerRef, draggables: draggables, dispatch: dispatch };
}