Skip to content

feat: browse files from the viewer sidebar, and remove datasets from Views - #465

Open
allison-truhlar wants to merge 26 commits into
ngviews-14-viewer-sidebarfrom
ngviews-15-sidebar-browser
Open

allison-truhlar wants to merge 26 commits into
ngviews-14-viewer-sidebarfrom
ngviews-15-sidebar-browser

Conversation

@allison-truhlar

@allison-truhlar allison-truhlar commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

The owner of a View can browse for data without leaving the embedded viewer, and can remove datasets from a View on the Views page. This builds on #464, which adds the "Add data" sidebar. The branch also tidies the Views table: it counts datasets instead of layers, drops the Sharing column, and sizes its columns to the screen.

What changed

Viewer sidebar

  • File browser in the sidebar: the viewer sidebar renders the file selector dialog's file browsing widget. The file list gains an optional checkbox column for picking several datasets to add to the Layer Cart.
  • Tabs and resizing: the sidebar splits into "Add data" and "Layer Cart" tabs. The sidebar now sits in a resizable panel, like the Browse page's side panels.
  • Cart count: the "Layer Cart" tab shows the cart's dataset count as a badge.

Removing datasets from a View

Before this change, the only way to remove a dataset from a View was to delete its layer inside Neuroglancer. That doesn't work for a dataset with no Neuroglancer layer, such as a plain directory.

  • Endpoint: POST /api/neuroglancer/views/{short_key}/remove-sources takes {"sources": [{"fsp_name", "path"}]}. For each dataset, remove_view_sources drops every Neuroglancer layer whose source uses one of the dataset's Data Link segments, and every view_layers row for it, including unsupported and broken rows. Everything happens in one database commit. A row that never recorded its key falls back to its layer_index. Only the owner can call it, and it returns a 404 when the View has none of the listed datasets.
  • Warning icon is a button: in the Sources column, the warning icon on an unsupported dataset now opens a confirmation dialog to remove that dataset.
  • "Remove datasets" row action: a new item in the row's actions menu opens a checklist of the View's datasets. A dataset that Neuroglancer split into one layer per channel appears once. "Remove" stays disabled until at least one dataset is checked.

Views table fixes

  • Datasets, not layers: when Neuroglancer loads a multichannel layer, it splits the layer into one layer per channel, and the next Save stores those split layers. Counting saved layer rows made the number jump from one per dataset to one per channel without adding any data. The column now counts distinct datasets, using the Sources de-dupe, and is renamed to "Datasets".
  • No Sharing column: every View is readable through its read link, and sharing_mode isn't yet implemented (only a View's owner can edit it). A per-row sharing label suggests a choice that doesn't exist yet. The column comes back once sharing modes are enforced.
  • Responsive column widths: columns use minmax tracks. They start at their full width and shrink toward a minimum on narrower screens.

Testing

The backend suite (1129) and frontend suite (569) pass. New tests cover remove_view_sources, the remove-sources endpoint, the warning-icon button, the "Remove datasets" menu item, and RemoveDatasetsDialog.

Manually smoke-tested in local development.

@krokicki

@allison-truhlar
allison-truhlar marked this pull request as ready for review October 9, 2026 16:42
@allison-truhlar
allison-truhlar added this pull request to stack #429 October 9, 2026 16:42
allison-truhlar added a commit to JaneliaSciComp/fileglancer-hub that referenced this pull request Oct 9, 2026
Dev runs the ngviews prerelease so the editable Views work in
JaneliaSciComp/fileglancer#465 can be tested against real data and Okta
before release.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
allison-truhlar and others added 24 commits October 13, 2026 10:23
…a sidebar

Extract the file selector dialog's browser (breadcrumbs, toolbar, file list, path field) into FileSelectorBrowser so the viewer sidebar renders the same widget. The table gains an optional checkbox column for picking several datasets to add to the Layer Cart, and a failed Load more keeps its loaded rows.
The sidebar splits into Add data and Layer Cart tabs, both kept mounted so switching keeps the browser's folder and checks, and sits in a resizable panel beside the viewer like the Browse page's side panels.
Neuroglancer splits a multichannel auto layer into per-channel layers
on load, and the next Save stores the split state. Counting saved layer
rows made the number jump from one per dataset to one per channel
without any data being added. Count distinct datasets instead, using
the same de-dupe as the Sources column, and rename the column to
Datasets.
The embedded viewer's sidebar hid how many datasets were in the cart
until you opened the Layer Cart tab. Show the count as a badge on the
tab, using the same dataset count as the toolbar's cart badge. The badge
stays rendered, hidden, when the cart is empty so the trigger's width
doesn't change under the tab indicator.
POST /api/neuroglancer/views/{short_key}/remove-sources drops each listed
dataset's Neuroglancer layers and view_layers rows, unsupported and broken
ones included, in one commit.
The unsupported-source warning icon is now a button that opens a remove
confirmation, and a new "Remove datasets" row action opens a checklist of
the View's datasets. The Sources column's de-dupe and path formatting move
into getViewSources and useSourceDisplayPath so the dialog shares them.
Every View is readable through its read link and sharing_mode is not
enforced, so a per-row sharing label suggests a choice that does not exist
yet. The Sources column starts 420px wide to take over the freed space.
Fixed pixel tracks made the table scroll sideways on narrower screens even
before anyone resized a column. The tracks now use minmax so they start at
their full width and shrink toward a floor, and Sources switches to a fixed
width only after the user drags it. TableCard rows use min-w-min instead of
min-w-max so minmax tracks can shrink at all; the Views table is the only
gridColsStyle caller.
Between 768px and 900px, the pagination controls and the search input
shared one cramped row in every TableCard. Stacking them up to 900px gives
the search the full card width.
A long file name pushed the name column wider than its container, so
the table overflowed narrow layouts like the Add data sidebar. A fixed
table layout lets the name column take the width the other columns
leave and truncate the rest.
The name link rendered inline, so truncate had no width to clip
against and a long name overflowed its cell. Rendering it as a block
lets it clip with an ellipsis.
The View page reads the Neuroglancer iframe's window.viewer, which browsers
allow only when the iframe shares the page's origin. The docs mentioned that
rule but only described the local-development passthrough, so a deployment
had no guidance on how to meet it. Setting up fileglancer-dev showed two gaps:
nginx serves /neuroglancer/ before the app sees the request, so
FGC_NEUROGLANCER_URL silently does nothing there, and Vite bundles
frontend/viewers.config.yaml whenever it exists on disk, so a developer's
local override once shipped in a dev build.

