Skip to content

Commit

Permalink
Patch to make tabulator work offline in "pn.config.inline" mode
Browse files Browse the repository at this point in the history
- notebook `render_mimebundle` defaults to resources=inline when pn.config.inline=True instead of always using cdn
- In this mode, we patch the doc json to replace ImportedStyleSheet with InlineStyleSheet
- resources.py patches loading of inlined js file to make require defines work in cases where the define() call has the module name not specified
- layout.ts: watch_stylesheets() makes sure to call "style_redraw()" in case no css link needs to be loaded. This is done after a timeout since some models (eg Tabulator) aren't completely setup when the watch_stylesheets() call is made.
  • Loading branch information
Pankaj Pandey committed Mar 29, 2024
1 parent d59b23b commit 610976f
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 4 deletions.
37 changes: 35 additions & 2 deletions panel/io/notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from .embed import embed_state
from .model import add_to_doc, diff
from .resources import (
PANEL_DIR, Resources, _env, bundle_resources, patch_model_css,
PANEL_DIR, Resources, _env, bundle_resources, patch_model_css, CDN_DIST, DIST_DIR
)
from .state import state

Expand Down Expand Up @@ -184,6 +184,32 @@ def render_template(
)
return ({'text/html': html, EXEC_MIME: ''}, {EXEC_MIME: {'id': ref}})


def _visit_json_data(data, parent=None, key=None):
if parent is not None:
yield data, parent, key
typ = type(data)
if typ is dict:
for k, v in data.items():
yield from _visit_json_data(v, data, k)
elif typ in {list, tuple, set}:
for i, v in enumerate(data):
yield from _visit_json_data(v, data, i)


def patch_inline_css(doc_data):
for data, parent, key in _visit_json_data(doc_data):
if type(data) is dict:
if 'type' in data and 'name' in data and data['name'] == 'ImportedStyleSheet' and data['type'] == 'object':
url = data['attributes']['url']
if url.startswith(CDN_DIST):
path = DIST_DIR / url.replace(CDN_DIST, '')
if path.exists():
css = path.read_text(encoding='utf-8')
data['name'] = 'InlineStyleSheet'
data['attributes']['css'] = css
del data['attributes']['url']

def render_model(
model: 'Model', comm: Optional['Comm'] = None, resources: str = 'cdn'
) -> Tuple[Dict[str, str], Dict[str, Dict[str, str]]]:
Expand All @@ -200,6 +226,10 @@ def render_model(
model.document._template_variables['dist_url'] = dist_url

(docs_json, [render_item]) = standalone_docs_json_and_render_items([model], suppress_callback_warning=True)

if resources == 'inline':
patch_inline_css(docs_json)

div = div_for_render_item(render_item)
render_json = render_item.to_json()
requirements = [pnext._globals[ext] for ext in pnext._loaded_extensions
Expand Down Expand Up @@ -239,12 +269,15 @@ def render_mimebundle(
model: 'Model', doc: 'Document', comm: 'Comm',
manager: Optional['CommManager'] = None,
location: Optional['Location'] = None,
resources: str = 'cdn'
resources: str = None
) -> Tuple[Dict[str, str], Dict[str, Dict[str, str]]]:
"""
Displays bokeh output inside a notebook using the PyViz display
and comms machinery.
"""
if resources is None:
from ..config import config
resources = 'inline' if config.inline else 'cdn'
# WARNING: Patches the client comm created by some external library
# e.g. HoloViews, with an on_open handler that will initialize
# the server comm.
Expand Down
53 changes: 52 additions & 1 deletion panel/io/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,8 +816,59 @@ def js_raw(self):
# Inline local dist resources
js_files = self._collect_external_resources("__javascript__")
self.extra_resources(js_files, '__javascript__')
skip_import_map = None
inlined_requires = {}
def get_inlined_js(url):
nonlocal skip_import_map
if skip_import_map is None:
from .notebook import require_components
_, _, exports, skip_imports = require_components()
export_to_module_map = {v:k for k,v in exports.items()}
skip_import_map = {}
for k, js_files in skip_imports.items():
for js_file in js_files:
skip_import_map[js_file] = k, export_to_module_map.get(k, k)
path = DIST_DIR / url.replace(CDN_DIST, '')
js = path.read_text(encoding='utf-8')
if url in skip_import_map:
export, modname = skip_import_map[url]
inlined_requires[modname] = export
js = '''
//console.log('in inlined script');
let define_orig = this.define;
const fnthis = this;
const fndefines = [];
this.define = function define(...args){
//console.log('define args:', args)
if (typeof args[0] == "string" ) {
return define_orig.apply(fnthis, args);
} else {
fndefines.push(%(modname)r);
//console.log('defining %(modname)s');
return define_orig.apply(fnthis, [%(modname)r].concat(args));
}
};
this.define.amd = define_orig.amd;
try {
//console.log('loading ', %(modname)r, this, this.define);
%(js)s;
} finally {
fnthis.define = define_orig;
//console.log('fndefines:', fndefines);
for (var mod of fndefines) {
require([mod], function(exp){
if (mod == %(modname)r) {
fnthis[%(export)r] = exp;
}
})
}
}
''' % dict(js=js, export=export, modname=modname)
return js

raw_js += [
(DIST_DIR / js.replace(CDN_DIST, '')).read_text(encoding='utf-8')
get_inlined_js(js)
# (DIST_DIR / js.replace(CDN_DIST, '')).read_text(encoding='utf-8')
for js in js_files if is_cdn_url(js)
]

Expand Down
5 changes: 4 additions & 1 deletion panel/models/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class PanelMarkupView extends WidgetView {
}
}
if (Object.keys(this._initialized_stylesheets).length === 0) {
this.style_redraw()
setTimeout(() => {this.style_redraw()}, 1);
}
}

Expand Down Expand Up @@ -175,6 +175,9 @@ export abstract class HTMLBoxView extends LayoutDOMView {
})
}
}
if (Object.keys(this._initialized_stylesheets).length === 0) {
setTimeout(() => {this.style_redraw()}, 1);
}
}

style_redraw(): void {
Expand Down

0 comments on commit 610976f

Please sign in to comment.