Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
65da214
#vfb-240 - feat: enhance neuroglass layer URL building with error han…
jrmartin Apr 24, 2026
954d364
#VFB-240 - feat: update buildSingleInstanceLayer to use async URL fet…
jrmartin Apr 24, 2026
6c68ad7
fix: correct neuroglass layer URL formatting in buildNeuroglassLayerU…
jrmartin Apr 24, 2026
27b88e0
fix: update neuroglass layer URL formatting in buildNeuroglassLayerUr…
jrmartin Apr 24, 2026
655bafc
#VFB-240 - use protocol
jrmartin Apr 26, 2026
4a515fb
Merge remote-tracking branch 'origin/development' into feature/VFB-240
jrmartin Apr 27, 2026
db2b3e0
#VFB-240 - Make building state function async
jrmartin Apr 27, 2026
f23c6ae
#vfb-240 - remove unused useMemo ref
jrmartin Apr 27, 2026
0d00511
#VFB-240 - Cleanup logs and fix paths rebuilds
jrmartin Apr 27, 2026
84c0db7
#VFB-242 - Create navigation bottoms constants, and update reference …
jrmartin Apr 28, 2026
3fd0838
#VFB-242 - Fix bug with loading queries from toolbar
jrmartin Apr 29, 2026
797438e
Merge remote-tracking branch 'origin/feature/VFB-242' into feature/VF…
jrmartin Apr 29, 2026
ee2f13e
#VFB-242 - feat: enhance query selection handling in QueriesSelection…
jrmartin Apr 29, 2026
bdeec07
Merge remote-tracking branch 'origin/feature/VFB-242' into feature/VF…
jrmartin Apr 29, 2026
b2bba74
#VFB-242 - feat: streamline query updates and improve bottom navigati…
jrmartin Apr 29, 2026
ba90a66
Merge remote-tracking branch 'origin/feature/VFB-242' into feature/VF…
jrmartin Apr 29, 2026
689183b
#VFB-242 - Lint fix , remove unusued var
jrmartin Apr 29, 2026
c651534
Merge remote-tracking branch 'origin/feature/VFB-242' into feature/VF…
jrmartin Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions applications/virtual-fly-brain/frontend/src/components/Layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { removeAllInstances } from './../reducers/actions/instances';
import { Box, Button,Modal, useMediaQuery, useTheme, Typography, CircularProgress, Link } from "@mui/material";
import { activateCircuits, activateImages } from "../reducers/actions/layout";
import { widgetsIDs } from "./layout/widgets";
import { bottomNavClearAll, bottomNavDownload, bottomNavLayers, bottomNavQuery, bottomNavSearch, bottomNavSnapshot, bottomNavUpload } from "../utils/constants";

