Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { Box, Typography } from '@mui/material';
import { getNeuroglassState, hasNeuroglassState } from '../utils/neuroglassStateConfig';

const NEUROGLASS_URL = import.meta.env.VITE_NEUROGLASS_URL || 'https://www.research.neuroglass.dev.metacell.us';

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

const focusedInstance = useSelector(state => state.instances?.focusedInstance);

const iframeSrcUrl = useMemo(() => {
if (!focusedInstance?.metadata?.Id) return '';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use template fallback when focused instance is absent

The early return on missing focus ID (return '') prevents the component from ever reaching the default-template fallback branch, so opening this widget before an instance is focused leaves iframeSrc empty and the panel stuck on the loading placeholder. That breaks the intended “focused instance or default template” behavior in startup/empty-focus states.

Useful? React with 👍 / 👎.


// Priority 1: Predefined state for focused instance (if available)
let stateToUse = null;
if (hasNeuroglassState(focusedInstance.metadata.Id)) {
stateToUse = getNeuroglassState(focusedInstance.metadata.Id);
}
// Priority 2: Default to template
else {
stateToUse = getNeuroglassState('VFB_00101567');
}

if (!stateToUse) return '';

const stateStr = JSON.stringify(stateToUse);
const encodedState = encodeURIComponent(stateStr);
return `${NEUROGLASS_URL}/new#!${encodedState}`;
}, [focusedInstance?.metadata?.Id]);

useEffect(() => {
if (iframeSrcUrl) {
setIframeSrc(iframeSrcUrl);
}
}, [iframeSrcUrl]);

return (
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
{iframeSrc ? (
<Box sx={{ flex: 1, border: '1px solid #ccc', borderRadius: 1, overflow: 'hidden' }}>
<iframe
src={iframeSrc}
style={{
width: '100%',
height: '100%',
border: 'none',
backgroundColor: '#000',
}}
title="Neuroglass Viewer"
sandbox="allow-scripts"
/>
</Box>
) : (
<Box sx={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: '#fafafa' }}>
<Typography color="textSecondary">Loading Neuroglass viewer...</Typography>
</Box>
)}
</Box>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,14 @@ export const toolbarMenu = (autoSaveLayout) => { return {
parameters: [widgets?.stackViewerWidget?.id]
}
},
{
label: "Neuroglass Viewer",
icon: "fa fa-brain",
action: {
handlerAction: ACTIONS.SHOW_WIDGET,
parameters: [widgets?.neuroglassViewerWidget?.id]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle missing Neuroglass widget in imported layouts

This menu item assumes state.widgets['neuroglassViewerWidget'] exists, but menuHandler builds the widget from Redux (shared/header/index.jsx, widgets[action.parameters[0]]) and imported custom layouts overwrite widgets from saved data (reducers/middleware/vfbMiddleware.js, widgets: { ...action.data.redux.widgets }). If a user loads a layout saved before this commit, that key is absent, so clicking “Neuroglass Viewer” dispatches an incomplete widget update and the viewer does not open.

Useful? React with 👍 / 👎.

}
},
{
label: "Template ROI Browser",
icon: "fa fa-indent",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ThreeDCanvas from '../ThreeDCanvas';
import VFBCircuitBrowser from '../VFBCircuitBrowser';
import VFBGraph from '../VFBGraph';
import VFBListViewer from '../VFBListViewer';
import NeuroglassViewer from '../NeuroglassViewer';
/**
* Key of the component is the `component` attribute of the widgetConfiguration.
* This map is used inside the LayoutManager to know which component to display for a given widget.
Expand All @@ -14,7 +15,8 @@ const componentMap = {
'roiBrowser': ROIBrowser,
'termContext' : VFBGraph,
'circuitBrowser' : VFBCircuitBrowser,
'listViewer': VFBListViewer
'listViewer': VFBListViewer,
'neuroglassViewer': NeuroglassViewer
};

export default componentMap
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export const widgetsIDs = {
roiBrowserWidgetID : 'roiBrowserWidget',
termContextWidgetID : 'termContextWidget',
circuitBrowserWidgetID : 'circuitBrowserWidget',
listViewerWidgetID : 'listViewerWidget'
listViewerWidgetID : 'listViewerWidget',
neuroglassViewerWidgetID : 'neuroglassViewerWidget'
};

export const widgets = {
Expand Down Expand Up @@ -81,4 +82,16 @@ export const widgets = {
pos: 4,
props: { size: { height: 600, width: 300 } }
},

neuroglassViewerWidget : {
id: widgetsIDs.neuroglassViewerWidgetID,
name: "Neuroglass Viewer",
component: "neuroglassViewer",
panelName: "right",
hideOnClose: true,
status: WidgetStatus.HIDDEN,
defaultPosition: 'RIGHT',
pos: 5,
props: { size: { height: 600, width: 800 } }
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Neuroglass Widget Actions
* Controls visibility and state of the Neuroglass viewer widget
*/

import { setWidgetVisible, WidgetStatus } from '@metacell/geppetto-meta-client/common/layout/actions';
import { widgetsIDs } from '../layout/widgets';
import { hasNeuroglassState, getNeuroglassState, NEUROGLASS_STATES_MAP } from './neuroglassStateConfig';


/**
* Show the Neuroglass viewer widget
* @param {Object} store - Redux store instance
*/
export const showNeuroglassViewer = (store) => {
store.dispatch(setWidgetVisible(widgetsIDs.neuroglassViewerWidgetID, true));
};

/**
* Hide the Neuroglass viewer widget
* @param {Object} store - Redux store instance
*/
export const hideNeuroglassViewer = (store) => {
store.dispatch(setWidgetVisible(widgetsIDs.neuroglassViewerWidgetID, false));
};

/**
* Toggle Neuroglass viewer widget visibility
* @param {Object} store - Redux store instance
*/
export const toggleNeuroglassViewer = (store) => {
const state = store.getState();
const widgets = state.widgets || {};
const neuroglassWidget = widgets[widgetsIDs.neuroglassViewerWidgetID];

const isVisible = neuroglassWidget?.status === WidgetStatus.ACTIVE;
store.dispatch(setWidgetVisible(widgetsIDs.neuroglassViewerWidgetID, !isVisible));
};

/**
* Check if an instance has Neuroglass data available
* @param {string} instanceId - VFB instance ID
* @returns {boolean} True if Neuroglass data exists
*/
export const hasNeuroglassData = (instanceId) => {
return hasNeuroglassState(instanceId);
};

/**
* Get the Neuroglass viewer state for a specific instance
* @param {string} instanceId - VFB instance ID
* @returns {Object|null} Neuroglass state or null if not available
*/
export const getNeuroglassStateForInstance = (instanceId) => {
return getNeuroglassState(instanceId);
};

/**
* Auto-show Neuroglass widget when a compatible instance is loaded
* @param {Object} store - Redux store instance
*/
export const autoShowNeuroglass = (store) => {
const state = store.getState();
const loadedInstances = state.instances.allLoadedInstances || [];

const hasCompatibleInstance = loadedInstances.some(
instance => hasNeuroglassData(instance.metadata?.Id)
);

if (hasCompatibleInstance) {
showNeuroglassViewer(store);
}
};
Loading
Loading