diff --git a/applications/virtual-fly-brain/frontend/src/components/CameraControls.jsx b/applications/virtual-fly-brain/frontend/src/components/CameraControls.jsx
index a7ba418f..fbbb706d 100644
--- a/applications/virtual-fly-brain/frontend/src/components/CameraControls.jsx
+++ b/applications/virtual-fly-brain/frontend/src/components/CameraControls.jsx
@@ -1,5 +1,5 @@
/* eslint-disable react-refresh/only-export-components */
-import React, { useState } from 'react';
+import React, { useState, forwardRef } from 'react';
import { Box, IconButton, Tooltip } from '@mui/material';
import { useDispatch } from 'react-redux';
import {
@@ -42,7 +42,8 @@ export const cameraControlsRotateState = {
STOPPING: 'stopping',
};
-const CameraControls = (props) => {
+// eslint-disable-next-line no-unused-vars
+const CameraControls = forwardRef((props, _ref) => {
const {
cameraControlsHandler,
canvasHeight,
@@ -163,6 +164,8 @@ const CameraControls = (props) => {
)}
);
-};
+});
+
+CameraControls.displayName = 'CameraControls';
export default CameraControls;
diff --git a/applications/virtual-fly-brain/frontend/src/components/StackViewerComponent.jsx b/applications/virtual-fly-brain/frontend/src/components/StackViewerComponent.jsx
index fe574e9e..a09c2d8c 100644
--- a/applications/virtual-fly-brain/frontend/src/components/StackViewerComponent.jsx
+++ b/applications/virtual-fly-brain/frontend/src/components/StackViewerComponent.jsx
@@ -173,7 +173,7 @@ const rgbToHex = (color) => {
this.app = new Application({ width : this.props.width, height : this.props.height});
// this.app.renderer.backgroundColor = '#1a1a1a';
// maintain full window size
- this.refs.stackCanvas?.getElementsByTagName("canvas")?.length == 0 && this.refs.stackCanvas?.appendChild(this.app.view);
+ this.stackCanvas?.getElementsByTagName("canvas")?.length == 0 && this.stackCanvas?.appendChild(this.app.view);
this.disp = new Container({ width : this.props.width, height : this.props.height});
this.disp.pivot.x = 0;
@@ -243,8 +243,8 @@ const rgbToHex = (color) => {
this.lastWindowHeight = window.innerHeight;
// Create bound methods for proper cleanup
- this.boundHandleWindowResize = this.handleWindowResize.bind(this);
- this.boundHandleVisualViewportChange = this.handleVisualViewportChange.bind(this);
+ this.boundHandleWindowResize = this.handleWindowResize;
+ this.boundHandleVisualViewportChange = this.handleVisualViewportChange;
// Listen for window resize events (covers most zoom scenarios)
window.addEventListener('resize', this.boundHandleWindowResize);
@@ -255,7 +255,7 @@ const rgbToHex = (color) => {
}
// Setup ResizeObserver for the canvas container if available
- if (window.ResizeObserver && this.refs.stackCanvas) {
+ if (window.ResizeObserver && this.stackCanvas) {
this.resizeObserver = new ResizeObserver(() => {
if (!this._isMounted) return;
@@ -268,13 +268,13 @@ const rgbToHex = (color) => {
}
});
- this.resizeObserver.observe(this.refs.stackCanvas);
+ this.resizeObserver.observe(this.stackCanvas);
}
// Fallback: Listen for devicePixelRatio changes (Webkit-based browsers)
if (window.matchMedia) {
this.mediaQueryList = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
- this.boundHandleDevicePixelRatioChange = this.handleDevicePixelRatioChange.bind(this);
+ this.boundHandleDevicePixelRatioChange = this.handleDevicePixelRatioChange;
this.mediaQueryList.addListener(this.boundHandleDevicePixelRatioChange);
}
@@ -343,7 +343,7 @@ const rgbToHex = (color) => {
// Properly cleanup PIXI application and WebGL context
if (this.app) {
// Remove canvas from DOM
- if (this.refs.stackCanvas && this.app.view && this.app.view.parentNode) {
+ if (this.stackCanvas && this.app.view && this.app.view.parentNode) {
this.app.view.parentNode.removeChild(this.app.view);
}
@@ -494,7 +494,9 @@ const rgbToHex = (color) => {
let extent = { imageX: imageX, imageY: imageY };
that.setState(extent);
that.props.setExtent(extent);
- that.onResize(that.props.width, that.props.height);
+ if (typeof that.onResize === 'function') {
+ that.onResize(that.props.width, that.props.height);
+ }
that.checkStack();
that.callPlaneEdges();
that.state.iBuffer = {};
@@ -1519,7 +1521,7 @@ const rgbToHex = (color) => {
*/
render: function () {
return (
- < div className="stack-canvas-container" ref="stackCanvas">
+
{ this.stackCanvas = el; }}>
);
},
@@ -2251,7 +2253,7 @@ const StackViewerComponent = () => createClass({
}
return
-
+
{markup}
diff --git a/applications/virtual-fly-brain/frontend/src/components/TermInfo.jsx b/applications/virtual-fly-brain/frontend/src/components/TermInfo.jsx
index 9e9e3bb0..2892feb7 100644
--- a/applications/virtual-fly-brain/frontend/src/components/TermInfo.jsx
+++ b/applications/virtual-fly-brain/frontend/src/components/TermInfo.jsx
@@ -748,6 +748,7 @@ const TermInfo = ({ open, setOpen }) => {
>
{!open ? (
{
sx={{ justifyContent: { lg: "space-between", xs: "flex-end" } }}
>
-
+
{
-
+
{
}
- defaultCollapseIcon={}
+ slots={{ expandIcon: ArrowRight, collapseIcon: ArrowDown }}
>
{/* Group queries that start with "Neurons with" */}
{groupedQueries.map((group, groupIndex) => (
diff --git a/applications/virtual-fly-brain/frontend/src/components/TermInfo/GeneralInformation.jsx b/applications/virtual-fly-brain/frontend/src/components/TermInfo/GeneralInformation.jsx
index b95d53c5..1b2a39d1 100644
--- a/applications/virtual-fly-brain/frontend/src/components/TermInfo/GeneralInformation.jsx
+++ b/applications/virtual-fly-brain/frontend/src/components/TermInfo/GeneralInformation.jsx
@@ -35,7 +35,8 @@ const GeneralInformation = ({ data, classes, showMetadataOnly = false }) => {
message: ''
});
const [isLoading, setIsLoading] = useState(false);
- const reduxState = useSelector(state => state);
+ const launchTemplate = useSelector(state => state.instances.launchTemplate);
+ const alignedTemplates = useSelector(state => state.globalInfo.alignedTemplates);
const MAX_LENGTH = 300;
// Utility function to decode URL-encoded strings
@@ -50,7 +51,7 @@ const GeneralInformation = ({ data, classes, showMetadataOnly = false }) => {
};
// Get current template information
- const currentTemplate = reduxState.instances.launchTemplate;
+ const currentTemplate = launchTemplate;
const currentTemplateName = currentTemplate?.metadata?.Name || 'Unknown Template';
const currentTemplateId = currentTemplate?.metadata?.Id;
@@ -64,7 +65,6 @@ const GeneralInformation = ({ data, classes, showMetadataOnly = false }) => {
);
} else {
// Check if template is aligned
- const alignedTemplates = reduxState.globalInfo.alignedTemplates;
const isAligned = alignedTemplates[templateId];
// If template is aligned, load it directly
@@ -410,7 +410,8 @@ const GeneralInformation = ({ data, classes, showMetadataOnly = false }) => {
{
templateIds.map((templateId) => {
return (
- templateId !== currentTemplateId && }
label={alignedTemplatesLabels[templateId] || templateId}
sx={{
@@ -1042,7 +1043,7 @@ const GeneralInformation = ({ data, classes, showMetadataOnly = false }) => {
return (
<>
- {!showMetadataOnly &&
+ {!showMetadataOnly &&
{
sm: 0,
},
width: showMetadataOnly ? '100%' : 'initial'
- }} item xs={12} sm={showMetadataOnly ? 12 : 8} md={showMetadataOnly ? 12 : 7} lg={showMetadataOnly ? 12 : 7}>
-
+ }} size={{ xs: 12, sm: showMetadataOnly ? 12 : 8, md: showMetadataOnly ? 12 : 7, lg: showMetadataOnly ? 12 : 7 }}>
+
{getMetadataProperties().map(({ key, value, isStatic, isAlignedTo }) => {
// Handle special cases
if (key === 'Description' || key === 'Comment') {
@@ -1122,7 +1123,7 @@ const GeneralInformation = ({ data, classes, showMetadataOnly = false }) => {
return (
- {key}
+ {key}
{renderedValue}
);
diff --git a/applications/virtual-fly-brain/frontend/src/components/TermInfo/TerminfoSlider.jsx b/applications/virtual-fly-brain/frontend/src/components/TermInfo/TerminfoSlider.jsx
index 41e1d2f0..02df06be 100644
--- a/applications/virtual-fly-brain/frontend/src/components/TermInfo/TerminfoSlider.jsx
+++ b/applications/virtual-fly-brain/frontend/src/components/TermInfo/TerminfoSlider.jsx
@@ -114,7 +114,8 @@ const TerminfoSlider = (props) => {
message: ''
});
const [isLoading, setIsLoading] = useState(false);
- const reduxState = useSelector(state => state);
+ const launchTemplate = useSelector(state => state.instances.launchTemplate);
+ const focusedInstance = useSelector(state => state.instances.focusedInstance);
const misalignedTemplate = useSelector(state => state.globalInfo.misalignedTemplate)
const alignedTemplates = useSelector(state => state.globalInfo.alignedTemplates)
const misalignedIDs = useSelector(state => state.globalInfo.misalignedIDs)
@@ -142,16 +143,16 @@ const TerminfoSlider = (props) => {
};
const imageClick = (image) => {
- if (image !== reduxState.instances.launchTemplate?.metadata?.Id) {
- const templates = Object.keys(reduxState.instances.focusedInstance.metadata.Examples);
+ if (image !== launchTemplate?.metadata?.Id) {
+ const templates = Object.keys(focusedInstance.metadata.Examples);
const examples = new Map();
templates.forEach(t => {
- reduxState.instances.focusedInstance.metadata.Examples[t].forEach(e => {
+ focusedInstance.metadata.Examples[t].forEach(e => {
examples.set(e.id, {...e, template: t});
});
});
const example = examples.get(image);
- if (example.template !== reduxState.instances.launchTemplate?.metadata?.Id) {
+ if (example.template !== launchTemplate?.metadata?.Id) {
setConfirmationModal({
open: true,
example,
@@ -183,7 +184,7 @@ const TerminfoSlider = (props) => {
const keys = Object.keys(props.examples);
// Get current template from store
- const currentTemplate = reduxState.instances.launchTemplate?.metadata?.Id;
+ const currentTemplate = launchTemplate?.metadata?.Id;
// Define the priority order for templates
const templatePriorityOrder = [
@@ -225,12 +226,11 @@ const TerminfoSlider = (props) => {
});
});
- console.log('TerminfoSlider: Setting slideImages with priority order', images);
setSlideImages(images);
} else {
setSlideImages([]);
}
- }, [props.examples, reduxState.instances.launchTemplate?.metadata?.Id]);
+ }, [props.examples, launchTemplate?.metadata?.Id]);
return (
@@ -255,8 +255,7 @@ const TerminfoSlider = (props) => {
src={slideImage.url}
onClick={() => imageClick(slideImage.id)}
alt={slideImage.caption}
- onLoad={() => console.log(`Image ${index} loaded:`, slideImage.url)}
- onError={() => console.log(`Image ${index} failed to load:`, slideImage.url)}
+ onError={() => console.error(`Image ${index} failed to load:`, slideImage.url)}
/>
))}
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 fcca0bf8..d4687f4a 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
@@ -200,7 +200,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "fa fa-search",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
- parameters: [bottomNavSearch]
+ parameters: [String(bottomNavSearch)]
}
},
{
@@ -208,7 +208,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "fa fa-clipboard-question",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
- parameters: [bottomNavQuery]
+ parameters: [String(bottomNavQuery)]
}
},
{
@@ -280,7 +280,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "fa fa-download",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
- parameters: [bottomNavDownload]
+ parameters: [String(bottomNavDownload)]
}
},
{
@@ -288,13 +288,13 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "fa fa-upload",
action: {
handlerAction: ACTIONS.SHOW_COMPONENT,
- parameters: [bottomNavUpload]
+ parameters: [String(bottomNavUpload)]
}
},
{
label: "NBLAST",
icon: "",
- action: {},
+ action: { handlerAction: "submenu", parameters: ["undefinedAction"] },
position: "right-start",
list: [
{
@@ -320,7 +320,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
{
label: "CATMAID",
icon: "",
- action: {},
+ action: { handlerAction: "submenu", parameters: ["undefinedAction"] },
position: "right-start",
list: [
{
@@ -505,7 +505,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
{
label: "VFB CONNECT (API)",
icon: "",
- action: {},
+ action: { handlerAction: "submenu", parameters: ["undefinedAction"] },
position: "right-start",
list: [
{
@@ -544,10 +544,13 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "",
action: {},
position: "bottom-start",
- dynamicListInjector: {
+ list: [],
+ dynamicListInjector: [{
+ label: "",
+ icon: "",
handlerAction: ACTIONS.HISTORY_MENU_INJECTOR,
parameters: [""]
- }
+ }]
},
{
label: "Templates",
@@ -682,7 +685,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "",
action: {
handlerAction: ACTIONS.RUN_QUERY,
- parameters: ["VFB_00101567"]
+ parameters: ["AllDatasets", "VFB_00101567"]
}
},
{
@@ -690,7 +693,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "",
action: {
handlerAction: ACTIONS.RUN_QUERY,
- parameters: ["VFB_00200000"]
+ parameters: ["AllDatasets", "VFB_00200000"]
}
},
{
@@ -707,7 +710,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "",
action: {
handlerAction: ACTIONS.RUN_QUERY,
- parameters: ["VFB_00110000"]
+ parameters: ["AllDatasets", "VFB_00110000"]
}
},
{
@@ -715,7 +718,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "",
action: {
handlerAction: ACTIONS.RUN_QUERY,
- parameters: ["VFB_00017894"]
+ parameters: ["AllDatasets", "VFB_00017894"]
}
},
{
@@ -723,7 +726,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "",
action: {
handlerAction: ACTIONS.RUN_QUERY,
- parameters: ["VFB_00100000"]
+ parameters: ["AllDatasets", "VFB_00100000"]
}
},
{
@@ -731,7 +734,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "",
action: {
handlerAction: ACTIONS.RUN_QUERY,
- parameters: ["VFB_00101384"]
+ parameters: ["AllDatasets", "VFB_00101384"]
}
},
{
@@ -739,7 +742,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "",
action: {
handlerAction: ACTIONS.RUN_QUERY,
- parameters: ["VFB_00030786"]
+ parameters: ["AllDatasets", "VFB_00030786"]
}
}
]
@@ -760,7 +763,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "",
action: {
handlerAction: ACTIONS.RUN_QUERY,
- parameters: ["VFB_00050000"]
+ parameters: ["AllDatasets", "VFB_00050000"]
}
},
{
@@ -768,7 +771,7 @@ export const toolbarMenu = (autoSaveLayout) => { return {
icon: "",
action: {
handlerAction: ACTIONS.RUN_QUERY,
- parameters: ["VFB_00049000"]
+ parameters: ["AllDatasets", "VFB_00049000"]
}
}
]
diff --git a/applications/virtual-fly-brain/frontend/src/components/queryBuilder/Card.jsx b/applications/virtual-fly-brain/frontend/src/components/queryBuilder/Card.jsx
index b1e04b1c..6ed90280 100644
--- a/applications/virtual-fly-brain/frontend/src/components/queryBuilder/Card.jsx
+++ b/applications/virtual-fly-brain/frontend/src/components/queryBuilder/Card.jsx
@@ -40,6 +40,8 @@ const QueryCard = ({ fullWidth, facets_annotation, query }) => {
}
};
+ const extractMarkdownText = (str) => str.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
+
return (
<>
{
return (
{key}
-
+
{
textOverflow: 'ellipsis'
}
}}>
- (
- handleLinkClick(href, e)}
- {...props}
- >
- {children}
-
- ),
- p: ({ children, ...props }) => (
- {children}
- )
- }}
- >
- {String(value)}
-
+ {key === 'id' ? (
+ getInstanceByID(String(value), true, true, true)}
+ >
+ {String(value)}
+
+ ) : (
+ (
+ handleLinkClick(href, e)}
+ {...props}
+ >
+ {children}
+
+ ),
+ p: ({ children, ...props }) => (
+ {children}
+ )
+ }}
+ >
+ {String(value)}
+
+ )}
diff --git a/applications/virtual-fly-brain/frontend/src/main.jsx b/applications/virtual-fly-brain/frontend/src/main.jsx
index a77e7559..c151b4c3 100644
--- a/applications/virtual-fly-brain/frontend/src/main.jsx
+++ b/applications/virtual-fly-brain/frontend/src/main.jsx
@@ -1,6 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import reportWebVitals from './reportWebVitals';
+
+// Suppress legacy childContextTypes warning from griddle-react/recompose (third-party, no fix available)
+const _origConsoleError = console.error;
+console.error = (...args) => {
+ if (typeof args[0] === 'string' && args[0].includes('childContextTypes')) return;
+ _origConsoleError(...args);
+};
import './index.css';
import App from './App';
import { Provider } from 'react-redux'
diff --git a/applications/virtual-fly-brain/frontend/src/reducers/middleware/urlUpdaterMiddleware.js b/applications/virtual-fly-brain/frontend/src/reducers/middleware/urlUpdaterMiddleware.js
index a2677272..09c90dff 100644
--- a/applications/virtual-fly-brain/frontend/src/reducers/middleware/urlUpdaterMiddleware.js
+++ b/applications/virtual-fly-brain/frontend/src/reducers/middleware/urlUpdaterMiddleware.js
@@ -237,8 +237,15 @@ export const urlUpdaterMiddleware = store => next => (action) => {
// if it's an individual, we need to check if it's aligned with the current template
if (isIndividual) {
+ // If Images is empty, the individual has no visuals and can be loaded by any template
+ if (Object.keys(action.payload?.Images || {}).length === 0) {
+ next(action);
+ updateUrlWithInstancesAndSelectedId(action.payload.Id, store);
+ return;
+ }
+
// Check if the individual is aligned with the current template
- const templateLookup = action.payload?.Images || action.payload?.Examples || {};
+ const templateLookup = action.payload?.Images || {};
const templates = Object.keys(templateLookup);
const loadedTemplate = launchTemplate?.metadata?.Id;
if (loadedTemplate && templates.includes(loadedTemplate)) {
@@ -267,10 +274,6 @@ export const urlUpdaterMiddleware = store => next => (action) => {
get3DMesh(action.payload);
return;
} else if (!templates.includes(loadedTemplate) && !IsTemplate) {
- if(Object.keys(action.payload?.Images || {}).length === 0 && Object.keys(action.payload?.Examples || {}).length === 0) {
- next(action);
- return;
- }
// If the individual is not aligned with the current template, we need to show the misalignment dialog
store.dispatch(setAlignTemplates(false, action.payload.Id, Object.keys(action.payload?.Images)|| Object.keys(action.payload?.Examples || {})));
return;
diff --git a/applications/virtual-fly-brain/frontend/src/shared/header/index.jsx b/applications/virtual-fly-brain/frontend/src/shared/header/index.jsx
index 62b33ae9..aaca5912 100644
--- a/applications/virtual-fly-brain/frontend/src/shared/header/index.jsx
+++ b/applications/virtual-fly-brain/frontend/src/shared/header/index.jsx
@@ -6,7 +6,7 @@ import { Box, Button } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import Menu from '@metacell/geppetto-meta-ui/menu/Menu';
import { History, Logo, Menu as MenuIcon, QueryStats } from "../../icons";
-import { getQueries, updateQueries } from '../../reducers/actions/queries';
+import { getQueries } from '../../reducers/actions/queries';
import { loadCustomLayout, saveCustomLayout } from "../../reducers/actions/layout";
import { updateWidget } from "@metacell/geppetto-meta-client/common/layout/actions";
import { selectInstance, focusInstance, getInstanceByID, triggerInstanceFailure } from '../../reducers/actions/instances';
@@ -112,7 +112,7 @@ const Header = ({ setBottomNav }) => {
break;
}
case ACTIONS.SHOW_COMPONENT:
- setBottomNav(action.parameters[0])
+ setBottomNav(Number(action.parameters[0]))
break;
case ACTIONS.SHOW_TERM_INFO: {
dispatch(setTermInfoOpened(true))
@@ -134,21 +134,10 @@ const Header = ({ setBottomNav }) => {
break;
}
case ACTIONS.RUN_QUERY: {
- let updatedQueries = [...queries];
- let matchQuery = updatedQueries?.find(q => q.short_form === action.parameters[0]);
- updatedQueries?.forEach(query => {
- if (query.queries) {
- Object.keys(query.queries)?.forEach(q => query.queries[q].active = false);
- }
- });
- if (matchQuery?.short_form == action?.parameters[0]) {
- Object.keys(matchQuery.queries)?.forEach(q => matchQuery.queries[q].active = true);
- updateQueries(updatedQueries);
- setBottomNav(bottomNavQuery)
- } else {
- getQueries(action.parameters[0], action.parameters[1])
- setBottomNav(bottomNavQuery)
- }
+ const type = action.parameters[0];
+ const short_form = action.parameters[1];
+ getQueries(short_form, type);
+ setBottomNav(bottomNavQuery);
break;
}
case ACTIONS.HISTORY_MENU_INJECTOR: {
@@ -161,7 +150,7 @@ const Header = ({ setBottomNav }) => {
icon: i?.is_query ? "fa fa-quora" : "fa fa-eye", // TODO : replace with figma icon
action: {
handlerAction: i?.is_query ? ACTIONS.RUN_QUERY : ACTIONS.SELECT_INSTANCE,
- parameters: [i?.short_form, i?.type]
+ parameters: [i?.type, i?.short_form]
}
},
);