Repository navigation
feat(gantt): snap single issue bar drags, resizes and drops to working days - #11060
ArtyomSavchenko merged 5 commits into
Conversation
Add prevWorkingDay (mirror of nextWorkingDay), findWorkingDay (the nearest working day in a direction, or undefined when none lies within the 60-day search window, so a caller can tell a failed search from a result), workingDaySpan (inclusive working-day length of a bar, never below 1), dueForSpan / startForSpan (the far end of a bar with a given working-day span) and workingDaysPerWeek (active weekdays of the mask). The helpers stay bounded for degenerate input: non-finite or inverted ranges count as one working day, ranges, spans and addWorkingDays step counts are capped at MAX_WORKING_SPAN_DAYS (about 100 years), and addWorkingDays treats a non-finite step count as zero instead of looping forever. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
reduce() takes an optional WorkingCalendar. With a calendar, a single-bar body drag lands the start on the nearest working day in the drag direction and keeps the bar's length in working days; the start handle rounds up to the next working day and the end handle rounds down to the previous one; a dropped unscheduled issue starts on the next working day and lasts two working days. A zero-delta move leaves the bar untouched. A co-drag keeps its shared raw delta, and without a calendar the reducer is unchanged. Degenerate calendars fall back to calendar days: a calendar without any working weekday, a bar whose origin is not a finite range of at most MAX_WORKING_SPAN_DAYS days, and a preview with no working day within reach (a holiday blackout longer than the 60-day search window), so a non-working day is never presented as a snapped result. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…ldren and keyboard shifts by working days GanttView passes the project calendar to the drag reducer for single issue bars; milestones and bulk co-drags keep calendar-day drags. A parent drag shifts its children by the parent's move measured in working days (shiftWithPrimary), and the arrow keys move a bar by working days (shiftScheduleDays), with Shift moving one week = the number of active weekdays (keyboardWeekStep). The help overlay now says "±1 week". Without a project calendar every path keeps its calendar-day arithmetic. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
|
@MichaelUray Please take a look at the following review comments:
|
Holding Shift during a single issue bar drag, resize or sidebar drop suspends working-day snapping for that gesture: the preview and the committed dates move in calendar days, exactly as in a project without working days, and the children of a dragged parent shift by the same calendar-day delta. Pressing or releasing Shift mid-drag updates the preview at once, so the user can decide before dropping. A "Calendar days" hint is shown under the date pill while snapping is suspended. The commit uses the modifier state of the release event itself, and losing window focus counts as releasing Shift, so a Shift let go outside the window cannot leave the drag committing calendar days. Shift is the free modifier during a drag: Alt is already read at release to bypass the cascade simulation (and Alt+drag is grabbed by several Linux window managers), Cmd/Ctrl toggle the selection, and Shift only range-selects on a plain click. A body drag still has to start without a modifier; Shift is pressed once the bar moves. The reducer takes `calendarDays` on `mousemove` and marks the state `snapSuspended` only when a calendar would otherwise apply; legacy mode, co-drags and milestones are unchanged. `dragCalendar(state, calendar)` gives the commit the calendar for the drag's own arithmetic. `modifierSyncMove(state, calendarDays, lastMove)` yields the move that re-syncs a running drag with the modifier at the last pointer position. Dependency cascades keep following the project calendar. The keyboard help lists the new shortcut; strings are added for all tracker locales. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
|
Added a per-drag override in the last commit: holding Shift during a single issue bar drag, resize or sidebar drop suspends working-day snapping for that gesture, so a bar can be placed on a weekend or holiday when it really has to. The preview and the committed dates then move in calendar days (children of a dragged parent shift by the same calendar-day delta), a small "Calendar days" hint appears under the date pill, and pressing or releasing Shift mid-drag updates the preview immediately. Dependency cascades still follow the project calendar. Why Shift rather than Alt: Alt is already read at release to bypass the cascade simulation, and several Linux window managers grab Alt+drag for moving windows. Cmd/Ctrl toggle the selection, while Shift only range-selects on a plain click, so it is free once a drag is running. The release re-reads Tests: reducer cases in |
|
@MichaelUray Please check failed test: |
The addWorkingDays cap test asserted a wall-clock bound (< 2000 ms) that failed on a slower CI runner. Assert the exact results instead: each loop step moves the cursor by one calendar day, so the distance of the result from the input is a deterministic count of the iterations. Cover the safety bound of calendars without working weekdays with small step counts and drop the costly 70-holiday walk, which cuts the suite's slowest tests from ~950 ms to ~250 ms. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
|
@ArtyomSavchenko Thanks. The test asserted a wall-clock bound ( |
Follow-up to #11054, which listed drag snapping as out of scope. Single issue bars
only: bulk co-drags and milestones are unchanged (see below).
Problem
With a working-days calendar on the project, moving or resizing a bar still works in
calendar days: the preview and the committed dates keep the bar's calendar-day length
and can land on a Saturday, a Sunday or a holiday. Dropping an unscheduled issue,
shifting a bar with the arrow keys and moving a parent together with its children
behave the same way. The cascade already places successors on working days (#10992
review, #11054), so a dragged predecessor was the one bar in a chain that could end
on a weekend.
Change
@hcengineering/gantt(engine, domain-neutral):prevWorkingDay,findWorkingDay(the nearest working day in a direction, orundefinedwhen none lies within the 60-day search window),workingDaySpan,dueForSpan,startForSpanandworkingDaysPerWeeknext to the existingworking-day helpers.
reduce()takes an optionalWorkingCalendar. With a calendar, a body drag landsthe start on the nearest working day in the drag direction and keeps the bar's length
in working days. The start handle rounds up to the next working day and the end
handle rounds down to the previous one, so a handle is never extended onto a
non-working day: with the pointer on a weekend the start waits on Monday and the end
stays on Friday. A dropped unscheduled issue starts on the next working day. A
zero-delta move leaves a bar untouched even if it sits on a non-working day, so a
click without movement still commits nothing. Without a calendar the reducer is
unchanged.
ranges and spans at
MAX_WORKING_SPAN_DAYS(about 100 years) and count non-finiteor inverted ranges as one working day.
addWorkingDaystreats a non-finite stepcount as zero instead of looping forever and caps a huge finite one (e.g. a
malformed stored lag) at the same bound. The reducer falls back to calendar days
for a calendar without any working weekday, for a bar whose origin is not a
finite range within the cap, and when no working day is within reach of the
pointer (a holiday blackout longer than the search window), so a non-working day
is never presented as a snapped result.
Tracker adapter:
GanttViewpasses the project calendar to the reducer for single issue bars.arrow keys move a bar by working days (Shift = one week = the number of active
weekdays; the help overlay now says "±1 week").
Per-drag override: hold Shift for calendar days
Sometimes one bar really has to sit on a weekend. Holding Shift during a single
issue bar drag, resize or sidebar drop suspends the snapping for that gesture: the
preview and the committed dates move in calendar days, exactly as in a project
without a working-days calendar, and the children of a dragged parent shift by the
same calendar-day delta. Pressing or releasing Shift mid-drag updates the preview
immediately (also without moving the pointer), so the decision can be made before
dropping. While snapping is suspended a small "Calendar days" hint is shown under the
date pill, and the keyboard help (
?) lists "Shift + drag". Dependency cascadestriggered by the drop still follow the project calendar; Alt keeps bypassing them.
Why Shift and not Alt: Alt is already read at release to bypass the cascade
simulation ("Alt + drag" in the help), and several Linux window managers grab
Alt+drag to move windows, so the gesture would not reach the browser there. Cmd/Ctrl
toggle the multi-selection on click and Ctrl+click opens the context menu on macOS.
Shift only range-selects on a plain click, so it is free once a drag runs. A body
drag therefore still starts without a modifier (Shift at pointer-down keeps meaning
range selection); Shift is pressed once the bar moves. Resize handles can be grabbed
with Shift already held.
Implementation:
mousemovecarries an optionalcalendarDaysflag; the reducer thencomputes the preview without the calendar and marks the state
snapSuspended. Theflag is set only when a calendar would otherwise apply, so legacy projects, co-drags
and milestones are unaffected and show no hint.
dragCalendar(state, calendar)returnsthe calendar a commit should use for the drag's own arithmetic (the parent-drag child
shift).
GanttViewkeeps the last pointer position and re-dispatches it on Shiftkeydown/keyup while a drag is active;
modifierSyncMovebuilds that replayed move.The release re-reads
shiftKeyfrom the pointer event and a windowblurcounts asreleasing Shift, so a Shift let go outside the browser window cannot leave the drop
committing calendar days. New strings
GanttDragCalendarDaysandGanttHelpCalendarDaysDragare added for all tracker locales.Not changed in this PR:
milliseconds, so snapping each member to working days could breach a predecessor
constraint the window was meant to protect; moving the window to working days is a
separate change.
is a product question.
Projects without
workingDaysConfigare not affected: every new branch is behindcalendar !== undefined, and the legacy paths are pinned by tests with fixed values.A bar stored entirely on non-working days becomes a one-working-day bar when it is moved.
Tests
working-days.test.ts: the new helpers, including a Friday holiday and thestartForSpan ∘ dueForSpanround trip, plus degenerate input (no working weekday,holidays covering the search window, non-finite and huge ranges, spans and step
counts such as
Number.MAX_SAFE_INTEGER).drag-controller.test.ts: body drag onto Saturday/Sunday/holiday in both directions,working-day span across a weekend, zero-delta on a non-working origin, both resize
handles with their clamps, unscheduled drop on a Saturday, co-drag unchanged with the
calendar passed, fixed legacy values for every branch without a calendar, and the
calendar-day fallback for degenerate calendars and bars and for a 150-day holiday
blackout (body drag, both resize handles, unscheduled drop; a sweep asserts no
snapped preview ever lands in the blackout).
scheduler-cascade.test.ts:shiftScheduleDays,shiftWithPrimaryandkeyboardWeekStepwith fixed values in both modes.drag-controller.test.ts,scheduler-cascade.test.ts): body dragonto Saturday stays on Saturday with the calendar length, both resize handles and the
unscheduled drop follow the pointer onto the weekend, pressing and releasing Shift
mid-drag switches the preview at the same pointer position (body and resize), no
effect and no flag without a calendar or for a co-drag,
dragCalendar, and a parentdrag whose child moves by working days without Shift and by calendar days with it.
modifierSyncMove: a blur or a release without Shift un-suspends the drag at the lastpointer position so the commit snaps again, a release still holding Shift keeps the
calendar-day preview, the unscheduled drop keeps its canvas position, and nothing is
replayed without an active drag or a prior move.
Open question
The body drag snaps to the nearest working day in the drag direction (Monday − 1 day
→ Friday) rather than always rounding up; happy to change that if you prefer.