Document the production, dev-host, and local setups side by side, with the
nginx proxy block that dev uses and the check that confirms it works.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
…Go to the Add data browser

The file selector table showed every row at the zones and zone levels
as "Folder". Those rows now read "Zone" and "File share".

The Views Add data sidebar gets the editable path field back, placed
between the toolbar and the file table with a Go button. The file
selector dialog keeps its path field at the bottom.
Pull the segment-boundary regex and the source-URL walk out of
rewriteDataLinkSegment into segmentPattern and mapSourceUrls, and add
layerUsesSegment on top of them. The viewer's Sources tab needs to map
layers to datasets the same way the server's ngstate.layer_uses_segment
does, without rewriting anything.

No behavior change to rewriteDataLinkSegment.
DatasetRow renders the header, layer-status icon and axis table for one
dataset, with an onRemove callback and an optional renderChannels slot.
CartDatasetRow becomes a wrapper that keeps the cart removal and the
channel checkboxes.

DatasetRow takes a tense ("Will load" / "Loads as") so the same row can
describe datasets already in a View.
Unsupported sources have no Neuroglancer layer, so reconciling
view_layers from ng_state can never remove them. Add
removed_unsupported_sharing_keys to ViewUpdateRequest; update_view drops
those unsupported rows after reconciling and reindexes the rest.

This lets the viewer remove an unsupported source locally and record it
on Save, instead of calling remove-sources, which commits immediately
and would clash with unsaved edits.
…rces

Replace the viewer sidebar's cart round trip with a direct add, and the
Layer Cart tab with a Sources tab that shows what the viewer holds.

Add data: only the checked rows are probed (Zarr/OME detection and the
dimension check), so the expensive checks still never run on every row.
"Add N to this View" reuses the mismatch and Data Link consent dialog,
and summarizes datasets that can't load as a layer or whose dimensions
differ. The viewer no longer touches the cart.

Sources: useViewSources groups Neuroglancer's layers into datasets by
Data Link segment, using the saved rows plus datasets added this session,
and follows state.changed (throttled; camera-only changes are ignored),
so layers removed in Neuroglancer drop out of the list. Removing a
source deletes its layers from Neuroglancer; removing an unsupported one
is held in the edit state and sent as removed_unsupported_sharing_keys
on Save. Both stay local until Save, and Discard undoes them.

After Save the views cache is updated in place, so the Sources tab maps
layers through the new rows right away.
The broken-link icon only offered a relink, while the unsupported icon
offered removal, which applies to a broken link too. Every broken source
now opens a "Broken source" dialog with both actions. A source whose
Data Link was never recorded, which used to show an inert icon, gets the
same dialog with removal only. Removal goes through the same mutation as
removing an unsupported source.
Cover adding a dataset with differing dimensions and no Data Link while
automatic Data Links are off: the dialog has to ask for both.
Vite bundled frontend/viewers.config.yaml whenever it existed on disk,
even though git ignored it, so a developer's local override could ship
in a PyPI build. The runtime viewers_config setting (FGC_VIEWERS_CONFIG)
already covers local customization without a rebuild, so it is now the
only override. The docs, CLAUDE.md, and config.yaml.template describe
it, and .gitignore ignores a root-level viewers.config.yaml for local
use.
@allison-truhlar
allison-truhlar force-pushed the ngviews-15-sidebar-browser branch from 23dee2f to 0aaa581 Compare October 13, 2026 14:26
The "Unsaved changes" banner under the toolbar appeared on every pan or
zoom, so in practice it was always there and cost the visualization
vertical space. Replace it with a Save button that always sits in the
toolbar next to the View name and is disabled until there are unsaved
changes, with Discard appearing beside it only while dirty. The
explanation that saving updates the View for everyone moves to the Save
tooltip.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant