diff --git a/vitessce/config.py b/vitessce/config.py index efb5317..977078d 100644 --- a/vitessce/config.py +++ b/vitessce/config.py @@ -469,7 +469,7 @@ class VitessceConfigView: A class to represent a view (i.e. visualization component) in the Vitessce view config layout. """ - def __init__(self, component, coordination_scopes, x, y, w, h): + def __init__(self, component, coordination_scopes, x, y, w, h, uid=None): """ Not meant to be instantiated directly, but instead created and returned by the ``VitessceConfig.add_view()`` method. @@ -489,6 +489,8 @@ def __init__(self, component, coordination_scopes, x, y, w, h): "w": w, "h": h } + if uid is not None: + self.view["uid"] = uid def _to_py_params(self): params_dict = { @@ -496,7 +498,7 @@ def _to_py_params(self): "x": self.view["x"], "y": self.view["y"], "w": self.view["w"], - "h": self.view["h"] + "h": self.view["h"], } # Only include coordination_scopes if there are coordination scopes other than # the coorindation scope for the 'dataset' coordination type. @@ -507,6 +509,8 @@ def _to_py_params(self): } if len(non_dataset_coordination_scopes) > 0: params_dict["coordination_scopes"] = non_dataset_coordination_scopes + if "uid" in self.view: + params_dict["uid"] = self.view["uid"] return params_dict def get_coordination_scope(self, c_type): @@ -1009,7 +1013,7 @@ def get_datasets(self): """ return self.config["datasets"] - def add_view(self, view_type, dataset=None, dataset_uid=None, x=0, y=0, w=1, h=1, mapping=None, coordination_scopes=None, props=None): + def add_view(self, view_type, dataset=None, dataset_uid=None, x=0, y=0, w=1, h=1, mapping=None, coordination_scopes=None, props=None, uid=None): """ Add a view to the config. @@ -1077,7 +1081,7 @@ def add_view(self, view_type, dataset=None, dataset_uid=None, x=0, y=0, w=1, h=1 if coordination_scopes is not None: internal_coordination_scopes.update(coordination_scopes) vcv = VitessceConfigView( - component_str, internal_coordination_scopes, x, y, w, h) + component_str, internal_coordination_scopes, x, y, w, h, uid=uid) # Use the mapping parameter if component is scatterplot and the mapping is not None if mapping is not None: @@ -1737,7 +1741,9 @@ def from_dict(config): raise ValueError( "Multiple datasets are present, so every view must have an explicit dataset coordination scope.") new_view = VitessceConfigView( - c['component'], c_coord_scopes, c['x'], c['y'], c['w'], c['h']) + c['component'], c_coord_scopes, c['x'], c['y'], c['w'], c['h'], + uid=(c["uid"] if "uid" in c else None) + ) if 'props' in c.keys(): new_view.set_props(**c['props']) vc.config['layout'].append(new_view) diff --git a/vitessce/widget.py b/vitessce/widget.py index 4b34473..7cab18e 100644 --- a/vitessce/widget.py +++ b/vitessce/widget.py @@ -204,6 +204,9 @@ def get_uid_str(uid): const storeUrls = view.model.get('store_urls'); const invokeTimeout = view.model.get('invoke_timeout'); + const pageMode = view.model.get('page_mode'); + const pageEsm = view.model.get('page_esm'); + const pkgName = (jsDevMode ? "@vitessce/dev" : "vitessce"); importMap.imports["vitessce"] = (customJsUrl.length > 0 @@ -217,8 +220,10 @@ def get_uid_str(uid): PluginViewType, PluginCoordinationType, PluginJointFileType, + PluginAsyncFunction, z, useCoordination, + usePageModeView, useGridItemSize, // TODO: names and function signatures are subject to change for the following functions // Reference: https://github.com/keller-mark/use-coordination/issues/37#issuecomment-1946226827 @@ -234,6 +239,7 @@ def get_uid_str(uid): let pluginCoordinationTypes = []; let pluginFileTypes = []; let pluginJointFileTypes = []; + let pluginAsyncFunctions = []; const stores = Object.fromEntries( storeUrls.map(storeUrl => ([ @@ -263,12 +269,13 @@ def get_uid_str(uid): const pluginModule = (await import(pluginEsmUrl)).default; URL.revokeObjectURL(pluginEsmUrl); - const pluginsObj = await pluginModule.createPlugins({ + const pluginDeps = { React, PluginFileType, PluginViewType, PluginCoordinationType, PluginJointFileType, + PluginAsyncFunction, z, invokeCommand: invokePluginCommand, useCoordination, @@ -279,7 +286,8 @@ def get_uid_str(uid): useComplexCoordinationSecondary, useCoordinationScopes, useCoordinationScopesBy, - }); + }; + const pluginsObj = await pluginModule.createPlugins(pluginDeps); if(Array.isArray(pluginsObj.pluginViewTypes)) { pluginViewTypes = [...pluginViewTypes, ...pluginsObj.pluginViewTypes]; } @@ -292,7 +300,28 @@ def get_uid_str(uid): if(Array.isArray(pluginsObj.pluginJointFileTypes)) { pluginJointFileTypes = [...pluginJointFileTypes, ...pluginsObj.pluginJointFileTypes]; } + if(Array.isArray(pluginsObj.pluginAsyncFunctions)) { + pluginAsyncFunctions = [...pluginAsyncFunctions, ...pluginsObj.pluginAsyncFunctions]; + } + } catch(e) { + console.error("Error loading plugin ESM or executing createPlugins function."); + console.error(e); + } + } + + let PageComponent; + if(pageMode && pageEsm.length > 0) { + try { + const pageEsmUrl = URL.createObjectURL(new Blob([pageEsm], { type: "text/javascript" })); + const pageModule = (await import(pageEsmUrl)).default; + URL.revokeObjectURL(pageEsmUrl); + + const pageDeps = { + usePageModeView, + }; + PageComponent = await pageModule.createPage(pageDeps); } catch(e) { + console.error("Error loading page ESM or executing createPage function.") console.error(e); } } @@ -360,14 +389,17 @@ def get_uid_str(uid): const vitessceProps = { height, theme, config, onConfigChange, validateConfig, - pluginViewTypes, pluginCoordinationTypes, pluginFileTypes, pluginJointFileTypes, - remountOnUidChange, stores, + pluginViewTypes, pluginCoordinationTypes, + pluginFileTypes,pluginJointFileTypes, pluginAsyncFunctions, + remountOnUidChange, stores, pageMode, }; return e('div', { ref: divRef, style: { height: height + 'px' } }, e(React.Suspense, { fallback: e('div', {}, 'Loading...') }, e(React.StrictMode, {}, - e(Vitessce, vitessceProps) + e(Vitessce, vitessceProps, + (pageMode ? e(PageComponent, {}) : null) + ), ), ), ); @@ -401,6 +433,7 @@ def get_uid_str(uid): PluginViewType, PluginCoordinationType, PluginJointFileType, + PluginAsyncFunction, z, useCoordination, invokeCommand, @@ -410,11 +443,32 @@ def get_uid_str(uid): pluginFileTypes: undefined, pluginCoordinationTypes: undefined, pluginJointFileTypes: undefined, + pluginAsyncFunctions: undefined, }; } export default { createPlugins }; """ +DEFAULT_PAGE_ESM = """ +function createPage(utilsForPage) { + const { + usePageModeView, + } = utilsForPage; + + function PageComponent(props) { + const BiomarkerSelect = usePageModeView('biomarker-select'); + + return ( +