import { DragSourceConnector, DragSourceMonitor, DropTargetConnector, DropTargetMonitor, DropTargetSpec } from 'react-dnd'; import ReactDOM from 'react-dom'; import { database } from '../../../common/database'; import { BaseModel } from '../../../models'; import * as models from '../../../models'; import { GrpcRequest } from '../../../models/grpc-request'; import { Request } from '../../../models/request'; import { RequestGroup } from '../../../models/request-group'; export type DnDDragProps = ReturnType; export type DnDDropProps = ReturnType; export type DnDProps = DnDDragProps & DnDDropProps; export interface DragObject { item?: GrpcRequest | Request | RequestGroup; } export const sourceCollect = (connect: DragSourceConnector, monitor: DragSourceMonitor) => ({ connectDragSource: connect.dragSource(), isDragging: monitor.isDragging(), }); export const targetCollect = (connect: DropTargetConnector, monitor: DropTargetMonitor) => ({ connectDropTarget: connect.dropTarget(), isDraggingOver: monitor.isOver(), }); export const isAbove = (monitor: DropTargetMonitor, component: any) => { const hoveredNode = ReactDOM.findDOMNode(component); // @ts-expect-error -- TSCONVERSION const hoveredTop = hoveredNode.getBoundingClientRect().top; const draggedTop = monitor.getSourceClientOffset()?.y; return draggedTop && hoveredTop > draggedTop; }; export const dropHandleCreator = ( { getParentId, getTargetId, }: { getParentId: (props: Props) => string | undefined; getTargetId: (props: Props) => string | undefined; } ): Required>['drop'] => (props, monitor, component) => { // the item comes from the dragSource const movingDoc = (monitor.getItem() as DragObject).item; const parentId = getParentId(props); const targetId = getTargetId(props); if (!movingDoc || !parentId) { return; } if (isAbove(monitor, component)) { moveDoc({ docToMove: movingDoc, parentId, targetId, targetOffset: 1 }); } else { moveDoc({ docToMove: movingDoc, parentId, targetId, targetOffset: -1 }); } }; export const hoverHandleCreator = (): Required>['hover'] => (_, monitor, component) => { if (isAbove(monitor, component)) { component.setDragDirection(1); } else { component.setDragDirection(-1); } }; const moveDoc = async ({ docToMove, parentId, targetId, targetOffset, }: { docToMove: BaseModel; parentId: string; targetId?: string; targetOffset: number; }) => { // Nothing to do. We are in the same spot as we started if (docToMove._id === targetId) { return; } // Don't allow dragging things into itself or children. This will disconnect // the node from the tree and cause the item to no longer show in the UI. const descendents = await database.withDescendants(docToMove); for (const doc of descendents) { if (doc._id === parentId) { return; } } function __updateDoc(doc, patch) { // @ts-expect-error -- TSCONVERSION return models.getModel(docToMove.type).update(doc, patch); } if (!targetId) { // We are moving to an empty area. No sorting required await __updateDoc(docToMove, { parentId }); return; } // NOTE: using requestToTarget's parentId so we can switch parents! const docs = [ ...(await models.request.findByParentId(parentId)), ...(await models.grpcRequest.findByParentId(parentId)), ...(await models.requestGroup.findByParentId(parentId)), ].sort((a, b) => (a.metaSortKey < b.metaSortKey ? -1 : 1)); // Find the index of doc B so we can re-order and save everything for (let i = 0; i < docs.length; i++) { const doc = docs[i]; if (doc._id === targetId) { let before, after; if (targetOffset < 0) { // We're moving to below before = docs[i]; after = docs[i + 1]; } else { // We're moving to above before = docs[i - 1]; after = docs[i]; } const beforeKey = before ? before.metaSortKey : docs[0].metaSortKey - 100; const afterKey = after ? after.metaSortKey : docs[docs.length - 1].metaSortKey + 100; if (Math.abs(afterKey - beforeKey) < 0.000001) { // If sort keys get too close together, we need to redistribute the list. This is // not performant at all (need to update all siblings in DB), but it is extremely rare // anyway console.log(`[app] Recreating Sort Keys ${beforeKey} ${afterKey}`); await database.bufferChanges(300); docs.map((r, i) => __updateDoc(r, { metaSortKey: i * 100, parentId, }), ); } else { const metaSortKey = afterKey - (afterKey - beforeKey) / 2; __updateDoc(docToMove, { metaSortKey, parentId, }); } break; } } };