@@ -145,7 +145,37 @@ def generate_executor(path: str, token: str, root_url: str) -> str:
145145 execute_template .render (path = path , token = token , root_url = root_url )
146146 )
147147
148- class PanelJupyterHandler (JupyterHandler ):
148+ class PanelBaseHandler (JupyterHandler ):
149+
150+ def initialize (self , ** kwargs ):
151+ super ().initialize (** kwargs )
152+ self .notebook_path = kwargs .pop ('notebook_path' , [])
153+
154+ def nb_path (self , path = None ):
155+ root_dir = get_server_root_dir (self .application .settings )
156+ rel_path = pathlib .Path (self .notebook_path or path )
157+ if rel_path .is_absolute ():
158+ notebook_path = str (rel_path )
159+ else :
160+ notebook_path = str ((root_dir / rel_path ).absolute ())
161+ return pathlib .Path (notebook_path )
162+
163+
164+ class PanelLayoutHandler (PanelBaseHandler ):
165+
166+ @tornado .web .authenticated
167+ async def get (self , path = None ):
168+ nb_path = self .nb_path (path )
169+ layout_path = nb_path .parent / f'.{ nb_path .name } .layout'
170+ if not layout_path .is_file ():
171+ return {}
172+ with open (layout_path , encoding = 'utf-8' ) as f :
173+ layout = json .load (f )
174+ self .set_header ('Content-Type' , 'text/json' )
175+ self .finish (layout )
176+
177+
178+ class PanelJupyterHandler (PanelBaseHandler ):
149179 """
150180 The PanelJupyterHandler expects to be given a path to a notebook,
151181 .py file or Lumen .yaml file. Based on the kernelspec in the
@@ -159,7 +189,6 @@ class PanelJupyterHandler(JupyterHandler):
159189
160190 def initialize (self , ** kwargs ):
161191 super ().initialize (** kwargs )
162- self .notebook_path = kwargs .pop ('notebook_path' , [])
163192 self .kernel_started = False
164193
165194 async def _get_info (self , msg_id , timeout = KERNEL_TIMEOUT ):
@@ -193,13 +222,7 @@ async def _get_info(self, msg_id, timeout=KERNEL_TIMEOUT):
193222
194223 @tornado .web .authenticated
195224 async def get (self , path = None ):
196- root_dir = get_server_root_dir (self .application .settings )
197- rel_path = pathlib .Path (self .notebook_path or path )
198- if rel_path .is_absolute ():
199- notebook_path = str (rel_path )
200- else :
201- notebook_path = str ((root_dir / rel_path ).absolute ())
202-
225+ notebook_path = self .nb_path (path )
203226 if (
204227 self .notebook_path and path
205228 ): # when we are in single notebook mode but have a path
@@ -218,7 +241,7 @@ async def get(self, path=None):
218241 # Provision a kernel with the desired kernelspec
219242 if self .request .arguments .get ('kernel' ):
220243 requested_kernel = self .request .arguments .pop ('kernel' )[0 ].decode ('utf-8' )
221- elif notebook_path .endswith ( '.ipynb' ) :
244+ elif notebook_path .suffix == '.ipynb' :
222245 with open (notebook_path ) as f :
223246 nb = json .load (f )
224247 requested_kernel = nb .get ('metadata' , {}).get ('kernelspec' , {}).get ('name' )
@@ -446,6 +469,8 @@ def _load_jupyter_server_extension(notebook_app):
446469 PanelWSProxy ),
447470 (urljoin (base_url , r"panel-preview/render/(.*)" ),
448471 PanelJupyterHandler , {}),
472+ (urljoin (base_url , r"panel-preview/layout/(.*)" ),
473+ PanelLayoutHandler , {}),
449474 (urljoin (base_url , r"panel_dist/(.*)" ),
450475 StaticFileHandler , dict (path = DIST_DIR )),
451476 (urljoin (base_url , f'panel-preview/{ COMPONENT_PATH } (.*)' ),
0 commit comments