feat(nav): back/forward navigation with tree auto-reveal#47
Open
DanR978 wants to merge 2 commits into
Open
Conversation
Adds a bounded selection-history stack to the explorer store and two arrow buttons to the toolbar, so navigating away from an object -- by clicking a relationship-graph node, picking a search result, or clicking a tree row -- can be undone. selectItem() pushes onto the stack, de-duping a repeated selection of the current node and truncating the forward entries on a new navigation, as a browser does. goBack/goForward restore a selection by setting state directly rather than calling selectItem, which would push the entry back on and trap the cursor at the end of the stack. A node is only visible once the folders and ancestors above it are expanded, so goBack/goForward re-expand that path -- the same expansion SearchModal and RelationshipGraph perform before they select. Without it, going back to a node inside a branch the user has since collapsed would update the detail pane and leave the tree inert. History is session-scoped (the explorer store is not persisted) and cleared by reset() on disconnect, so it never leaks across servers. Also handles the X1/X2 side buttons found on most mice. Electron only: in the web build those buttons drive the browser's own history, and taking them over would strand the SPA.
Back/forward (and search, and relationship-graph clicks) expand the path to the selected node, but nothing brought it on screen -- a node could land highlighted below the fold. Center it when no copy of it is already visible. Reveal is owned entirely by TreeView. An `obj:` id names an object, not a tree: objects are rendered both under Namespaces -> ObjectType as plain DOM and in the windowed Objects list, so the id prefix cannot say which row to scroll to. So ask the DOM first -- whichever copies are mounted carry the selected style -- and fall back to the virtualized list only when no DOM row exists, via an imperative handle. That fallback works because uniform row heights give the virtualizer a measurement for every index, mounted or not. The same object can be mounted more than once (the flat list shows it at top level and again under its composition parent). If any copy is on screen, do nothing; otherwise center the first. Both paths scroll vertically only -- scrollToIndex writes scrollTop and never scrollLeft, and the DOM path adjusts scrollTop directly rather than using scrollIntoView, which would drag a horizontally-scrolled tree sideways. Reveal is keyed on the selection changing, guarded by a single ref, so expanding an unrelated node or the 30s background poll (which replaces allObjects) never scrolls the tree away from where the user left it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
Adds Back and Forward buttons to the toolbar. They step through the objects you
have selected, the same way a browser steps through pages you have visited.
The sidebar tree now follows the selection. When something is selected, the tree
opens the folders above it and scrolls it into view.
Objects get selected from the relationship graph, from search results, and from
the tree itself. All of those go into the history now, and all of them scroll
the tree.
History (explorer.ts, Toolbar.tsx)
selectItem()pushes the selection onto a list, capped at 50 entries. Selectingthe same node twice does not add a second entry, and going back and then
selecting something new drops the forward entries, like a browser does.
goBack()andgoForward()set the state directly instead of callingselectItem(). If they called it, going back would push that entry on again andyou could never leave the end of the list.
A node is only visible if the folders above it are open, so back and forward
re-open that path. Search and the relationship graph already do the same thing
before they select something.
History is kept in memory only, and
reset()clears it on disconnect, so itcannot carry over to another server.
The two side buttons on a mouse (X1 and X2) work too. That part is Electron only,
since in the web build those buttons belong to the browser.
Scrolling (TreeView.tsx, VirtualObjectRows.tsx)
TreeViewowns this. Anobj:id names an object, not a spot in the tree, andthe same object can be rendered both under Namespaces > ObjectType and in the
flat Objects list. So the id alone does not say which row to scroll to.
It looks for rows in the DOM with the
selectedclass. If one of them is alreadyon screen it does nothing. Otherwise it centers the first one. If none exist in
the DOM, it asks the virtualized Objects list to scroll by index instead. That
works because all rows are the same height, so the virtualizer knows where a row
is even when it has not been rendered.
Both paths only scroll up and down, never sideways.
Scrolling only runs when the selection changes, so expanding another node, or the
30 second background refresh, will not move the tree on you.
Testing
npm run typecheckpasses. (npm run lintfails onmaintoo, since ESLint isinstalled without a config file.)
I ran the store directly to check the history rules: order, back and forward
stopping at the ends, forward entries dropped after a new selection, no
duplicates, the 50 entry cap,
reset()clearing it, the right folders re-openingfor each id type, and an object that lists itself as its own parent not looping
forever.
For the scrolling I read the
@tanstack/virtual-coresource to confirm thatscrollToIndexonly changes the vertical scroll, and that every row has aposition even when it has not been rendered.
Known limitation (already on main)
Rows in the flat Objects list use
obj:${elementId}as their id no matter wherethey sit, and an object shows up both at the top level and under its composition
parent. Selecting one highlights all of the copies. This PR does not fix that,
but it handles it: if any copy is on screen, nothing scrolls. Giving node ids a
path would be a good follow up.
Risk
No new dependencies and no API changes. Scrolling does nothing when the node is
already visible, and the history does nothing until you press a button.