diff --git a/node_modules/.yarn-integrity b/node_modules/.yarn-integrity new file mode 100644 index 0000000..f498cf6 --- /dev/null +++ b/node_modules/.yarn-integrity @@ -0,0 +1,12 @@ +{ + "systemParams": "linux-x64-93", + "modulesFolders": [ + "node_modules" + ], + "flags": [], + "linkedModules": [], + "topLevelPatterns": [], + "lockfileEntries": {}, + "files": [], + "artifacts": {} +} \ No newline at end of file diff --git a/webapp/components/AddPlotMenu.js b/webapp/components/AddPlotMenu.js index f09c0c1..d645f4e 100644 --- a/webapp/components/AddPlotMenu.js +++ b/webapp/components/AddPlotMenu.js @@ -33,9 +33,9 @@ export default class AddPlotMenu extends Component { } dontGoToSameHostTwice (widget) { - const { instancePaths } = widget; + const { instancePaths } = widget.config; - return instancePaths && instancePaths.indexOf(this.props.instancePath) == -1; + return widget.instancePaths && instancePaths.indexOf(this.props.instancePath) == -1; } goOnlyToTimeseriesWidgets (widget) { diff --git a/webapp/components/ListMenu.js b/webapp/components/ListMenu.js index 1d68648..8a6c7d7 100644 --- a/webapp/components/ListMenu.js +++ b/webapp/components/ListMenu.js @@ -57,6 +57,7 @@ export default class ListMenuComponent extends React.Component { parameters: [addToPlot, { hostId: availablePlot.id, instancePath: this.props.entity.path, + component: 'Plot', type: 'timeseries', }], }, diff --git a/webapp/components/NWBPlot.js b/webapp/components/NWBPlot.js index 8c9bf41..75c9d88 100644 --- a/webapp/components/NWBPlot.js +++ b/webapp/components/NWBPlot.js @@ -47,7 +47,7 @@ export default class NWBTimeseriesPlotComponent extends React.Component { const plots = instancePaths.map(instancePath => ({ x: `${instancePath}.timestamps`, y: `${instancePath}.data`, - lineOptions: { color: this.props.modelSettings[instancePath].color }, + lineOptions: { color: this.props.modelSettings[instancePath]?.color }, })); return ( diff --git a/webapp/components/configuration/AddToPlotComponent.jsx b/webapp/components/configuration/AddToPlotComponent.jsx index 3b03e99..c5a06a5 100644 --- a/webapp/components/configuration/AddToPlotComponent.jsx +++ b/webapp/components/configuration/AddToPlotComponent.jsx @@ -3,11 +3,11 @@ import React from 'react'; import AddPlotMenuConnect from '../reduxconnect/AddPlotMenuConnect'; const AddToPlotComponent = ({ icon, label, action, tooltip }) => ({ value }) => ( - - ) + +) export default AddToPlotComponent; \ No newline at end of file diff --git a/webapp/components/reduxconnect/AddPlotMenuConnect.js b/webapp/components/reduxconnect/AddPlotMenuConnect.js index 5a431ec..64278a2 100644 --- a/webapp/components/reduxconnect/AddPlotMenuConnect.js +++ b/webapp/components/reduxconnect/AddPlotMenuConnect.js @@ -3,7 +3,7 @@ import AddPlotMenu from '../AddPlotMenu'; const mapStateToProps = (state, ownProps) => ({ icon: ownProps.icon, - widgets: Object.values(state.flexlayout.widgets).filter(w => w.component == 'Plot').map(w => ({ instancePaths: w.instancePaths, id: w.id, name: w.name })), + widgets: Object.values(state.flexlayout.widgets).filter(w => w.component == 'Plot').map(w => ({ instancePaths: w.config.instancePaths, id: w.id, name: w.name })), }); export default connect(mapStateToProps)(AddPlotMenu); diff --git a/webapp/components/reduxconnect/ListMenuContainer.js b/webapp/components/reduxconnect/ListMenuContainer.js index b501810..0be0e2b 100644 --- a/webapp/components/reduxconnect/ListMenuContainer.js +++ b/webapp/components/reduxconnect/ListMenuContainer.js @@ -5,7 +5,7 @@ import { updateSettings } from '../../redux/actions/nwbfile'; const mapStateToProps = state => ({ modelSettings: state.nwbfile.modelSettings, - widgets: Object.values(state.widgets).filter(w => w.component == 'Plot').map(w => ({ instancePaths: w.instancePaths, id: w.id, name: w.name })), + widgets: Object.values(state.widgets).filter(w => w.component == 'Plot').map(w => ({ instancePaths: w.config.instancePaths, id: w.id, name: w.name })), }); const mapDispatchToProps = dispatch => ({ diff --git a/webapp/package.json b/webapp/package.json index 271bcbc..082b6b7 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -19,9 +19,9 @@ "@material-ui/core": "4.12.1", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.60", - "@metacell/geppetto-meta-client": "1.0.0-final", - "@metacell/geppetto-meta-core": "1.0.0-final", - "@metacell/geppetto-meta-ui": "1.0.0-final", + "@metacell/geppetto-meta-client": "1.2.4", + "@metacell/geppetto-meta-core": "1.2.4", + "@metacell/geppetto-meta-ui": "1.2.4", "griddle-react": "^1.13.1", "jszip": "^3.2.1", "less-vars-to-js": "^1.3.0", diff --git a/webapp/redux/actions/widgets.js b/webapp/redux/actions/widgets.js index 948cd0d..69acc6e 100644 --- a/webapp/redux/actions/widgets.js +++ b/webapp/redux/actions/widgets.js @@ -4,7 +4,6 @@ import * as LayoutActions from "@metacell/geppetto-meta-client/common/layout/act export const { ADD_WIDGET, - ADD_PLOT_TO_EXISTING_WIDGET, UPDATE_WIDGET, SET_LAYOUT, DESTROY_WIDGET, @@ -12,6 +11,8 @@ export const { RESET_LAYOUT } = LayoutActions.layoutActions; +export const ADD_PLOT_TO_EXISTING_WIDGET = "ADD_PLOT_TO_EXISTING_WIDGET"; + export const showPlot = ({ path, title }) => ({ type: ADD_WIDGET, data: { @@ -19,7 +20,7 @@ export const showPlot = ({ path, title }) => ({ config: { instancePaths: [path] }, component: "Plot", - type: "TimeSeries", + name: title || path.slice(FILEVARIABLE_LENGTH), status: WidgetStatus.ACTIVE, panelName: "bottomPanel" @@ -31,7 +32,7 @@ export const addToPlot = ({ hostId, instancePath }) => ({ data: { hostId, config: { instancePath }, - type: "TimeSeries" + component: "Plot" } }); @@ -39,9 +40,7 @@ export const plotAll = ({ plots, title }) => ({ type: ADD_WIDGET, data: { id: `plot@${plots.join("-")}`, - component: "Plot", - type: "TimeSeries", name: title, status: WidgetStatus.ACTIVE, panelName: "bottomPanel", @@ -55,7 +54,7 @@ export const showImageSeries = ({ path, showDetail }) => ({ id: `img@${path}`, component: "ImageSeries", - type: "ImageSeries", + name: path.slice(FILEVARIABLE_LENGTH), status: WidgetStatus.ACTIVE, panelName: "bottomPanel", diff --git a/webapp/redux/middleware/nwbMiddleware.js b/webapp/redux/middleware/nwbMiddleware.js index 007840e..f14b7b8 100644 --- a/webapp/redux/middleware/nwbMiddleware.js +++ b/webapp/redux/middleware/nwbMiddleware.js @@ -32,6 +32,8 @@ import { NOTEBOOK_READY, notebookReady } from "../actions/notebook"; import { WidgetStatus } from "@metacell/geppetto-meta-client/common/layout/model"; import { getNotebookPath } from "../../services/NotebookService"; +import { Layout } from "@metacell/geppetto-meta-ui/flex-layout/src"; +import { call } from "file-loader"; export const DEFAULT_WIDGETS = { python: { @@ -87,20 +89,24 @@ export async function resolveImportValue (typePath, callback) { } -function handleShowWidget (store, next, action) { +function handleShowWidget (store, next, action, callback) { // const instance = Instances.getInstance(path); - if (action.data.type === "TimeSeries") { + if (action.data.component === "Plot") { // Instances.getInstance(path).getType().wrappedObj.name - return handlePlotTimeseries(store, next, action); + return handlePlotTimeseries(store, next, action, callback); } - if (action.data.type === "ImageSeries") { + if (action.data.component === "ImageSeries") { // Instances.getInstance(path).getType().wrappedObj.name action.data.config.showDetail && store.dispatch(updateDetailsWidget(action.data.config.instancePath)); return handleImportTimestamps(store, next, action); } if (action.data.id) { - return next(action); + if (callback) { + callback(); + } + next(action); + } } @@ -136,7 +142,7 @@ function fileLoadedLayout () { return widgets; } -async function handlePlotTimeseries (store, next, action) { +async function handlePlotTimeseries (store, next, action, callback) { // If a set of actions are passed, loop through them and execute each one independently async function retrieveImportValue (data, data_path) { @@ -145,6 +151,7 @@ async function handlePlotTimeseries (store, next, action) { GEPPETTO.ModelFactory.deleteInstance(data); Instances.getInstance(data_path); resolve(); + }); }); } @@ -169,14 +176,22 @@ async function handlePlotTimeseries (store, next, action) { } if (promises.length) { store.dispatch(waitData("Loading timeseries data...", action.type)); - Promise.allSettled(promises).then(() => next(action)); + Promise.allSettled(promises).then(() => { + next(action); + if (callback){ + callback(); + } + }); } else { next(action); + if (callback){ + callback(); + } } } function handleImportTimestamps (store, next, action) { - const time_path = `${action.data.instancePath}.timestamps`; + const time_path = `${action.data.config.instancePath}.timestamps`; const timestamps = Instances.getInstance(time_path); if (timestamps.getValue().resolve == "ImportValue") { @@ -232,8 +247,21 @@ const nwbMiddleware = store => next => action => { case UPDATE_WIDGET: case ADD_WIDGET: - case ADD_PLOT_TO_EXISTING_WIDGET: return handleShowWidget(store, next, action); + + case ADD_PLOT_TO_EXISTING_WIDGET: { + const widgets = store.getState().widgets; + const widget = widgets[action.data.hostId]; + + const instancePaths = [...widget.config.instancePaths, action.data.config.instancePath]; + const newId = 'plot@' + instancePaths.join('-'); + return handleShowWidget(store, next, LayoutActions.addWidget({ + ...widget, + id: newId, + name: widget.name + '+', + config: { ...widget.config, instancePaths } + }), () => next(LayoutActions.deleteWidget(action.data.hostId))); + } case GeppettoActions.backendActions.MODEL_LOADED: next(action); next(nwbFileLoaded()); diff --git a/webapp/redux/reducers/all.js b/webapp/redux/reducers/all.js index faadc2a..766fd9c 100644 --- a/webapp/redux/reducers/all.js +++ b/webapp/redux/reducers/all.js @@ -1,9 +1,8 @@ -import { combineReducers } from 'redux'; - import general from './general'; import nwbfile from './nwbfile'; import notebook from './notebook'; + export default { general, nwbfile, diff --git a/webapp/redux/reducers/flexlayout.js b/webapp/redux/reducers/flexlayout.js deleted file mode 100644 index 4e4c560..0000000 --- a/webapp/redux/reducers/flexlayout.js +++ /dev/null @@ -1,182 +0,0 @@ -import { - ADD_WIDGET, - UPDATE_WIDGET, - RESET_LAYOUT, - DESTROY_WIDGET, - ACTIVATE_WIDGET, - ADD_PLOT_TO_EXISTING_WIDGET, - showList, showAcquisition, showStimulus, showProcessing, showSweeps, showGeneral -} from '../actions/flexlayout'; - -import { NWB_FILE_LOADED } from '../actions/nwbfile' - -import { WidgetStatus } from '../../constants'; - - -function removeUndefined (obj) { - return Object.keys(obj).forEach(key => obj[key] === undefined ? delete obj[key] : ''); -} - -export const FLEXLAYOUT_DEFAULT_STATUS = { - widgets: { - - 'python': { - id: 'python', - name: 'Python', - status: WidgetStatus.MINIMIZED, - icon: 'fa-python', - component: 'PythonConsole', - panelName: "bottomPanel", - enableClose: false - }, - 'general': { - id: 'general', - name: 'General', - status: WidgetStatus.ACTIVE, - panelName: "leftPanel", - enableClose: false - }, - - 'details': { - id: 'details', - name: 'Details', - instancePath: '', - status: WidgetStatus.HIDDEN, - component: 'Metadata', - panelName: "leftPanel", - enableClose: false, - showObjectInfo: true - } - - - }, - -}; - - -export default (state = FLEXLAYOUT_DEFAULT_STATUS, action) => { - if (action.data) { - removeUndefined(action.data); // Prevent deletion in case of unpolished update action - } - - switch (action.type) { - - case ADD_WIDGET: - case UPDATE_WIDGET: { - const newWidget = { ...state.widgets[action.data.id], panelName: extractPanelName(action), ...action.data }; - return { - ...state, widgets: { - ...updateWidgetStatus(state.widgets, newWidget), - [action.data.id]: newWidget - } - } ; - } - - case DESTROY_WIDGET:{ - const newWidgets = { ...state.widgets }; - delete newWidgets[action.data.id]; - return { ...state, widgets: newWidgets }; - } - - case ACTIVATE_WIDGET: { - const activatedWidget = state.widgets[action.data.id]; - if (state.widgets['details'].panelName == activatedWidget.panelName) { - return state; - } - const newDetails = activatedWidget.instancePath - ? { - ...state.widgets['details'], - instancePath: state.widgets[action.data.id].instancePath - } : state.widgets['details']; // We always show the meta data of currently selected widget - return { - ...state, widgets: { - ...updateWidgetStatus(state.widgets, { panelName: state.widgets[action.data.id], status: WidgetStatus.ACTIVE }), - details: newDetails, - [action.data.id]: { ...activatedWidget, status: WidgetStatus.ACTIVE } - } - } - } - - case RESET_LAYOUT: - return FLEXLAYOUT_DEFAULT_STATUS; - - case ADD_PLOT_TO_EXISTING_WIDGET: { - const widget = { ...state.widgets[action.data.hostId] }; - const widgets = { ...state.widgets }; - delete widgets[action.data.hostId]; - - widget.instancePaths.push(action.data.instancePath); - const newId = 'plot@' + widget.instancePaths.join('-'); - if (widget){ - return { - widgets: { - ...updateWidgetStatus(widgets, { panelName: widget.panelName, status: WidgetStatus.ACTIVE }), - [newId]: { - ...widget, - id: newId, - name: widget.name + '+', - } - } - } - } - - - return state - } - - case NWB_FILE_LOADED: - return { widgets: { ...state.widgets, ...fileLoadedLayout() } }; - - default: - return state - } -} - -function filterWidgets (widgets, filterFn) { - return Object.fromEntries(Object.values(widgets).filter(filterFn)); -} - -/** - * Ensure there is one only active widget in the same panel - * @param {*} widgets - * @param {*} param1 - */ -function updateWidgetStatus (widgets, { status, panelName }) { - if (status != WidgetStatus.ACTIVE) { - return widgets; - } - return Object.fromEntries(Object.values(widgets).filter(widget => widget).map(widget => [ - widget.id, - { - ...widget, - status: widget.panelName == panelName ? WidgetStatus.HIDDEN : widget.status - } - ])); -} - -function extractPanelName (action) { - return action.data.component == "Plot" ? "bottomPanel" : "leftPanel"; -} - - -function fileLoadedLayout () { - const widgets = { [showGeneral.data.id]: showGeneral.data }; - - if (Instances.getInstance('nwbfile.stimulus') && Instances.getInstance('nwbfile.stimulus').getType().getVariables().length) { - widgets[showStimulus.data.id] = showStimulus.data; - } - - if (Instances.getInstance('nwbfile.acquisition')) { - widgets[showAcquisition.data.id] = showAcquisition.data; - } - - if (Instances.getInstance('nwbfile.sweep_table')) { - widgets[showSweeps.data.id] = showSweeps.data; - } - - if (Instances.getInstance('nwbfile.processing') && Instances.getInstance('nwbfile.processing').getType().getVariables().length) { - widgets[showProcessing.data.id] = showProcessing.data; - } - return widgets; -} - \ No newline at end of file diff --git a/webapp/redux/reducers/nwbfile.js b/webapp/redux/reducers/nwbfile.js index f914082..8b0747b 100644 --- a/webapp/redux/reducers/nwbfile.js +++ b/webapp/redux/reducers/nwbfile.js @@ -51,9 +51,9 @@ export default (state = {}, action) => { return { ...state, modelSettings: { ...state.modelSettings, [action.data.path]: { ...action.data } } }; } case LayoutActions.layoutActions.ADD_WIDGET: { - if (action.data.instancePaths && action.data.type === 'TimeSeries') { + if (action.data.config?.instancePaths && action.data.component === 'Plot') { const modelSettings = { ...state.modelSettings }; - for (const path of action.data.instancePaths) { + for (const path of action.data.config.instancePaths) { if (!state.modelSettings[path]) { const color = nextColor(); modelSettings[path] = { color }; @@ -64,7 +64,7 @@ export default (state = {}, action) => { return state; } case LayoutActions.layoutActions.ADD_PLOT_TO_EXISTING_WIDGET: { - const path = action.data.instancePath; + const path = action.data.config.instancePath; if (!state.modelSettings[path]) { const modelSettings = { ...state.modelSettings }; const color = nextColor(); diff --git a/webapp/redux/reducers/widgets.js b/webapp/redux/reducers/widgets.js index cda494a..deb7f27 100644 --- a/webapp/redux/reducers/widgets.js +++ b/webapp/redux/reducers/widgets.js @@ -63,8 +63,8 @@ export default (state = {}, action) => { const widgets = { ...state.widgets }; delete widgets[action.data.hostId]; - widget.instancePaths.push(action.data.instancePath); - const newId = `plot@${widget.instancePaths.join('-')}`; + widget.config.instancePaths.push(action.data.config.instancePath); + const newId = `plot@${widget.config.instancePaths.join('-')}`; if (widget) { return { widgets: { diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 5b4b600..fc537bf 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -1201,10 +1201,10 @@ prop-types "^15.7.2" react-is "^16.8.0 || ^17.0.0" -"@metacell/geppetto-meta-client@^2.0.0-rc0": - version "2.0.0-rc0" - resolved "https://registry.yarnpkg.com/@metacell/geppetto-meta-client/-/geppetto-meta-client-2.0.0-rc0.tgz#e18fc527cf1af17b3e37b3e394b6bed73434d878" - integrity sha512-Fm9YHLW461FqFfmov1tg9R5HG0Tg9R6oRxilUxNnwxvtaFYwiVLyNGfergyJt528IiDiUOUmXo53R5iKE5KMUw== +"@metacell/geppetto-meta-client@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@metacell/geppetto-meta-client/-/geppetto-meta-client-1.2.4.tgz#ba79df8849f52879d76245f9ea1b3ea0cabec9f3" + integrity sha512-CSwzeo8JwTZKT4TspyC3i6CU3FZp9XSprTPXN5rM7iIG6yOTN3WaEkgYTKuPM7dD8OvAEAQ24dJPK+fPDx3xyQ== dependencies: "@material-ui/core" "^4.1.3" pako "^1.0.3" @@ -1214,15 +1214,15 @@ redux "^4.1.0" url-join "^4.0.0" -"@metacell/geppetto-meta-core@^2.0.0-rc0": - version "2.0.0-rc0" - resolved "https://registry.yarnpkg.com/@metacell/geppetto-meta-core/-/geppetto-meta-core-2.0.0-rc0.tgz#823fd8aa612633e9184e58c77cecd07b9849d452" - integrity sha512-c104DE3CZRyw+TUjqgD1uX/+CY//6Hc1Inj4gMjzuSoZ7SkdcvxYoE+GLg7T9dzd9xLLU11sLCX+S7scMogVzg== +"@metacell/geppetto-meta-core@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@metacell/geppetto-meta-core/-/geppetto-meta-core-1.2.4.tgz#3504817e35ef05e95909c39a76bbbe7616a1496e" + integrity sha512-2JfJm//3Lool1+X+LVVqGgw8O7YgSL1MCbRAsOzOYNdmbD1LUVDL2MdoMs0fdW5IilazxOAoEulWxyjBB5B7YQ== -"@metacell/geppetto-meta-ui@^2.0.0-rc0": - version "2.0.0-rc0" - resolved "https://registry.yarnpkg.com/@metacell/geppetto-meta-ui/-/geppetto-meta-ui-2.0.0-rc0.tgz#2a40d6c91f56f3db57952d7f2aeb49763399c9c7" - integrity sha512-hb55WyCPQnzZ6UWS08Vfj85D6oRp7XRvsFxTTk6DVJh3DBWNaLPt4X56bVZ5GuK1N2cux9wKYskPWCZEySex3g== +"@metacell/geppetto-meta-ui@1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@metacell/geppetto-meta-ui/-/geppetto-meta-ui-1.2.4.tgz#fa062f37954e618524f710a6bb67029a698d6fdd" + integrity sha512-AVVcpflEr7Ad5S4HjHSSPgbT/pDltOjgtgRensNIUo7cqS1cHXdWqHK8g3mY5nBeJBoj/40KSyVtZhwCQIe+Vg== "@plotly/d3-sankey-circular@0.33.1": version "0.33.1" diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..fb57ccd --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +