diff --git a/applications/virtual-fly-brain/frontend/src/components/Layout.jsx b/applications/virtual-fly-brain/frontend/src/components/Layout.jsx
index 5785d70d..bbce5db1 100644
--- a/applications/virtual-fly-brain/frontend/src/components/Layout.jsx
+++ b/applications/virtual-fly-brain/frontend/src/components/Layout.jsx
@@ -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,
@@ -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();
}
@@ -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] }
@@ -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 && }
- {bottomNav === 3 && }
- {bottomNav === 6 && }
+ {bottomNav === bottomNavSnapshot && < VFBSnapshot open={true} setBottomNav={setBottomNav} />}
+ {bottomNav === bottomNavUpload && < VFBUploader open={true} setBottomNav={setBottomNav} />}
+ {bottomNav === bottomNavDownload && }
+ {bottomNav === bottomNavQuery && }
+ {bottomNav === bottomNavSearch && }
>
) : (
<>
{
- bottomNav != 3 && tabContent
+ bottomNav != bottomNavQuery && tabContent
}
- {bottomNav === 0 && }
- {bottomNav === 1 && }
- {bottomNav === 2 && }
- {bottomNav === 3 && }
- {bottomNav === 6 && }
+ {bottomNav === bottomNavSnapshot && }
+ {bottomNav === bottomNavUpload && }
+ {bottomNav === bottomNavDownload && }
+ {bottomNav === bottomNavQuery && }
+ {bottomNav === bottomNavSearch && }
>
)}
diff --git a/applications/virtual-fly-brain/frontend/src/components/NeuroglassViewer.jsx b/applications/virtual-fly-brain/frontend/src/components/NeuroglassViewer.jsx
index b2ce5c05..6283c49b 100644
--- a/applications/virtual-fly-brain/frontend/src/components/NeuroglassViewer.jsx
+++ b/applications/virtual-fly-brain/frontend/src/components/NeuroglassViewer.jsx
@@ -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';
@@ -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);
@@ -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(() => {
diff --git a/applications/virtual-fly-brain/frontend/src/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.jsx b/applications/virtual-fly-brain/frontend/src/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.jsx
index df02622b..fcca0bf8 100644
--- a/applications/virtual-fly-brain/frontend/src/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.jsx
+++ b/applications/virtual-fly-brain/frontend/src/components/configuration/VFBToolbar/vfbtoolbarMenuConfiguration.jsx
@@ -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 = {
@@ -199,7 +200,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "fa fa-search",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
- parameters: [5]
+ parameters: [bottomNavSearch]
}
},
{
@@ -207,7 +208,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "fa fa-clipboard-question",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
- parameters: [2]
+ parameters: [bottomNavQuery]
}
},
{
@@ -279,7 +280,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "fa fa-download",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
- parameters: [1]
+ parameters: [bottomNavDownload]
}
},
{
@@ -287,7 +288,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "fa fa-upload",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
- parameters: [0]
+ parameters: [bottomNavUpload]
}
},
{
diff --git a/applications/virtual-fly-brain/frontend/src/components/queryBuilder/Query.jsx b/applications/virtual-fly-brain/frontend/src/components/queryBuilder/Query.jsx
index 46959eb8..fe1f7d23 100644
--- a/applications/virtual-fly-brain/frontend/src/components/queryBuilder/Query.jsx
+++ b/applications/virtual-fly-brain/frontend/src/components/queryBuilder/Query.jsx
@@ -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);
@@ -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));
});
@@ -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 });
@@ -249,8 +279,8 @@ const Query = forwardRef(({ fullWidth, queries, searchTerm }, ref) => {
return acc;
}, {})
}));
- updateQueries(clearQueries);
- }, [queries]);
+ dispatch(updateQueries(clearQueries));
+ }, [queries, dispatch]);
const clearAllTags = useCallback(() => {
setChipTags(prevTags => prevTags.map(tag => ({ ...tag, active: true })));
@@ -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={
{
}
// Open the query component panel
- setBottomNav(2);
+ setBottomNav(bottomNavQuery);
}
}
@@ -140,13 +141,13 @@ const Header = ({ setBottomNav }) => {
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)
+ getQueries(action.parameters[0])
+ setBottomNav(bottomNavQuery)
}
break;
}
@@ -310,7 +311,7 @@ const Header = ({ setBottomNav }) => {
{focusedInstance?.metadata?.Id && (focusedInstance?.metadata?.Queries?.length > 0 || queries?.find(q => q.short_form === focusedInstance.metadata.Id)) && (