Z ZPI 文档 v0.3.8
介绍页

源码

packages/desktop/src/renderer/task-group-drag.ts

typescript126 行查看原文
// Drag lifecycle and final-position commands adapted from ZCode project-sidebar/grouped (Apache-2.0), 3c5b2276.
import { type CollisionDetection, closestCenter } from "@dnd-kit/core";
import {
  sameGroupNode,
  type TaskGroupCommand,
  type TaskGroupNode,
  type TaskGroupState,
  taskGroupFor,
} from "../shared/task-groups.ts";
export type GroupDropTarget =
  | TaskGroupNode
  | { type: "header" | "footer" | "empty" | "collapsed"; id: string };
// ZCode separates whole-group reorder targets from task header/footer/empty targets.
export const taskGroupCollisionDetection: CollisionDetection = (args) => {
  const active = args.active.data.current as TaskGroupNode | undefined;
  return closestCenter({
    ...args,
    droppableContainers: args.droppableContainers.filter((container) => {
      const target = container.data.current as GroupDropTarget | undefined;
      return active?.type === "group"
        ? target?.type === "group" || target?.type === "task"
        : target?.type !== "group";
    }),
  });
};

/** Commit the dragged node relative to its final visible neighbors, as in ZCode groupedMoveCommand. */
export function taskGroupMoveCommand(
  origin: TaskGroupState,
  preview: TaskGroupState,
  source: TaskGroupNode,
  visibleTaskIds?: ReadonlySet<string>,
): TaskGroupCommand | undefined {
  const locate = (state: TaskGroupState) => {
    const groupId = source.type === "task" ? taskGroupFor(state, source.id) : null;
    const siblings: TaskGroupNode[] = (
      groupId ? (state.members[groupId] ?? []).map((id) => ({ type: "task" as const, id })) : state.roots
    ).filter((node) => node.type === "group" || !visibleTaskIds || visibleTaskIds.has(node.id));
    return { groupId, siblings, index: siblings.findIndex((node) => sameGroupNode(node, source)) };
  };
  const before = locate(origin),
    after = locate(preview);
  if (
    before.index < 0 ||
    after.index < 0 ||
    (before.groupId === after.groupId && before.index === after.index)
  )
    return;
  const next = after.siblings[after.index + 1],
    previous = after.siblings[after.index - 1];
  if (source.type === "group") {
    const target = next ?? previous;
    return target
      ? { type: "moveGroup", id: source.id, target, position: next ? "before" : "after" }
      : undefined;
  }
  return {
    type: "moveTask",
    id: source.id,
    sourceGroupId: before.groupId,
    groupId: after.groupId,
    ...(next ? { before: next } : previous ? { after: previous } : { start: true }),
  };
}

export function taskGroupOrderSignature(state: TaskGroupState): string {
  return JSON.stringify([state.roots, state.members]);
}
/** ZCode: upward over a header = root-before, downward = group-start; footer is the inverse. */
export function taskGroupDropCommand(
  state: TaskGroupState,
  active: TaskGroupNode,
  over: GroupDropTarget,
  position: "before" | "after",
): TaskGroupCommand | undefined {
  if (active.type === "group") {
    const target: TaskGroupNode = over.type === "task" ? over : { type: "group", id: over.id };
    const targetGroupId = target.type === "group" ? target.id : taskGroupFor(state, target.id);
    // 重排后自身目标会跟到指针下;忽略它,保留最后有效预览,避免用 origin 重算而跳回原位。
    if (targetGroupId === active.id) return;
    return { type: "moveGroup", id: active.id, target, position };
  }
  if (over.type === "task") {
    if (active.id === over.id) return;
    return { type: "moveTask", id: active.id, groupId: taskGroupFor(state, over.id), [position]: over };
  }
  if (over.type === "empty" || (over.type === "header" && position === "after")) {
    return { type: "moveTask", id: active.id, groupId: over.id, start: true };
  }
  if (over.type === "footer" && position === "before") {
    return { type: "moveTask", id: active.id, groupId: over.id, before: null };
  }
  return { type: "moveTask", id: active.id, groupId: null, [position]: { type: "group", id: over.id } };
}

export interface TaskGroupDragMotion {
  deltaY: number;
  position: "before" | "after";
  target?: GroupDropTarget;
}
export function taskGroupDragMotion(
  previous: TaskGroupDragMotion,
  deltaY: number,
  target = previous.target,
): TaskGroupDragMotion {
  return {
    deltaY,
    target,
    position: deltaY > previous.deltaY ? "after" : deltaY < previous.deltaY ? "before" : previous.position,
  };
}
export function canStartTaskGroupDrag(
  event: { isPrimary: boolean; button: number },
  interactiveControl: boolean,
  dragTitle = false,
): boolean {
  return event.isPrimary && event.button === 0 && (!interactiveControl || dragTitle);
}
export function taskGroupRowDragProps(active: TaskGroupNode | undefined, id: string, hidden = false) {
  return {
    dragging: active?.type === "task" && active.id === id,
    tooltipsDisabled: Boolean(active) || hidden,
    interactionDisabled: hidden,
  };
}