11import importlib .util
22from urllib .parse import quote_plus
33import json
4+ import sys
45
56# Widget dependencies
67import anywidget
@@ -104,7 +105,8 @@ def get_base_url_and_port(port, next_port, proxy=False, base_url=None, host_name
104105
105106 if base_url is None :
106107 if proxy :
107- if importlib .util .find_spec ('jupyter_server_proxy' ) is None :
108+ is_in_workspaces = sys .executable .startswith ('/hive' )
109+ if importlib .util .find_spec ('jupyter_server_proxy' ) is None and not is_in_workspaces :
108110 raise ValueError (
109111 "To use the widget through a proxy, jupyter-server-proxy must be installed." )
110112 if host_name is None :
@@ -167,23 +169,24 @@ def get_uid_str(uid):
167169// which means that the client needs to prepend the part of the URL before /proxy/8000 such as
168170// https://hub.gke2.mybinder.org/user/vitessce-vitessce-python-swi31vcv/proxy/8000/A/0/cells
169171// For workspaces: https://workspaces-pt.hubmapconsortium.org/passthrough/HOSTNAME/PORT/ADDITIONAL_PATH_INFO?QUERY_PARAMS=HELLO_WORLD
170- function prependBaseUrl(config, proxy, hasHostName) {
171- if (!proxy || hasHostName) {
172- return config;
173- }
174- let proxyPath;
175- const { origin } = new URL(window.location.href);
176- const isInWorkspaces = origin.includes(WORKSPACES_URL_KEYWORD)
172+ function prependBaseUrl(config, proxy, hasHostName, currentPort) {
173+ if (!proxy || hasHostName) {
174+ return config;
175+ }
176+ const { origin, pathname } = new URL(window.location.href);
177+ const isInWorkspaces = origin.startsWith(WORKSPACES_URL_KEYWORD);
178+
179+ const localhostBaseUrl = `http://localhost:${currentPort}`;
180+ const jupyterLabConfigEl = document.getElementById('jupyter-config-data');
181+
182+ let baseUrl;
177183 if (isInWorkspaces) {
178- const pathSegments = window.location. pathname.split('/');
184+ const pathSegments = pathname.split('/');
179185 const passthroughIndex = pathSegments.indexOf('passthrough');
180186 if (passthroughIndex !== -1) {
181- proxyPath = pathSegments.slice(0, passthroughIndex + 3 ).join('/');
187+ baseUrl = pathSegments.slice(0, passthroughIndex + 2 ).join('/');
182188 }
183- }
184- const jupyterLabConfigEl = document.getElementById('jupyter-config-data');
185- let baseUrl;
186- if (jupyterLabConfigEl) {
189+ } else if (jupyterLabConfigEl) {
187190 // This is jupyter lab
188191 baseUrl = JSON.parse(jupyterLabConfigEl.textContent || '').baseUrl;
189192 } else {
@@ -195,24 +198,15 @@ def get_uid_str(uid):
195198 datasets: config.datasets.map(d => ({
196199 ...d,
197200 files: d.files.map(f => {
198- // Checks to handle different scenarios of urls presented in workspaces and otherwise, i.e., local vs. remote
199- // For regular urls
200- let constructedUrl = f.url;
201- // if in workspaces, only local data is accessed
202- if (isInWorkspaces && f.url.startsWith(WORKSPACES_URL_KEYWORD)){
203- constructedUrl = `${proxyPath}${baseUrl}${f.url}`;
204- }
205- else if (isInWorkspaces && !f.url.startsWith(WORKSPACES_URL_KEYWORD)){
206- constructedUrl = f.url;
207- }
208- // if local data is accessed in the notebook
209- else if (f.url.startsWith('proxy')) {
210- constructedUrl = `${origin}${baseUrl}${f.url}`;
201+ if (isInWorkspaces && f.url.startsWith('proxy')) {
202+ // When the user is in workspaces, we do not use jupyter_server_proxy.
203+ // Instead, the workspaces infrastructure has its own "passthrough" functionality.
204+ f.url = f.url.replace('proxy', '');
211205 }
212206 return {
213207 ...f,
214- url: constructedUrl ,
215- }
208+ url: `${origin}${baseUrl}${f.url}` ,
209+ };
216210 }),
217211 })),
218212 };
@@ -409,7 +403,7 @@ def get_uid_str(uid):
409403 function VitessceWidget(props) {
410404 const { model } = props;
411405
412- const [config, setConfig] = React.useState(prependBaseUrl(model.get('config'), model.get('proxy'), model.get('has_host_name')));
406+ const [config, setConfig] = React.useState(prependBaseUrl(model.get('config'), model.get('proxy'), model.get('has_host_name'), model.get('current_port') ));
413407 const [validateConfig, setValidateConfig] = React.useState(true);
414408 const height = model.get('height');
415409 const theme = model.get('theme') === 'auto' ? (prefersDark ? 'dark' : 'light') : model.get('theme');
@@ -597,6 +591,7 @@ class VitessceWidget(anywidget.AnyWidget):
597591 page_mode = Bool (False ).tag (sync = True )
598592 page_esm = Unicode ('' ).tag (sync = True )
599593 invoke_timeout = Int (300000 ).tag (sync = True )
594+ current_port = Int (0 ).tag (sync = True )
600595
601596 store_urls = List (trait = Unicode ('' ), default_value = []).tag (sync = True )
602597
@@ -653,7 +648,8 @@ def __init__(self, config, height=600, theme='auto', uid=None, port=None, proxy=
653648 plugin_esm = plugin_esm , remount_on_uid_change = remount_on_uid_change ,
654649 page_mode = page_mode , page_esm = ('' if page_esm is None else page_esm ),
655650 invoke_timeout = invoke_timeout ,
656- uid = uid_str , store_urls = list (self ._stores .keys ())
651+ uid = uid_str , store_urls = list (self ._stores .keys ()),
652+ current_port = use_port
657653 )
658654
659655 # Register chained plugin on_config_change functions with a change observer.
@@ -759,6 +755,7 @@ def ipython_display(config, height=600, theme='auto', base_url=None, host_name=N
759755 "invoke_timeout" : 30000 ,
760756 "proxy" : proxy ,
761757 "has_host_name" : host_name is not None ,
758+ "current_port" : use_port ,
762759 "height" : height ,
763760 "theme" : theme ,
764761 "config" : config_dict ,
0 commit comments