diff --git a/apps/yaak-proxy/components/FlowListHeader.tsx b/apps/yaak-proxy/components/FlowListHeader.tsx index 1e002ab48..a232080f8 100644 --- a/apps/yaak-proxy/components/FlowListHeader.tsx +++ b/apps/yaak-proxy/components/FlowListHeader.tsx @@ -38,6 +38,7 @@ import { type ColumnPrefs, type CustomColumnId, canHide, + dropBoundary, hideOtherColumns, isCustomColumn, moveColumn, @@ -409,7 +410,7 @@ function useColumnDrag({ id: ColumnId; /** The pointer, across the window */ x: number; - /** The boundary it would drop at, and that boundary's line, or null where it would not move */ + /** The boundary it would move to, null where it would stay, and the line drawn on it */ to: number | null; line: number | null; frame: number; @@ -433,6 +434,8 @@ function useColumnDrag({ if (column == null) return; state.current = { id: column.id, x: pointer.current, to: null, line: null, frame: 0 }; setDragging(column); + // The line shows from the drag's first movement, not a frame later + target(state.current); const tick = () => { const s = state.current; @@ -511,18 +514,14 @@ function useColumnDrag({ /** Sets where the drag would drop, from the pointer and the labels where they are now */ function target(s: NonNullable) { - // The boundary before the first label whose middle is right of the pointer const cells = Array.from( gridRef.current?.querySelectorAll(":scope > [data-column]") ?? [], ).map((el) => el.getBoundingClientRect()); - let to = cells.findIndex((r) => s.x < r.left + r.width / 2); - if (to === -1) to = cells.length; - // Where it already is, it would not move, so no line. Its place is read each time, as the - // columns can change under a drag: Operation shows itself when a flow has one. + // Its place is read each time, as the columns can change under a drag: Operation shows + // itself when a flow has one. const from = latest.current.columns.findIndex((c) => c.id === s.id); - const moves = from !== -1 && to !== from && to !== from + 1; - s.to = moves ? to : null; - const line = moves ? (cells[to]?.left ?? cells.at(-1)?.right ?? null) : null; + const { to, line } = dropBoundary(s.x, cells, from); + s.to = to; // Told again only when it changes: another boundary, or the same one scrolled if (line !== s.line) latest.current.onDropLine(line); s.line = line; diff --git a/apps/yaak-proxy/lib/columns.test.ts b/apps/yaak-proxy/lib/columns.test.ts index 446b3c71c..cafcb4b3c 100644 --- a/apps/yaak-proxy/lib/columns.test.ts +++ b/apps/yaak-proxy/lib/columns.test.ts @@ -11,6 +11,7 @@ import { customPathProblem, customValue, defaultColumnPrefs, + dropBoundary, fieldReference, fieldReferenceChanges, gridTemplate, @@ -322,6 +323,27 @@ describe("column order", () => { expect(parseColumnPrefs({ order: "time" }).order).toEqual([]); }); + test("the drop line shows at every boundary, its own slot's edges included", () => { + // Four columns 100px wide from x 24, after the mark column; the third is dragged + const cells = [24, 124, 224, 324].map((left) => ({ left, right: left + 100 })); + const at = (x: number) => dropBoundary(x, cells, 2); + // Over the mark column or the first's left half: the first boundary + expect(at(5)).toEqual({ to: 0, line: 24 }); + expect(at(60)).toEqual({ to: 0, line: 24 }); + expect(at(80)).toEqual({ to: 1, line: 124 }); + // The left neighbour's near half and its own left half: its left edge, staying + expect(at(190)).toEqual({ to: null, line: 224 }); + expect(at(250)).toEqual({ to: null, line: 224 }); + // Its own right half and the right neighbour's near half: its right edge, staying + expect(at(290)).toEqual({ to: null, line: 324 }); + expect(at(350)).toEqual({ to: null, line: 324 }); + // Past the right neighbour's middle, and past the end + expect(at(380)).toEqual({ to: 4, line: 424 }); + expect(at(900)).toEqual({ to: 4, line: 424 }); + // The last column dragged to the end stays + expect(dropBoundary(900, cells, 3)).toEqual({ to: null, line: 424 }); + }); + test("Reset Column Order puts every column back", () => { let prefs = moveColumn(defaultColumnPrefs, "time", 0); prefs = toggleColumn(prefs, "latency"); diff --git a/apps/yaak-proxy/lib/columns.ts b/apps/yaak-proxy/lib/columns.ts index e55e2b4a2..693cd9ee4 100644 --- a/apps/yaak-proxy/lib/columns.ts +++ b/apps/yaak-proxy/lib/columns.ts @@ -298,6 +298,25 @@ export function resetOrder(prefs: ColumnPrefs): ColumnPrefs { return { ...prefs, order: [] }; } +/** + * Where a dragged column would drop, from the pointer and the shown columns' edges, left to + * right: the boundary before the first column whose middle is right of the pointer, and the + * line's place on it. Over its own column or the near half of a neighbour, that is an edge of + * its own slot, where it would stay, so `to` is null; the line still shows there. Left of the + * first column, over the fixed mark column, it is the first boundary. + */ +export function dropBoundary( + x: number, + cells: { left: number; right: number }[], + from: number, +): { to: number | null; line: number | null } { + let at = cells.findIndex((r) => x < (r.left + r.right) / 2); + if (at === -1) at = cells.length; + const line = cells[at]?.left ?? cells.at(-1)?.right ?? null; + const stays = from === -1 || at === from || at === from + 1; + return { to: stays ? null : at, line }; +} + /** Shows `id` alone */ export function hideOtherColumns(prefs: ColumnPrefs, id: ColumnId): ColumnPrefs { const visible: ColumnPrefs["visible"] = {};