Skip to content

Manually re-order trainruns in nodes #636

@louisgreiner

Description

@louisgreiner

Description

Type Size (weeks)
Feature Scope 1: 6
Scope 2: 3
Scope 3: 1

Meta issue: #551

This feature should allow user to manually re-order the trainruns inside a node, by manually allocating the ports of the trainrun sections. This will be allowed on Node Edition, user will be able to drag a trainrun section's port to another port slot. Then, the algorithm that already automatically assign ports should take these manual settings into account.

Such train's configuration at a node should be doable (see more examples from SNCF Réseau here)
Image

(Currently hacking with labeled trainruns:)Image

Mock-ups

First draft:
Image

Homemade mock-up for an "edited node's view":

  • empty / assigned ports (grey spot)
  • handles (red spots)
  • size of the node adapted to new number of ports

Use this as "mock-up":
Image

A full empty node should be able to host these empty ports (= minimal size):
Image

Acceptance Criteria

Scope 1 (Port swapping):

  • On Node Edition mode (when a node is currently being edited (= double-clicked (Node details are opened on the right side)):
    • All the ports of the node are displayed:
      • Assigned ones
      • Empty ones (not assigned, all the possible ports that the size of the node allows)
        • Especially for edges with no trainruns on them, display the empty ports
      • Each Port, assigned or not, takes the same space and defines a "row" (or "column") (already current behavior)
      • The minimal size of an edge is all the ports (assigned or empty) contained within the first and last assigned ports of the edge (included)
    • Ports look different (three visual states):
      • empty: the port is not already assigned
      • assigned: the port is already assigned
      • receiving (highlighted): the port is not already assigned, but the user is currently dragging a train on this Port -> "preview state"
    • User can grab an assigned port to drag it to another port location:
      • Snapping:
        • If inside of the node's edges, then snap to the closest port, including the dragged port (euclidean distance for snapping range can be decided during the implementation)
          • Note: If dragging and stopping above the origin, this should be dealt like the other cases: if the cursor is in a snap range ('say the origin), the drag should end up in associated port
        • If outside of the node's edges, then cancel the operation to prevent premature cancels (euclidean distance for snapping range can be decided during the implementation)
      • The "receiving" port becomes highlighted when user drags a port onto it without releasing the click
      • Dragging a port (= Port1) of a train (= Train1) to another port (= Port2) location makes its location locked to this one
        • If Port2 was empty, Port1 takes the location of Port2
        • If Port2 was already assigned by a train (= Train2)
          • and Port2 was not locked for Train2, Port1 takes the location of Port2 and Port2 is re-calculated for Train2 (only Train1 has a locked Port)
          • and Port2 was locked for Train2, Port1 takes the location of Port2 and Port2 takes the location of Port1 (both Train1 and Train2 have locked Ports, but swapped)
        • Note: if Port1 is dragged while snapped at the same position, Port1 stays at the same location and becomes locked
    • A locked Port and a not locked Port look the same (no need for differentiation)
  • Setting a port a specific location should emit an Operation
  • Automated port allocation algorithms still work on the rest of the "not locked" Ports
  • To reset all the specific Port allocations made by the user, a button is available in the Node Edition menu. By clicking on it, the user specifications are deleted and the Ports are recalculated by the automatic algorithm. (TODO: need mock-up ; edit: sorry no mock-up yet)

Scope 2: (Node resizing):

  • On Node Edition mode, user can grab an edge of the node to resize it
    • On hover of an edge, the mouse cursor becomes a bi-directional arrow and the edge is highlighted
    • When user drags an edge of a node and drags it on the external direction, the node grows on that direction, adding empty ports for the grown sides (= new behavior than Scope 1)
      • This action snaps on the closest previous / next port "row" (or "column")
      • If user tries to resize on the internal direction of the node, further that the minimal size of the node, the resize is stuck to the minimal size
      • When user stops dragging and releases its click, the node keeps the given size, and the new empty ports are kept (= new behavior than Scope 1)
    • When user leaves the Node Edition mode, the Node goes back to its minimal size (= removing the empty Ports before the first assigned Port and after the last assigned port)

Scope 3 (Moving ports using arrow keys)

  • Nice-to-have: arrow up/down or left/right feature on a port selection
    • In Node Edition mode, user can select a port
    • When a port is selected, user can move this port to neighbor port positions
      • If the port is on the left or right side, pressing Arrow Up makes it go on the above position, pressing Arrow Down makes it go on the below position
      • If Port is on the top or bottom side, pressing Arrow Left makes it go on the left position, pressing Arrow Right makes it go on the right position
      • After being moved, the port becomes locked
        • Note: and not the other neighbor ports that are swapped by this one
      • Selection is kept after pressing any key; selection is discarded after pressing Escape key

Implementation Plan

Enhancement (can be done at any time, but rather in a separated PR):

  • Refacto enum PositionAlignment and use a string value instead of int enum value
    • We want something like TOP = "top", not totally remove the enum

Scope 1 (size 6):

  • Update the node size calculation method to take the port index value instead of the ports number
    • Note (not sure of what the current implementation is): If port.getPositionIndex() is already used, this might be already done actually. On the other hand, this.port.length is not the good solution imo to get the size (this would end up dealing with 5 ports, even if we have 5 ports and others in between (which are not "existing"). We don't want to have ports with port.getTrainrunSectionId() === undefined, we only want proper ports.
  • Node Edition:
    • Display all ports
      • assigned
      • empty
        • Especially for empty edges (see above "minimal size" node picture)
      • receiving (only a visual state)
    • Implement drag operation from a port to another port: update the dragged port locked value
      • Update the Port1, locked to true, positionIndex and positionAlignment attributes
      • Update the Port2 locked to false (only on swap with an assigned port)
    • Add the possibility to reset the locked attributes of all ports of a node
  • Create new event emitter class PortOperation (here)
    • Add operation.emit() when ports get locked (or resetting locked)

Scope 2 (size 3):

  • Allow a node to have a size that is not bounded by first and last assigned ports, but also by empty ports
  • Node's edge drag (highlighted edge)
  • Resize the node
    • Snapping to the previous/next port "row" (or "column")
  • On Node Edition closed, resize the node the be bounded back by first and last assigned ports

Scope 3 (size ?):

  • Allow to select a port in Node Edition mode
  • Implement port move to previous/next right/top/left/bottom index
  • Discard port selection when Node Edition mode is closed or on pressing "Escape" key

Tests

Definition of Ready

  • PO/UX-UI

    • Mock-ups are complete and validated
    • ACs are clear and have been reviewed by another refiner
  • Technical

    • Implementation plan has been written and validated by another maintainer
  • General

    • Validated by Adrian

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions