Source code for bowtie.control

"""Control components."""

from typing import Callable, Dict, Optional, Union, Sequence, List

from bowtie._component import Component, jdumps, jsbool, jsnull


# pylint: disable=too-few-public-methods
class _Controller(Component):
    """Abstract class all control components inherit.

    Used to test if a an object is a controller.
    """

    # pylint: disable=abstract-method
    @property
    def _instantiate(self) -> str:
        tagwrap = '{component}' + self._tagbase
        return self._insert(tagwrap, self._comp)


[docs]class Button(_Controller): """An Ant design button.""" _TEMPLATE = 'button.jsx' _COMPONENT = 'SimpleButton' _PACKAGE = None _ATTRS = "label={{'{label}'}}" def __init__(self, label: str = '') -> None: """Create a button. Parameters ---------- label : str, optional Label on the button. """ super().__init__() self._comp = self._tag.format( label=label ) def on_click(self) -> None: """Emit an event when the button is clicked. There is no getter associated with this event. | **Payload:** ``None``. Returns ------- str Name of click event. """
[docs]class Upload(_Controller): """Draggable file upload widget.""" _TEMPLATE = 'upload.jsx' _COMPONENT = 'AntUpload' _PACKAGE = None _ATTRS = "multiple={{{multiple}}}" def __init__(self, multiple=True): """Create the widget. Note: the handler parameter may be changed in the future. Parameters ---------- multiple : bool, optional If true, you can upload multiple files at once. Even with this set to true, the handler will get called once per file uploaded. """ super().__init__() self._comp = self._tag.format( multiple=jsbool(multiple) ) def on_upload(self): """Emit an event when the selection changes. There is no getter associated with this event. | **Payload:** ``tuple`` with a str (name) and BytesIO (stream). The user is responsible for storing the object in this function if they want it for later use. To indicate an error, return True, otherwise a return value of None or False indicate success. """
[docs]class Switch(_Controller): """Toggle switch.""" _TEMPLATE = 'switch.jsx' _COMPONENT = 'Toggle' _PACKAGE = None _ATTRS = 'defaultChecked={{{defaultChecked}}}' def __init__(self, initial: bool = False) -> None: """Create a toggle switch. Parameters ---------- initial : bool, optional Starting state of the switch. """ super().__init__() self._comp = self._tag.format( defaultChecked=jsbool(initial) ) def on_switch(self): """Emit an event when the switch is toggled. | **Payload:** ``bool`` status of the switch. Returns ------- str Name of event. """ return self.get # pylint: disable=no-self-use def get(self, data): """ Get the state of the switch. Returns ------- bool True if the switch is enabled. """ return data
class _DatePickers(_Controller): """Specific Date Pickers inherit this class.""" _TEMPLATE = 'date.jsx' _COMPONENT = 'PickDates' _PACKAGE = None _ATTRS = ('date={{{date_type}}} ' 'month={{{month_type}}} ' 'range={{{range_type}}}') def __init__(self, date_type: bool = False, month_type: bool = False, range_type: bool = False) -> None: super().__init__() self._comp = self._tag.format( date_type=jsbool(date_type), month_type=jsbool(month_type), range_type=jsbool(range_type) )
[docs]class DatePicker(_DatePickers): """A Date Picker. Let's you choose an individual day. """ def __init__(self) -> None: """Create a date picker.""" super().__init__(date_type=True) def on_change(self): """Emit an event when a date is selected. | **Payload:** ``str`` of the form ``"yyyy-mm-dd"``. Returns ------- str Name of event. """ return self.get # pylint: disable=no-self-use def get(self, data): """ Get the currently selected date. Returns ------- str Date in the format "YYYY-MM-DD" """ return data
[docs]class MonthPicker(_DatePickers): """A Month Picker. Let's you choose a month and year. """ def __init__(self) -> None: """Create month picker.""" super().__init__(month_type=True) def on_change(self): """Emit an event when a month is selected. | **Payload:** ``str`` of the form ``"yyyy-mm"``. Returns ------- str Name of event. """ return self.get # pylint: disable=no-self-use def get(self, data): """ Get the currently selected month. Returns ------- str Month in the format "YYYY-MM" """ return data
[docs]class RangePicker(_DatePickers): """A Date Range Picker. Choose two dates to use as a range. """ def __init__(self) -> None: """Create a date range picker.""" super().__init__(range_type=True) def on_change(self): """Emit an event when a range is selected. | **Payload:** ``list`` of two dates ``["yyyy-mm-dd", "yyyy-mm-dd"]``. Returns ------- str Name of event. """ return self.get # pylint: disable=no-self-use def get(self, data): """ Get the currently selected date range. Returns ------- list A list of two strings ``["yyyy-mm-dd", "yyyy-mm-dd"]``. """ return data
[docs]class Number(_Controller): """A number input widget with increment and decrement buttons.""" _TEMPLATE = 'number.jsx' _COMPONENT = 'AntNumber' _PACKAGE = None _ATTRS = ('start={{{start}}} ' 'min={{{minimum}}} ' 'max={{{maximum}}} ' 'step={{{step}}} ' "size={{'{size}'}}") def __init__(self, start: int = 0, minimum: float = -1e100, maximum: float = 1e100, step: int = 1, size: str = 'default') -> None: """Create a number input. Parameters ---------- start : number, optional Starting number minimum : number, optional Lower bound maximum : number, optional Upper bound size : 'default', 'large', 'small', optional Size of the text box. References ---------- https://ant.design/components/input/ """ super().__init__() self._comp = self._tag.format( start=start, minimum=minimum, maximum=maximum, step=step, size=size ) def on_change(self): """Emit an event when the number is changed. | **Payload:** ``number`` Returns ------- str Name of event. """ return self.get # pylint: disable=no-self-use def get(self, data): """ Get the current number. Returns ------- number """ return data
[docs]class Textbox(_Controller): """A single line text box.""" _TEMPLATE = 'textbox.jsx' _COMPONENT = 'Textbox' _PACKAGE = None _ATTRS = ("placeholder={{'{placeholder}'}} " "size={{'{size}'}} " "type={{'{area}'}} " 'autosize={{{autosize}}} ' 'disabled={{{disabled}}}') def __init__(self, placeholder: str = 'Enter text', size: str = 'default', area: bool = False, autosize: bool = False, disabled: bool = False) -> None: """Create a text box. Parameters ---------- placeholder : str, optional Initial text that appears. size : 'default', 'large', 'small', optional Size of the text box. area : bool, optional Create a text area if True else create a single line input. autosize : bool, optional Automatically size the widget based on the content. disabled : bool, optional Disable input to the widget. References ---------- https://ant.design/components/input/ """ super().__init__() self._comp = self._tag.format( area='textarea' if area else 'text', autosize=jsbool(autosize), disabled=jsbool(disabled), placeholder=placeholder, size=size ) # pylint: disable=no-self-use
[docs] def do_text(self, text): """Set the text of the text box. Parameters ---------- text : str String of the text box. """ return text
def on_enter(self): """Emit an event when enter is pressed in the text box. | **Payload:** ``str`` Returns ------- str Name of event. """ return self.get def on_change(self) -> Callable: """Emit an event when the text is changed. | **Payload:** ``str`` Returns ------- str Name of event. """ return self.get # pylint: disable=no-self-use def get(self, data): """ Get the current text. Returns ------- str """ return data
[docs]class Slider(_Controller): """Ant Design slider.""" _TEMPLATE = 'slider.jsx' _COMPONENT = 'AntSlider' _PACKAGE = None _ATTRS = ('range={{{range}}} ' 'min={{{minimum}}} ' 'max={{{maximum}}} ' 'step={{{step}}} ' 'start={{{start}}} ' 'marks={{{marks}}} ' 'vertical={{{vertical}}}') def __init__(self, start: Optional[Union[float, Sequence[float]]] = None, ranged: bool = False, minimum: float = 0, maximum: float = 100, step: float = 1, vertical: bool = False) -> None: """Create a slider. Parameters ---------- start : number or list with two values, optional Determines the starting value. If a list of two values are given it will be a range slider. ranged : bool, optional If this is a range slider. minimum : number, optional Minimum value of the slider. maximum : number, optional Maximum value of the slider. step : number, optional Step size. vertical : bool, optional If True, the slider will be vertical References ---------- https://ant.design/components/slider/ """ super().__init__() if start is None: if ranged: start = [minimum, maximum] else: start = minimum elif isinstance(start, Sequence): if len(start) > 2: raise ValueError('start cannot be more than 2 numbers') start = list(start) ranged = True self._comp = self._tag.format( range=jsbool(ranged), minimum=minimum, maximum=maximum, start=start, step=step, marks={minimum: str(minimum), maximum: str(maximum)}, vertical=jsbool(vertical) ) # pylint: disable=no-self-use
[docs] def do_max(self, value): """Replace the max value of the slider. Parameters ---------- value : int Maximum value of the slider. """ return value
[docs] def do_min(self, value): """Replace the min value of the slider. Parameters ---------- value : int Minimum value of the slider. """ return value
[docs] def do_value(self, value): """Set the value of the slider. Parameters ---------- value : int Value of the slider. """ return value
[docs] def do_inc(self, value=1): """Increment value of slider by given amount. Parameters ---------- value : int Number to change value of slider by. """ return value
[docs] def do_min_max_value(self, minimum, maximum, value): """Set the minimum, maximum, and value of slider simultaneously. Parameters ---------- minimum : int Minimum value of the slider. maximum : int Maximum value of the slider. value : int Value of the slider. """ return minimum, maximum, value
def on_change(self) -> Callable: """Emit an event when the slider's value changes. | **Payload:** ``number`` or ``list`` of values. Returns ------- str Name of event. """ return self.get def on_after_change(self): """Emit an event when the slider control is released. | **Payload:** ``number`` or ``list`` of values. Returns ------- str Name of event. """ return self.get def get(self, data): """ Get the currently selected value(s). Returns ------- list or number List if it's a range slider and gives two values. """ return data
[docs]class Nouislider(_Controller): """A lightweight JavaScript range slider library.""" _TEMPLATE = 'nouislider.jsx' _COMPONENT = 'Nouislider' _PACKAGE = 'nouislider@12.1.0' _ATTRS = ('range={{{{min: {min}, max: {max}}}}} ' 'socket={{socket}} ' 'start={{{start}}} ' 'tooltips={{{tooltips}}}') def __init__(self, start: Union[int, Sequence[int]] = 0, minimum: int = 0, maximum: int = 100, tooltips: bool = True) -> None: """Create a slider. Parameters ---------- start : number or list with two values, optional Determines the starting value. If a list of two values are given it will be a range slider. minimum : number, optional Minimum value of the slider. maximum : number, optional Maximum value of the slider. tooltips : bool, optional Show a popup text box. References ---------- https://refreshless.com/nouislider/events-callbacks/ """ super().__init__() if not isinstance(start, Sequence): nstart = [start] else: nstart = list(start) self._comp = self._tag.format( min=minimum, max=maximum, start=nstart, tooltips=jsbool(tooltips) ) def on_update(self): """Emit an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ return self.get def on_slide(self): """Emit an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ return self.get def on_set(self): """Emit an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ return self.get def on_change(self) -> Callable: """Emit an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ return self.get def on_start(self): """Emit an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ return self.get def on_end(self): """Emit an event when the slider is moved. https://refreshless.com/nouislider/events-callbacks/ | **Payload:** ``list`` of values. Returns ------- str Name of event. """ return self.get # pylint: disable=no-self-use def get(self, data): """ Get the currently selected value(s). Returns ------- list or number List if it's a range slider and gives two values. """ return data
[docs]class Checkbox(_Controller): """Ant Design checkboxes.""" _TEMPLATE = 'checkbox.jsx' _COMPONENT = 'Checkboxes' _PACKAGE = None _ATTRS = ('options={{{options}}} ' 'defaults={{{defaults}}}') def __init__(self, labels: Optional[Sequence[str]] = None, values: Optional[Sequence[Union[int, str]]] = None, defaults: Optional[Sequence[Union[int, str]]] = None) -> None: """Create checkboxes. Parameters ---------- labels : optional, sequence of stings values : optional, sequence of strings or ints defaults : optional, sequence of strings or ints References ---------- https://ant.design/components/checkbox/ """ super().__init__() options: List[Dict] = [] if labels is not None and values is not None: options = [{'label': label, 'value': value} for label, value in zip(labels, values)] if defaults is None: defaults = [] self._comp = self._tag.format( options=options, defaults=defaults ) # pylint: disable=no-self-use
[docs] def do_values(self, *value: Union[str, int]): """Set the value(s) of the slider. Parameters ---------- value : int Value of the slider. """ return value
[docs] def do_options(self, labels: Sequence[str], values: Sequence[Union[str, int]]) -> Sequence[Dict]: """Replace the checkbox options. Parameters ---------- labels : array-like List of strings which will be visible to the user. values : array-like List of values associated with the labels that are hidden from the user. Returns ------- None """ return [{'label': label, 'value': value} for label, value in zip(labels, values)]
[docs] def do_check(self, values: Sequence[Union[int, str]]): """Set the value of the slider. Parameters ---------- values : Sequence of int, str The radio value to select. """ return values
def on_change(self) -> Callable: """Emit an event when the slider's value changes. | **Payload:** ``number`` or ``list`` of values. Returns ------- str Name of event. """ return self.get def get(self, data): """ Get the checked values. Returns ------- list List of values """ return data
[docs]class Radio(_Controller): """Ant Design Radio Group.""" _TEMPLATE = 'radio.jsx' _COMPONENT = 'Radios' _PACKAGE = None _ATTRS = ('options={{{options}}} ' 'default={{{default}}}') def __init__(self, labels: Optional[Sequence[str]] = None, values: Optional[Sequence[Union[int, str]]] = None, default: Optional[Union[int, str]] = None) -> None: """Create radio boxes. Parameters ---------- labels : optional, sequence of stings values : optional, sequence of strings or ints default : optional, string or int References ---------- https://ant.design/components/radio/ """ super().__init__() options: List[Dict] = [] if labels is not None and values is not None: options = [{'label': label, 'value': value} for label, value in zip(labels, values)] self._comp = self._tag.format( options=options, default=jsnull(default), ) # pylint: disable=no-self-use
[docs] def do_select(self, value: Union[str, int]): """Set the value of the slider. Parameters ---------- value : int, str The radio value to select. """ return value
[docs] def do_options(self, labels, values): """Replace the radio button options. Parameters ---------- labels : Sequence List of strings which will be visible to the user. values : Sequence List of values associated with the labels that are hidden from the user. Returns ------- None """ return [{'label': label, 'value': value} for label, value in zip(labels, values)]
def on_change(self) -> Callable: """Emit an event when the slider's value changes. | **Payload:** ``number`` or ``list`` of values. Returns ------- str Name of event. """ return self.get def get(self, data): """ Get the currently selected value(s). Returns ------- list or number List if it's a range slider and gives two values. """ return data