Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: First pass of ui.table functionality #95

Merged
merged 3 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions plugins/ui/src/deephaven/ui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from deephaven.plugin import Registration, Callback
from .components import *
from .elements import *
from .hooks import *
from .object_types import *

Expand Down
19 changes: 11 additions & 8 deletions plugins/ui/src/deephaven/ui/_internal/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
def get_component_name(component):
from typing import Any


def get_component_name(component: Any) -> str:
"""
Get the name of the component

Expand All @@ -10,11 +13,11 @@ def get_component_name(component):
"""
try:
return component.__module__ + "." + component.__name__
except Exception as e:
except Exception:
return component.__class__.__module__ + "." + component.__class__.__name__


def get_component_qualname(component):
def get_component_qualname(component: Any) -> str:
"""
Get the name of the component

Expand All @@ -26,11 +29,11 @@ def get_component_qualname(component):
"""
try:
return component.__module__ + "." + component.__qualname__
except Exception as e:
except Exception:
return component.__class__.__module__ + "." + component.__class__.__qualname__


def to_camel_case(snake_case_text: str):
def to_camel_case(snake_case_text: str) -> str:
"""
Convert a snake_case string to camelCase.

Expand All @@ -44,7 +47,7 @@ def to_camel_case(snake_case_text: str):
return components[0] + "".join(x.title() for x in components[1:])


def dict_to_camel_case(snake_case_dict: dict):
def dict_to_camel_case(snake_case_dict: dict[str, Any]) -> dict[str, Any]:
"""
Convert a dict with snake_case keys to a dict with camelCase keys.

Expand All @@ -54,13 +57,13 @@ def dict_to_camel_case(snake_case_dict: dict):
Returns:
The camelCase dict.
"""
camel_case_dict = {}
camel_case_dict: dict[str, Any] = {}
for key, value in snake_case_dict.items():
camel_case_dict[to_camel_case(key)] = value
return camel_case_dict


def remove_empty_keys(dict):
def remove_empty_keys(dict: dict[str, Any]) -> dict[str, Any]:
"""
Remove keys from a dict that have a value of None.

Expand Down
4 changes: 4 additions & 0 deletions plugins/ui/src/deephaven/ui/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .icon import icon
from .make_component import make_component as component
from .panel import panel
from .spectrum import *
from .table import table


__all__ = [
Expand All @@ -16,9 +18,11 @@
"icon_wrapper",
"illustrated_message",
"html",
"panel",
"slider",
"spectrum_element",
"switch",
"table",
"text",
"text_field",
"toggle_button",
Expand Down
2 changes: 1 addition & 1 deletion plugins/ui/src/deephaven/ui/components/make_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

def make_component(func):
"""
Create a ComponentNode from the passed in function.
Create a FunctionalElement from the passed in function.
"""

@functools.wraps(func)
Expand Down
12 changes: 12 additions & 0 deletions plugins/ui/src/deephaven/ui/components/panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from ..elements import BaseElement, Element


def panel(*children: Element, title: str | None = None):
"""
A panel is a container that can be used to group elements.

Args:
children: Elements to render in the panel.
title: Title of the panel.
"""
return BaseElement("deephaven.ui.components.Panel", *children, title=title)
9 changes: 9 additions & 0 deletions plugins/ui/src/deephaven/ui/components/table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from deephaven.table import Table
from ..elements import UITable


def table(table: Table) -> UITable:
"""
Add some extra methods to the Table class for giving hints to displaying a table
"""
return UITable(table)
3 changes: 2 additions & 1 deletion plugins/ui/src/deephaven/ui/elements/BaseElement.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Any
from .Element import Element
from .._internal import dict_to_camel_case, RenderContext

Expand All @@ -8,7 +9,7 @@ class BaseElement(Element):
Must provide a name for the element.
"""

def __init__(self, name: str, *children, **props):
def __init__(self, name: str, *children: Element, **props: Any):
self._name = name
if len(children) > 0 and props.get("children") is not None:
raise ValueError("Cannot provide both children and props.children")
Expand Down
3 changes: 2 additions & 1 deletion plugins/ui/src/deephaven/ui/elements/Element.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from typing import Any
from .._internal import RenderContext


Expand All @@ -18,7 +19,7 @@ def name(self) -> str:
return "deephaven.ui.Element"

@abstractmethod
def render(self, context: RenderContext) -> dict:
def render(self, context: RenderContext) -> dict[str, Any]:
"""
Renders this element, and returns the result as a dictionary of props for the element.
If you just want to render children, pass back a dict with children only, e.g. { "children": ... }
Expand Down
58 changes: 58 additions & 0 deletions plugins/ui/src/deephaven/ui/elements/UITable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import logging
from typing import Any, Callable
from deephaven.table import Table
from .Element import Element
from .._internal import dict_to_camel_case, RenderContext

logger = logging.getLogger(__name__)

RowIndex = int
RowDataMap = dict[str, Any]


class UITable(Element):
"""
Wrap a Table with some extra props for giving hints to displaying a table
"""

_table: Table
"""
The table that is wrapped with some extra props
"""

_props: dict[str, Any]
"""
The extra props that are added by each method
"""

def __init__(self, table: Table, props: dict[str, Any] = {}):
"""
Create a UITable from the passed in table. UITable provides an [immutable fluent interface](https://en.wikipedia.org/wiki/Fluent_interface#Immutability) for adding UI hints to a table.

Args:
table: The table to wrap
"""
self._table = table

# Store the extra props that are added by each method
self._props = props

@property
def name(self):
return "deephaven.ui.elements.UITable"

def _with_prop(self, key: str, value: Any) -> "UITable":
logger.debug("_with_prop(%s, %s)", key, value)
return UITable(self._table, {**self._props, key: value})

def render(self, context: RenderContext) -> dict[str, Any]:
logger.debug("Returning props %s", self._props)
return dict_to_camel_case({**self._props, "table": self._table})

def on_row_double_press(
self, callback: Callable[[int, dict[str, Any]], None]
) -> "UITable":
"""
Add a callback to be invoked when a row is double-clicked.
"""
return self._with_prop("on_row_double_press", callback)
7 changes: 2 additions & 5 deletions plugins/ui/src/deephaven/ui/elements/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
from .Element import Element
from .BaseElement import BaseElement
from .FunctionElement import FunctionElement
from .UITable import UITable

__all__ = [
"BaseElement",
"Element",
"FunctionElement",
]
__all__ = ["BaseElement", "Element", "FunctionElement", "UITable"]
46 changes: 46 additions & 0 deletions plugins/ui/test/deephaven/ui/test_ui_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import annotations
import typing
from typing import Any
from .BaseTest import BaseTestCase


if typing.TYPE_CHECKING:
from deephaven.ui import UITable


class UITableTestCase(BaseTestCase):
def setUp(self) -> None:
from deephaven import empty_table

self.source = empty_table(100).update(["X = i", "Y = i * 2"])

def expect_render(self, ui_table: UITable, expected_props: dict[str, Any]):
from deephaven.ui._internal import RenderContext

context = RenderContext()
result = ui_table.render(context)

self.assertDictEqual(result, expected_props)

def test_empty_ui_table(self):
import deephaven.ui as ui

t = ui.table(self.source)

self.expect_render(t, {"table": self.source})

def test_on_row_double_press(self):
import deephaven.ui as ui

def callback(row):
pass

t = ui.table(self.source).on_row_double_press(callback)

self.expect_render(
t,
{
"table": self.source,
"onRowDoublePress": callback,
},
)