const {
secondaryBg,
Expand Down Expand Up @@ -80,14 +81,14 @@ const MainLayout = ({ bottomNav, setBottomNav }) => {
const queryComponentOpened = useSelector( state => state.globalInfo?.queryComponentOpened );

useEffect( () => {
if ( queryComponentOpened && bottomNav !== 2 ){
setBottomNav(2)
if ( queryComponentOpened && bottomNav !== bottomNavQuery ){
setBottomNav(bottomNavQuery)
}
}, [bottomNav, queryComponentOpened, setBottomNav]);

// Handle Clear All functionality
useEffect(() => {
if (bottomNav === 4) {
if (bottomNav === bottomNavClearAll) {
if (allLoadedInstances?.length > 1) {
removeAllInstances();
}
Expand All @@ -97,7 +98,7 @@ const MainLayout = ({ bottomNav, setBottomNav }) => {
}, [bottomNav, allLoadedInstances?.length, setBottomNav]);

useEffect( () => {
if ( bottomNav === 3 ){
if ( bottomNav === bottomNavLayers ){
const layoutManager = getLayoutManagerInstance();
if (!layoutManager.model.getNodeById("listViewerWidget").isVisible()) {
const newWidget = { ...widgets[widgetsIDs.listViewerWidgetID] }
Expand Down Expand Up @@ -252,22 +253,22 @@ const MainLayout = ({ bottomNav, setBottomNav }) => {
{desktopScreen ? (
<>
{tabContent}
{bottomNav === 0 && < VFBSnapshot open={true} setBottomNav={setBottomNav} />}
{bottomNav === 1 && < VFBUploader open={true} setBottomNav={setBottomNav} />}
{bottomNav === 2 && <VFBDownloadContents open={true} setBottomNav={setBottomNav} />}
{bottomNav === 3 && <QueryBuilder setBottomNav={setBottomNav} fullWidth={sidebarOpen} tabSelected={0}/>}
{bottomNav === 6 && <QueryBuilder setBottomNav={setBottomNav} fullWidth={sidebarOpen} tabSelected={1}/>}
{bottomNav === bottomNavSnapshot && < VFBSnapshot open={true} setBottomNav={setBottomNav} />}
{bottomNav === bottomNavUpload && < VFBUploader open={true} setBottomNav={setBottomNav} />}
{bottomNav === bottomNavDownload && <VFBDownloadContents open={true} setBottomNav={setBottomNav} />}
{bottomNav === bottomNavQuery && <QueryBuilder setBottomNav={setBottomNav} fullWidth={sidebarOpen} tabSelected={0}/>}
{bottomNav === bottomNavSearch && <QueryBuilder setBottomNav={setBottomNav} fullWidth={sidebarOpen} tabSelected={1}/>}
</>
) : (
<>
{
bottomNav != 3 && tabContent
bottomNav != bottomNavQuery && tabContent
}
{bottomNav === 0 && <VFBSnapshot open={true} setBottomNav={setBottomNav} />}
{bottomNav === 1 && <VFBUploader open={true} setBottomNav={setBottomNav} />}
{bottomNav === 2 && <VFBDownloadContents open={true} setBottomNav={setBottomNav} />}
{bottomNav === 3 && <QueryBuilder setBottomNav={setBottomNav} fullWidth={sidebarOpen} tabSelected={0}/>}
{bottomNav === 6 && <QueryBuilder setBottomNav={setBottomNav} fullWidth={sidebarOpen} tabSelected={1}/>}
{bottomNav === bottomNavSnapshot && <VFBSnapshot open={true} setBottomNav={setBottomNav} />}
{bottomNav === bottomNavUpload && <VFBUploader open={true} setBottomNav={setBottomNav} />}
{bottomNav === bottomNavDownload && <VFBDownloadContents open={true} setBottomNav={setBottomNav} />}
{bottomNav === bottomNavQuery && <QueryBuilder setBottomNav={setBottomNav} fullWidth={sidebarOpen} tabSelected={0}/>}
{bottomNav === bottomNavSearch && <QueryBuilder setBottomNav={setBottomNav} fullWidth={sidebarOpen} tabSelected={1}/>}
</>
)}
</Box>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo } from 'react';
import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import { Box, Typography, useMediaQuery } from '@mui/material';
import { useTheme } from '@mui/material/styles';
Expand All @@ -8,6 +8,7 @@ const NEUROGLASS_URL = import.meta.env.NEUROGLASS_URL ?? '';

export default function NeuroglassViewer() {
const [debouncedSrc, setDebouncedSrc] = useState('');
const [iframeSrc, setIframeSrc] = useState('');

const allLoadedInstances = useSelector(state => state.instances?.allLoadedInstances);
const focusedInstance = useSelector(state => state.instances?.focusedInstance);
Expand All @@ -16,16 +17,43 @@ export default function NeuroglassViewer() {
const theme = useTheme();
const isMobile = !useMediaQuery(theme.breakpoints.up('lg'));

// Rebuilds whenever instances, focused item, layout preference, or viewport size changes.
const iframeSrc = useMemo(() => {
const layout = resolveNeuroglassLayout(neuroglassView, isMobile);
const state = buildNeuroglassState(
allLoadedInstances,
focusedInstance?.metadata?.Id,
layout,
);
if (!state || !NEUROGLASS_URL) return '';
return `${NEUROGLASS_URL}/embed#!${encodeURIComponent(JSON.stringify(state))}`;
useEffect(() => {
let cancelled = false;
const abortController = new AbortController();
async function buildSrc() {
const layout = resolveNeuroglassLayout(neuroglassView, isMobile);
try{
const state = await buildNeuroglassState(
allLoadedInstances,
focusedInstance?.metadata?.Id,
layout,
abortController.signal
);

if (cancelled || abortController.signal.aborted) return;

if (!state || !NEUROGLASS_URL) {
setIframeSrc('');
return;
}

setIframeSrc(
`${NEUROGLASS_URL}/embed#!${encodeURIComponent(JSON.stringify(state))}`
);
} catch (error) {
if (error?.name === 'AbortError' || abortController.signal.aborted) {
return;
}
throw error;
}
}

buildSrc();

return () => {
cancelled = true;
abortController.abort();
};
}, [allLoadedInstances, focusedInstance?.metadata?.Id, neuroglassView, isMobile]);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import vars from "../../../theme/variables";
import { widgets } from "../../layout/widgets";
import { bottomNavDownload, bottomNavQuery, bottomNavSearch, bottomNavUpload } from '../../../utils/constants';
const { primaryFont, whiteColor, tabActiveColor, primaryBg } = vars;

const ACTIONS = {
Expand Down Expand Up @@ -199,15 +200,15 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "fa fa-search",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
parameters: [5]
parameters: [bottomNavSearch]
}
},
{
label: "Query",
icon: "fa fa-clipboard-question",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
parameters: [2]
parameters: [bottomNavQuery]
}
},
{
Expand Down Expand Up @@ -279,15 +280,15 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "fa fa-download",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
parameters: [1]
parameters: [bottomNavDownload]
}
},
{
label: "NBLAST Uploader",
icon: "fa fa-upload",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
parameters: [0]
parameters: [bottomNavUpload]
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ const getQueries = (newQueries, searchTerm) => {
if (!newQueries || newQueries.length === 0) return [];

let updatedQueries = [];
const term = searchTerm ? searchTerm.toLowerCase() : undefined;
const term = searchTerm ? searchTerm.toLowerCase() : "";

newQueries.forEach((query) => {
if (query.queries) {
Object.keys(query.queries).forEach((key) => {
if (query.queries[key]?.active) {
let rows = query.queries[key]?.rows || [];
let rows = query.queries[key]?.rows || query.queries[key]?.preview_results?.rows || [];

if (term) {
if (term != undefined) {
rows = rows.filter(row => {
// Cache the string conversion for performance
const values = Object.values(row);
Expand Down Expand Up @@ -95,20 +95,41 @@ const Query = forwardRef(({ fullWidth, queries, searchTerm }, ref) => {
const [sortDirection, setSortDirection] = useState(1); // 1 for ascending, -1 for descending

// Memoize filtered searches
const filteredSearches = useMemo(() => getQueries(queries, searchTerm), [queries, searchTerm]);
const filteredSearches = useMemo(() => {
return getQueries(queries, searchTerm);
}, [queries, searchTerm]);

const getRows = (item) => {
if (item?.rows?.length > 0) {
return item.rows;
}

if (item?.preview_results?.rows?.length > 0) {
return item.preview_results.rows;
}

return null;
};

// Memoize the final filtered results based on both search term and active chip tags
const finalFilteredResults = useMemo(() => {
if (!filteredSearches || filteredSearches.length === 0) return [];

const activeChipLabels = chipTags.filter(f => f.active).map(tag => tag.label);
const rows = filteredSearches.flatMap(item => {
const nestedRows = getRows(item);
return nestedRows || [item];
});

// Filter by active chip tags
const activeChipLabels = chipTags
.filter(f => f.active)
.map(tag => tag.label);

let filtered;

if (activeChipLabels.length === 0) {
filtered = filteredSearches;
filtered = rows;
} else {
filtered = filteredSearches.filter(row => {
filtered = rows.filter(row => {
const tags = getTags(row.tags);
return activeChipLabels.some(label => tags.includes(label));
});
Expand Down Expand Up @@ -146,13 +167,22 @@ const Query = forwardRef(({ fullWidth, queries, searchTerm }, ref) => {
if (!queries || queries.length === 0) return [];

const tagMap = new Map();

queries.forEach(query => {
if (query.queries) {
Object.keys(query.queries).forEach(q => {
if (query.queries[q]?.active) {
query.queries[q]?.rows?.forEach(row => {
const currentQuery = query.queries[q];

if (currentQuery?.active) {
const rows =
currentQuery?.rows?.length > 0
? currentQuery.rows
: currentQuery?.preview_results?.rows || [];

rows.forEach(row => {
if (row.tags) {
const rowTags = getTags(row.tags);

rowTags.forEach(rowTag => {
if (!tagMap.has(rowTag)) {
tagMap.set(rowTag, { label: rowTag, active: true });
Expand Down Expand Up @@ -343,7 +373,7 @@ const Query = forwardRef(({ fullWidth, queries, searchTerm }, ref) => {
onClick={() => null}
disabled={!tag.active}
onDelete={() => handleChipDelete(tag.label)}
key={tag}
key={tag?.label}
deleteIcon={
<Cross
size={12}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Box, Button } from "@mui/material";
import React from "react";
import { ClearAll, Download, Query, Upload, Layers } from "../../icons";
import vars from "../../theme/variables";
import { bottomNavClearAll, bottomNavDownload, bottomNavQuery, bottomNavSnapshot } from "../../utils/constants";

const {
whiteColor,
Expand All @@ -12,22 +13,22 @@ const {

const navArr = [
{
id: 0,
id: bottomNavSnapshot,
icon: Upload,
name: 'Upload'
},
{
id: 1,
id: bottomNavDownload,
icon: Download,
name: 'Download'
},
{
id: 2,
id: bottomNavQuery,
icon: Query,
name: 'Query'
},
{
id: 3,
id: bottomNavClearAll,
icon: ClearAll,
name: 'Clear all'
},
Expand Down Expand Up @@ -72,7 +73,7 @@ const BottomNav = ({ setBottomNav, bottomNav }) => {
flexWrap='wrap'
sx={classes.root}
>
{navArr.map((item, index) => (
{navArr.map((item) => (
<Button
sx={{
height: '100%',
Expand All @@ -83,7 +84,7 @@ const BottomNav = ({ setBottomNav, bottomNav }) => {
flexDirection: 'column',
}}

onClick={() => setBottomNav(index)}
onClick={() => setBottomNav(item?.id)}
key={item.id}
>
<item.icon color={item?.id === bottomNav ? tabActiveColor : 'white'} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import layout3 from "../../components/layout/layout3";
import { WidgetStatus } from "@metacell/geppetto-meta-client/common/layout/model";
import { getLayoutManagerInstance } from "@metacell/geppetto-meta-client/common/layout/LayoutManager";
import { bottomNavQuery } from "../../utils/constants";

const { primaryBg, headerBoxShadow } = vars;

Expand Down Expand Up @@ -68,7 +69,7 @@
}

// Open the query component panel
setBottomNav(2);
setBottomNav(bottomNavQuery);
}
}

Expand Down Expand Up @@ -140,13 +141,13 @@
Object.keys(query.queries)?.forEach(q => query.queries[q].active = false);
}
});
if (matchQuery?.queries?.[action?.parameters[1]]) {
matchQuery.queries[action.parameters[1]].active = true;
if (matchQuery?.short_form == action?.parameters[0]) {
Object.keys(matchQuery.queries)?.forEach(q => matchQuery.queries[q].active = true);
updateQueries(updatedQueries);
setBottomNav(2)
setBottomNav(bottomNavQuery)
} else {
getQueries(action.parameters[0], action.parameters[1])
setBottomNav(2)
setBottomNav(bottomNavQuery)
}
break;
}
Expand Down Expand Up @@ -208,7 +209,7 @@
}

useEffect(() => {
menuConfig = toolbarMenu(autoSaveLayout);

Check warning on line 212 in applications/virtual-fly-brain/frontend/src/shared/header/index.jsx

View workflow job for this annotation

GitHub Actions / eslint

Assignments to the 'menuConfig' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect
}, [autoSaveLayout]);

return (
Expand Down Expand Up @@ -310,7 +311,7 @@
<MediaQuery minWidth={1200}>
{focusedInstance?.metadata?.Id && (focusedInstance?.metadata?.Queries?.length > 0 || queries?.find(q => q.short_form === focusedInstance.metadata.Id)) && (
<Button
onClick={() => setBottomNav((prev) => prev === 2 ? null : 2)}
onClick={() => setBottomNav((prev) => prev === bottomNavQuery ? null : bottomNavQuery)}
variant="outlined"
>
<QueryStats size={16} />
Expand Down
Loading
Loading