From 5afbe2b0c17c3abefed79e9b176823defa3aea79 Mon Sep 17 00:00:00 2001 From: Brad Date: Mon, 19 Feb 2024 07:26:57 +1100 Subject: [PATCH] Bump perspective version to 2.8.0 (#5722) --- examples/reference/panes/Perspective.ipynb | 2 +- panel/_templates/js_resources.html | 2 +- panel/compiler.py | 12 +++++-- panel/io/resources.py | 4 +++ panel/models/perspective.py | 41 +++++++++++++--------- panel/models/perspective.ts | 12 +++---- panel/pane/perspective.py | 19 ++++++---- panel/tests/ui/pane/test_perspective.py | 3 +- 8 files changed, 60 insertions(+), 35 deletions(-) diff --git a/examples/reference/panes/Perspective.ipynb b/examples/reference/panes/Perspective.ipynb index 6b87f493db..0b3e09889e 100644 --- a/examples/reference/panes/Perspective.ipynb +++ b/examples/reference/panes/Perspective.ipynb @@ -41,7 +41,7 @@ "* **``selectable``** (bool, default=True): Whether rows are selectable\n", "* **``sort``** (list): List of sorting specs, e.g. `[[\"x\", \"desc\"]]`\n", "* **``split_by``** (list): A list of columns to pivot by. e.g. `[\"x\", \"y\"]`\n", - "* **``theme``** (str): The theme of the viewer, available options include `'material'`, `'material-dark'`, `'monokai'`, `'solarized'`, `'solarized-dark'` and `'vaporwave'`\n", + "* **``theme``** (str): The theme of the viewer, available options include `'pro'`, `'pro-dark'`, `'monokai'`, `'solarized'`, `'solarized-dark'` and `'vaporwave'`\n", "* **``toggle_config``** (bool): Whether to show the config menu. Default is True.\n", "\n", "##### Callbacks\n", diff --git a/panel/_templates/js_resources.html b/panel/_templates/js_resources.html index 2a616d144f..efc5680f22 100644 --- a/panel/_templates/js_resources.html +++ b/panel/_templates/js_resources.html @@ -25,7 +25,7 @@ {%- for name, file in js_module_exports.items() %} {%- endfor %} diff --git a/panel/compiler.py b/panel/compiler.py index 0627047042..fb1096d745 100644 --- a/panel/compiler.py +++ b/panel/compiler.py @@ -66,7 +66,7 @@ def write_bundled_files(name, files, explicit_dir=None, ext=None): filename = str(filename) if ext and not str(filename).endswith(ext): filename += f'.{ext}' - if filename.endswith('.ttf'): + if filename.endswith(('.ttf', '.wasm')): with open(filename, 'wb') as f: f.write(response.content) else: @@ -250,11 +250,17 @@ def bundle_models(verbose=False, external=True): continue if verbose: print(f'Collecting {name} resources') - prev_jsfiles = getattr(model, '__javascript_raw__', None) + prev_jsfiles = ( + getattr(model, '__javascript_raw__', []) + + getattr(model, '__javascript_modules_raw__', []) + ) or None prev_jsbundle = getattr(model, '__tarball__', None) prev_cls = model for cls in model.__mro__[1:]: - jsfiles = getattr(cls, '__javascript_raw__', None) + jsfiles = ( + getattr(cls, '__javascript_raw__', []) + + getattr(cls, '__javascript_modules_raw__', []) + ) or None if ((jsfiles is None and prev_jsfiles is not None) or (jsfiles is not None and jsfiles != prev_jsfiles)): if prev_jsbundle: diff --git a/panel/io/resources.py b/panel/io/resources.py index 169ad3772a..b7d42137c0 100644 --- a/panel/io/resources.py +++ b/panel/io/resources.py @@ -751,6 +751,10 @@ def js_modules(self): from ..reactive import ReactiveHTML modules = list(config.js_modules.values()) + for model in Model.model_class_reverse_map.values(): + if hasattr(model, '__javascript_modules__'): + modules.extend(model.__javascript_modules__) + self.extra_resources(modules, '__javascript_modules__') if config.design: design_resources = config.design().resolve_resources( diff --git a/panel/models/perspective.py b/panel/models/perspective.py index 58574326d9..b513772bef 100644 --- a/panel/models/perspective.py +++ b/panel/models/perspective.py @@ -1,5 +1,5 @@ from bokeh.core.properties import ( - Any, Bool, Dict, Either, Enum, Instance, List, Null, Nullable, String, + Any, Bool, Dict, Either, Enum, Instance, List, Null, String, ) from bokeh.events import ModelEvent from bokeh.models import ColumnDataSource @@ -10,10 +10,11 @@ from .layout import HTMLBox PERSPECTIVE_THEMES = [ - 'material', 'material-dark', 'monokai', 'solarized', 'solarized-dark', 'vaporwave' + 'monokai', 'solarized', 'solarized-dark', 'vaporwave', 'dracula', + 'pro', 'pro-dark', 'gruvbox', 'gruvbox-dark', ] -PERSPECTIVE_VERSION = '1.9.3' +PERSPECTIVE_VERSION = '2.8.0' THEME_PATH = f"@finos/perspective-viewer@{PERSPECTIVE_VERSION}/dist/css/" THEME_URL = f"{config.npm_cdn}/{THEME_PATH}" @@ -51,7 +52,7 @@ class Perspective(HTMLBox): columns = Either(List(Either(String, Null)), Null()) - expressions = Nullable(List(String)) + expressions = Either(Dict(String, Any), Null()) editable = Bool(default=True) @@ -73,26 +74,34 @@ class Perspective(HTMLBox): toggle_config = Bool(True) - theme = Enum(*PERSPECTIVE_THEMES, default="material") + theme = Enum(*PERSPECTIVE_THEMES, default="pro") - # pylint: disable=line-too-long - __javascript__ = [ - f"{config.npm_cdn}/@finos/perspective@{PERSPECTIVE_VERSION}/dist/umd/perspective.js", - f"{config.npm_cdn}/@finos/perspective-viewer@{PERSPECTIVE_VERSION}/dist/umd/perspective-viewer.js", - f"{config.npm_cdn}/@finos/perspective-viewer-datagrid@{PERSPECTIVE_VERSION}/dist/umd/perspective-viewer-datagrid.js", - f"{config.npm_cdn}/@finos/perspective-viewer-d3fc@{PERSPECTIVE_VERSION}/dist/umd/perspective-viewer-d3fc.js", + __javascript_module_exports__ = ['perspective'] + + __javascript_modules_raw__ = [ + f"{config.npm_cdn}/@finos/perspective@{PERSPECTIVE_VERSION}/dist/cdn/perspective.js", + f"{config.npm_cdn}/@finos/perspective@{PERSPECTIVE_VERSION}/dist/cdn/perspective.worker.js", + f"{config.npm_cdn}/@finos/perspective@{PERSPECTIVE_VERSION}/dist/cdn/perspective.cpp.wasm", + f"{config.npm_cdn}/@finos/perspective-viewer@{PERSPECTIVE_VERSION}/dist/cdn/perspective-viewer.js", + f"{config.npm_cdn}/@finos/perspective-viewer@{PERSPECTIVE_VERSION}/dist/cdn/perspective_bg.wasm", + f"{config.npm_cdn}/@finos/perspective-viewer-datagrid@{PERSPECTIVE_VERSION}/dist/cdn/perspective-viewer-datagrid.js", + f"{config.npm_cdn}/@finos/perspective-viewer-d3fc@{PERSPECTIVE_VERSION}/dist/cdn/perspective-viewer-d3fc.js", ] + @classproperty + def __javascript_modules__(cls): + return [js for js in bundled_files(cls, 'javascript_modules') if 'wasm' not in js and 'worker' not in js] + __js_skip__ = { - "perspective": __javascript__, + "perspective": __javascript_modules__, } __js_require__ = { "paths": { - "perspective": f"{config.npm_cdn}/@finos/perspective@{PERSPECTIVE_VERSION}/dist/umd/perspective", - "perspective-viewer": f"{config.npm_cdn}/@finos/perspective-viewer@{PERSPECTIVE_VERSION}/dist/umd/perspective-viewer", - "perspective-viewer-datagrid": f"{config.npm_cdn}/@finos/perspective-viewer-datagrid@{PERSPECTIVE_VERSION}/dist/umd/perspective-viewer-datagrid", - "perspective-viewer-d3fc": f"{config.npm_cdn}/@finos/perspective-viewer-d3fc@{PERSPECTIVE_VERSION}/dist/umd/perspective-viewer-d3fc", + "perspective": f"{config.npm_cdn}/@finos/perspective@{PERSPECTIVE_VERSION}/dist/cdn/perspective", + "perspective-viewer": f"{config.npm_cdn}/@finos/perspective-viewer@{PERSPECTIVE_VERSION}/dist/cdn/perspective-viewer", + "perspective-viewer-datagrid": f"{config.npm_cdn}/@finos/perspective-viewer-datagrid@{PERSPECTIVE_VERSION}/dist/cdn/perspective-viewer-datagrid", + "perspective-viewer-d3fc": f"{config.npm_cdn}/@finos/perspective-viewer-d3fc@{PERSPECTIVE_VERSION}/dist/cdn/perspective-viewer-d3fc", }, "exports": { "perspective": "perspective", diff --git a/panel/models/perspective.ts b/panel/models/perspective.ts index f04fed58af..8d1d09e16d 100644 --- a/panel/models/perspective.ts +++ b/panel/models/perspective.ts @@ -6,8 +6,8 @@ import {HTMLBox, HTMLBoxView, set_size} from "./layout" import type {Attrs} from "@bokehjs/core/types" const THEMES: any = { - 'material-dark': 'Material Dark', - 'material': 'Material Light', + 'pro-dark': 'Pro Dark', + 'pro': 'Pro Light', 'vaporwave': 'Vaporwave', 'solarized': 'Solarized', 'solarized-dark': 'Solarized Dark', @@ -200,7 +200,7 @@ export class PerspectiveView extends HTMLBoxView { const props: any = {} for (let option in config) { let value = config[option] - if (value === undefined || (option == 'plugin' && value === "debug") || option === 'settings') + if (value === undefined || (option == 'plugin' && value === "debug") || this.model.properties.hasOwnProperty(option) === undefined) continue if (option === 'filter') option = 'filters' @@ -256,7 +256,7 @@ export namespace Perspective { aggregates: p.Property split_by: p.Property columns: p.Property - expressions: p.Property + expressions: p.Property editable: p.Property filters: p.Property group_by: p.Property @@ -288,7 +288,7 @@ export class Perspective extends HTMLBox { this.define(({Any, Array, Boolean, Ref, Nullable, String}) => ({ aggregates: [ Any, {} ], columns: [ Array(Nullable(String)), [] ], - expressions: [ Nullable(Array(String)), null ], + expressions: [ Any, {} ], split_by: [ Nullable(Array(String)), null ], editable: [ Boolean, true ], filters: [ Nullable(Array(Any)), null ], @@ -300,7 +300,7 @@ export class Perspective extends HTMLBox { toggle_config: [ Boolean, true ], sort: [ Nullable(Array(Array(String))), null ], source: [ Ref(ColumnDataSource), ], - theme: [ String, 'material' ] + theme: [ String, 'pro' ] })) } } diff --git a/panel/pane/perspective.py b/panel/pane/perspective.py index 2f870bd672..45babb0aea 100644 --- a/panel/pane/perspective.py +++ b/panel/pane/perspective.py @@ -29,10 +29,11 @@ from ..model.perspective import PerspectiveClickEvent -DEFAULT_THEME = "material" +DEFAULT_THEME = "pro" THEMES = [ - 'material', 'material-dark', 'monokai', 'solarized', 'solarized-dark', 'vaporwave' + 'material', 'material-dark', 'monokai', 'solarized', 'solarized-dark', + 'vaporwave', 'pro', 'pro-dark' ] class Plugin(Enum): @@ -264,7 +265,7 @@ class Perspective(ModelPane, ReactiveData): :Example: - >>> Perspective(df, plugin='hypergrid', theme='material-dark') + >>> Perspective(df, plugin='hypergrid', theme='pro-dark') """ aggregates = param.Dict(default=None, nested_refs=True, doc=""" @@ -276,7 +277,7 @@ class Perspective(ModelPane, ReactiveData): editable = param.Boolean(default=True, allow_None=True, doc=""" Whether items are editable.""") - expressions = param.List(default=None, nested_refs=True, doc=""" + expressions = param.ClassSelector(class_=(dict, list), default=None, nested_refs=True, doc=""" A list of expressions computing new columns from existing columns. For example [""x"+"index""]""") @@ -310,8 +311,8 @@ class Perspective(ModelPane, ReactiveData): toggle_config = param.Boolean(default=True, doc=""" Whether to show the config menu.""") - theme = param.ObjectSelector(default='material', objects=THEMES, doc=""" - The style of the PerspectiveViewer. For example material-dark""") + theme = param.ObjectSelector(default='pro', objects=THEMES, doc=""" + The style of the PerspectiveViewer. For example pro-dark""") priority: ClassVar[float | bool | None] = None @@ -373,6 +374,8 @@ def _filter_properties(self, properties): def _get_properties(self, doc, source=None): props = super()._get_properties(doc) + if 'theme' in props and 'material' in props['theme']: + props['theme'] = props['theme'].replace('material', 'pro') del props['object'] if props.get('toggle_config'): props['height'] = self.height or 300 @@ -431,6 +434,8 @@ def _process_param_change(self, params): params['stylesheets'] = [ ImportedStyleSheet(url=ss) for ss in css ] + params.get('stylesheets', self.stylesheets) + if 'theme' in params and 'material' in params['theme']: + params['theme'] = params['theme'].replace('material', 'pro') props = super()._process_param_change(params) for p in ('columns', 'group_by', 'split_by'): if props.get(p): @@ -441,6 +446,8 @@ def _process_param_change(self, params): props['filters'] = [[str(col), *args] for col, *args in props['filters']] if props.get('aggregates'): props['aggregates'] = {str(col): agg for col, agg in props['aggregates'].items()} + if isinstance(props.get('expressions'), list): + props['expressions'] = {f'expression_{i}': exp for i, exp in enumerate(props['expressions'])} return props def _as_digit(self, col): diff --git a/panel/tests/ui/pane/test_perspective.py b/panel/tests/ui/pane/test_perspective.py index 1b61828a20..8e3265cc32 100644 --- a/panel/tests/ui/pane/test_perspective.py +++ b/panel/tests/ui/pane/test_perspective.py @@ -34,9 +34,8 @@ def test_perspective_click_event(page): perspective.on_click(lambda e: events.append(e)) serve_component(page, perspective) - page.wait_for_timeout(1000) - page.locator('tr').nth(3).click() + page.locator('.pnx-perspective-viewer').locator('tr').nth(4).locator('td').nth(3).click(force=True) wait_until(lambda: len(events) == 1, page)