From e3c7079e380880d5759d98d180eaf688122f1c69 Mon Sep 17 00:00:00 2001 From: aliabid94 Date: Wed, 10 Jul 2024 20:55:45 -0700 Subject: [PATCH] gr.DateTime component (#8713) * changes * changes * add changeset * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * changes * Update gradio/components/datetime.py Co-authored-by: Abubakar Abid * changes * changes --------- Co-authored-by: Ali Abid Co-authored-by: gradio-pr-bot Co-authored-by: Abubakar Abid --- .changeset/sharp-ideas-run.md | 8 + demo/datetime_component/run.ipynb | 1 + demo/datetime_component/run.py | 6 + demo/datetimes/run.ipynb | 1 + demo/datetimes/run.py | 27 +++ demo/native_plots/line_plot_demo.py | 1 - gradio/__init__.py | 1 + gradio/components/__init__.py | 2 + gradio/components/datetime.py | 155 +++++++++++++ gradio/components/line_plot.py | 102 +++++---- gradio/events.py | 11 +- .../gradio/03_components/datetime.svx | 89 ++++++++ js/app/package.json | 1 + js/app/test/datetimes.spec.ts | 53 +++++ js/datetime/CHANGELOG.md | 3 + js/datetime/Example.svelte | 5 + js/datetime/Index.svelte | 204 ++++++++++++++++++ js/datetime/package.json | 25 +++ js/icons/src/Back.svelte | 17 ++ js/icons/src/Calendar.svelte | 51 +++++ js/icons/src/index.ts | 2 + js/plot/Index.svelte | 9 +- js/plot/shared/Plot.svelte | 10 + js/plot/shared/plot_types/AltairPlot.svelte | 63 +++++- js/plot/shared/plot_types/altair_utils.ts | 2 +- package.json | 1 + pnpm-lock.yaml | 25 +++ requirements.txt | 2 +- test/components/test_datetime.py | 43 ++++ test/components/test_line_plot.py | 2 +- test/requirements.txt | 2 +- 31 files changed, 866 insertions(+), 58 deletions(-) create mode 100644 .changeset/sharp-ideas-run.md create mode 100644 demo/datetime_component/run.ipynb create mode 100644 demo/datetime_component/run.py create mode 100644 demo/datetimes/run.ipynb create mode 100644 demo/datetimes/run.py create mode 100644 gradio/components/datetime.py create mode 100644 js/_website/src/lib/templates/gradio/03_components/datetime.svx create mode 100644 js/app/test/datetimes.spec.ts create mode 100644 js/datetime/CHANGELOG.md create mode 100644 js/datetime/Example.svelte create mode 100644 js/datetime/Index.svelte create mode 100644 js/datetime/package.json create mode 100644 js/icons/src/Back.svelte create mode 100644 js/icons/src/Calendar.svelte create mode 100644 test/components/test_datetime.py diff --git a/.changeset/sharp-ideas-run.md b/.changeset/sharp-ideas-run.md new file mode 100644 index 0000000000..b54685a79c --- /dev/null +++ b/.changeset/sharp-ideas-run.md @@ -0,0 +1,8 @@ +--- +"@gradio/app": minor +"@gradio/icons": minor +"@gradio/plot": minor +"gradio": minor +--- + +feat:Time range component diff --git a/demo/datetime_component/run.ipynb b/demo/datetime_component/run.ipynb new file mode 100644 index 0000000000..6b1826e52d --- /dev/null +++ b/demo/datetime_component/run.ipynb @@ -0,0 +1 @@ +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: datetime_component"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " gr.DateTime()\n", "\n", "demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/datetime_component/run.py b/demo/datetime_component/run.py new file mode 100644 index 0000000000..3f58eeb5b8 --- /dev/null +++ b/demo/datetime_component/run.py @@ -0,0 +1,6 @@ +import gradio as gr + +with gr.Blocks() as demo: + gr.DateTime() + +demo.launch() diff --git a/demo/datetimes/run.ipynb b/demo/datetimes/run.ipynb new file mode 100644 index 0000000000..d410c02e96 --- /dev/null +++ b/demo/datetimes/run.ipynb @@ -0,0 +1 @@ +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: datetimes"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "\n", "with gr.Blocks() as demo:\n", " date1 = gr.DateTime(include_time=True, label=\"Date and Time\", type=\"datetime\", elem_id=\"date1\")\n", " date2 = gr.DateTime(include_time=False, label=\"Date Only\", type=\"string\", elem_id=\"date2\")\n", " date3 = gr.DateTime(elem_id=\"date3\", timezone=\"Europe/Paris\")\n", "\n", " with gr.Row():\n", " btn1 = gr.Button(\"Load Date 1\")\n", " btn2 = gr.Button(\"Load Date 2\")\n", " btn3 = gr.Button(\"Load Date 3\")\n", "\n", " click_output = gr.Textbox(label=\"Last Load\")\n", " change_output = gr.Textbox(label=\"Last Change\")\n", " submit_output = gr.Textbox(label=\"Last Submit\")\n", "\n", " btn1.click(lambda x:x, date1, click_output)\n", " btn2.click(lambda x:x, date2, click_output)\n", " btn3.click(lambda x:x, date3, click_output)\n", "\n", " for item in [date1, date2, date3]:\n", " item.change(lambda x:x, item, change_output)\n", " item.submit(lambda x:x, item, submit_output)\n", " \n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/datetimes/run.py b/demo/datetimes/run.py new file mode 100644 index 0000000000..394c343480 --- /dev/null +++ b/demo/datetimes/run.py @@ -0,0 +1,27 @@ +import gradio as gr + +with gr.Blocks() as demo: + date1 = gr.DateTime(include_time=True, label="Date and Time", type="datetime", elem_id="date1") + date2 = gr.DateTime(include_time=False, label="Date Only", type="string", elem_id="date2") + date3 = gr.DateTime(elem_id="date3", timezone="Europe/Paris") + + with gr.Row(): + btn1 = gr.Button("Load Date 1") + btn2 = gr.Button("Load Date 2") + btn3 = gr.Button("Load Date 3") + + click_output = gr.Textbox(label="Last Load") + change_output = gr.Textbox(label="Last Change") + submit_output = gr.Textbox(label="Last Submit") + + btn1.click(lambda x:x, date1, click_output) + btn2.click(lambda x:x, date2, click_output) + btn3.click(lambda x:x, date3, click_output) + + for item in [date1, date2, date3]: + item.change(lambda x:x, item, change_output) + item.submit(lambda x:x, item, submit_output) + + +if __name__ == "__main__": + demo.launch() \ No newline at end of file diff --git a/demo/native_plots/line_plot_demo.py b/demo/native_plots/line_plot_demo.py index a87e9d72ce..a6c5591708 100644 --- a/demo/native_plots/line_plot_demo.py +++ b/demo/native_plots/line_plot_demo.py @@ -65,7 +65,6 @@ def line_plot_fn(dataset): tooltip=['country', 'life_expect'], overlay_point=False, title="Life expectancy for countries", - stroke_dash_legend_title="Country Cluster", ) diff --git a/gradio/__init__.py b/gradio/__init__.py index f2b42dd06b..008c63bd4c 100644 --- a/gradio/__init__.py +++ b/gradio/__init__.py @@ -26,6 +26,7 @@ from gradio.components import ( DataFrame, Dataframe, Dataset, + DateTime, DownloadButton, Dropdown, DuplicateButton, diff --git a/gradio/components/__init__.py b/gradio/components/__init__.py index 287a98d0e1..93172601a0 100644 --- a/gradio/components/__init__.py +++ b/gradio/components/__init__.py @@ -19,6 +19,7 @@ from gradio.components.code import Code from gradio.components.color_picker import ColorPicker from gradio.components.dataframe import Dataframe from gradio.components.dataset import Dataset +from gradio.components.datetime import DateTime from gradio.components.download_button import DownloadButton from gradio.components.dropdown import Dropdown from gradio.components.duplicate_button import DuplicateButton @@ -95,6 +96,7 @@ __all__ = [ "Markdown", "MessageDict", "Textbox", + "DateTime", "Dropdown", "Model3D", "File", diff --git a/gradio/components/datetime.py b/gradio/components/datetime.py new file mode 100644 index 0000000000..5b77738be4 --- /dev/null +++ b/gradio/components/datetime.py @@ -0,0 +1,155 @@ +"""gr.DateTime() component.""" + +from __future__ import annotations + +import re +from datetime import datetime, timedelta +from typing import Any, Literal + +import pytz +from gradio_client.documentation import document + +from gradio.components.base import FormComponent +from gradio.events import Events + + +@document() +class DateTime(FormComponent): + """ + Component to select a date and (optionally) a time. + """ + + EVENTS = [ + Events.change, + Events.submit, + ] + + def __init__( + self, + value: float | str | datetime | None = None, + *, + include_time: bool = True, + type: Literal["timestamp", "datetime", "string"] = "timestamp", + timezone: str | None = None, + label: str | None = None, + show_label: bool | None = None, + info: str | None = None, + every: float | None = None, + scale: int | None = None, + min_width: int = 160, + visible: bool = True, + elem_id: str | None = None, + elem_classes: list[str] | str | None = None, + render: bool = True, + key: int | str | None = None, + ): + """ + Parameters: + value: default value for datetime. + label: The label for this component. Appears above the component and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to. + show_label: if True, will display label. + include_time: If True, the component will include time selection. If False, only date selection will be available. + type: The type of the value. Can be "timestamp", "datetime", or "string". If "timestamp", the value will be a number representing the start and end date in seconds since epoch. If "datetime", the value will be a datetime object. If "string", the value will be the date entered by the user. + timezone: The timezone to use for timestamps, such as "US/Pacific" or "Europe/Paris". If None, the timezone will be the local timezone. + info: additional component description. + every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute. + scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True. + min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first. + visible: If False, component will be hidden. + elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles. + render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later. + key: if assigned, will be used to assume identity across a re-render. Components that have the same key across a re-render will have their value preserved. + """ + super().__init__( + every=every, + scale=scale, + min_width=min_width, + visible=visible, + label=label, + show_label=show_label, + info=info, + elem_id=elem_id, + elem_classes=elem_classes, + render=render, + key=key, + value=value, + ) + self.type = type + self.include_time = include_time + self.time_format = "%Y-%m-%d %H:%M:%S" if include_time else "%Y-%m-%d" + self.timezone = timezone + + def preprocess(self, payload: str | None) -> str | float | datetime | None: + """ + Parameters: + payload: the text entered in the textarea. + Returns: + Passes text value as a {str} into the function. + """ + if payload is None or payload == "": + return None + if self.type == "string" and "now" not in payload: + return payload + datetime = self.get_datetime_from_str(payload) + if self.type == "string": + return datetime.strftime(self.time_format) + if self.type == "datetime": + return datetime + elif self.type == "timestamp": + return datetime.timestamp() + + def postprocess(self, value: float | datetime | str | None) -> str | None: + """ + Parameters: + value: Expects a tuple pair of datetimes. + Returns: + A tuple pair of timestamps. + """ + if value is None: + return None + + if isinstance(value, datetime): + return datetime.strftime(value, self.time_format) + elif isinstance(value, str): + return value + else: + return datetime.fromtimestamp( + value, tz=pytz.timezone(self.timezone) if self.timezone else None + ).strftime(self.time_format) + + def api_info(self) -> dict[str, Any]: + return { + "type": "string", + "description": f"Formatted as YYYY-MM-DD{' HH:MM:SS' if self.include_time else ''}", + } + + def example_payload(self) -> str: + return "2020-10-01 05:20:15" + + def example_value(self) -> str: + return "2020-10-01 05:20:15" + + def get_datetime_from_str(self, date: str) -> datetime: + now_regex = r"^(?:\s*now\s*(?:-\s*(\d+)\s*([dmhs]))?)?\s*$" + + if "now" in date: + match = re.match(now_regex, date) + if match: + num = int(match.group(1) or 0) + unit = match.group(2) or "s" + if unit == "d": + delta = timedelta(days=num) + elif unit == "h": + delta = timedelta(hours=num) + elif unit == "m": + delta = timedelta(minutes=num) + else: + delta = timedelta(seconds=num) + return datetime.now() - delta + else: + raise ValueError("Invalid 'now' time format") + else: + dt = datetime.strptime(date, self.time_format) + if self.timezone: + dt = pytz.timezone(self.timezone).localize(dt) + return dt diff --git a/gradio/components/line_plot.py b/gradio/components/line_plot.py index 211c5895ca..b4c22d26aa 100644 --- a/gradio/components/line_plot.py +++ b/gradio/components/line_plot.py @@ -9,6 +9,7 @@ from gradio_client.documentation import document from gradio.components.base import Component from gradio.components.plot import AltairPlot, AltairPlotData, Plot +from gradio.events import Events if TYPE_CHECKING: import pandas as pd @@ -27,6 +28,8 @@ class LinePlot(Plot): data_model = AltairPlotData + EVENTS = [Events.select] + def __init__( self, value: pd.DataFrame | Callable | None = None, @@ -73,7 +76,6 @@ class LinePlot(Plot): x_lim: list[int] | None = None, y_lim: list[int] | None = None, caption: str | None = None, - interactive: bool | None = True, label: str | None = None, show_label: bool | None = None, container: bool = True, @@ -87,17 +89,18 @@ class LinePlot(Plot): render: bool = True, key: int | str | None = None, show_actions_button: bool = False, + interactive: bool | None = None, ): """ Parameters: value: The pandas dataframe containing the data to display in a scatter plot. - x: Column corresponding to the x axis. - y: Column corresponding to the y axis. + x: Column corresponding to the x axis. Can be grouped if datetime, e.g. 'yearmonth(date)' or 'minuteseconds(date)' with a column name 'date'. Any time unit supported by altair can be used. + y: Column corresponding to the y axis. Can be aggregated, e.g. 'sum(price)' or 'count(price)' with a column name 'price'. Any aggregation function supported by altair can be used. color: The column to determine the point color. If the column contains numeric data, gradio will interpolate the column data so that small values correspond to light colors and large values correspond to dark values. stroke_dash: The column to determine the symbol used to draw the line, e.g. dashed lines, dashed lines with points. overlay_point: Whether to draw a point on the line for each (x, y) coordinate pair. title: The title to display on top of the chart. - tooltip: The column (or list of columns) to display on the tooltip when a user hovers a point on the plot. + tooltip: The column (or list of columns) to display on the tooltip when a user hovers a point on the plot. Set to [] to disable tooltips. x_title: The title given to the x axis. By default, uses the value of the x parameter. y_title: The title given to the y axis. By default, uses the value of the y parameter. x_label_angle: The angle for the x axis labels. Positive values are clockwise, and negative values are counter-clockwise. @@ -111,7 +114,7 @@ class LinePlot(Plot): x_lim: A tuple or list containing the limits for the x-axis, specified as [x_min, x_max]. y_lim: A tuple of list containing the limits for the y-axis, specified as [y_min, y_max]. caption: The (optional) caption to display below the plot. - interactive: Whether users should be able to interact with the plot by panning or zooming with their mouse or trackpad. + interactive: Deprecated. label: The (optional) label to display on the top left corner of the plot. show_label: Whether the label should be displayed. every: Continously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer. @@ -127,7 +130,9 @@ class LinePlot(Plot): self.y = y self.color = color self.stroke_dash = stroke_dash - self.tooltip = tooltip + self.tooltip = ( + tooltip if tooltip is not None else [elem for elem in [x, y, color] if elem] + ) self.title = title self.x_title = x_title self.y_title = y_title @@ -141,7 +146,6 @@ class LinePlot(Plot): self.x_lim = x_lim self.y_lim = y_lim self.caption = caption - self.interactive_chart = interactive if isinstance(width, str): width = None warnings.warn( @@ -172,6 +176,11 @@ class LinePlot(Plot): every=every, inputs=inputs, ) + if interactive is not None: + warnings.warn( + "The `interactive` parameter is deprecated and will be removed in a future version. " + "The LinePlot component is always interactive." + ) def get_block_name(self) -> str: return "plot" @@ -220,25 +229,23 @@ class LinePlot(Plot): width: int | None = None, x_lim: list[int] | None = None, y_lim: list[int] | None = None, - interactive: bool | None = None, ): """Helper for creating the scatter plot.""" import altair as alt - interactive = True if interactive is None else interactive encodings = { "x": alt.X( - x, # type: ignore - title=x_title or x, # type: ignore - scale=AltairPlot.create_scale(x_lim), # type: ignore + x, + title=x_title or x, + scale=AltairPlot.create_scale(x_lim), axis=alt.Axis(labelAngle=x_label_angle) if x_label_angle is not None else alt.Axis(), ), "y": alt.Y( - y, # type: ignore - title=y_title or y, # type: ignore - scale=AltairPlot.create_scale(y_lim), # type: ignore + y, + title=y_title or y, + scale=AltairPlot.create_scale(y_lim), axis=alt.Axis(labelAngle=y_label_angle) if y_label_angle is not None else alt.Axis(), @@ -265,46 +272,60 @@ class LinePlot(Plot): ), } - highlight = None - if interactive and any([color, stroke_dash]): - highlight = alt.selection( - type="single", # type: ignore - on="mouseover", - fields=[c for c in [color, stroke_dash] if c], - nearest=True, - ) - if stroke_dash: - stroke_dash = { - "field": stroke_dash, # type: ignore - "legend": AltairPlot.create_legend( # type: ignore - position=stroke_dash_legend_position, # type: ignore - title=stroke_dash_legend_title or stroke_dash, # type: ignore - ), # type: ignore - } # type: ignore + stroke_dash_encoding = { + "field": stroke_dash, + "legend": AltairPlot.create_legend( + position=stroke_dash_legend_position or "bottom", + title=stroke_dash_legend_title, + ), + } else: - stroke_dash = alt.value(alt.Undefined) # type: ignore + stroke_dash_encoding = alt.value(alt.Undefined) if tooltip: encodings["tooltip"] = tooltip - chart = alt.Chart(value).encode(**encodings) # type: ignore + chart = alt.Chart(value).encode(**encodings) points = chart.mark_point(clip=True).encode( opacity=alt.value(alt.Undefined) if overlay_point else alt.value(0), ) - lines = chart.mark_line(clip=True).encode(strokeDash=stroke_dash) + lines = chart.mark_line(clip=True).encode(strokeDash=stroke_dash_encoding) - if highlight: - points = points.add_selection(highlight) + highlight = alt.selection_point( + on="mouseover", + fields=[c for c in [color, stroke_dash] if c], + nearest=True, + clear="mouseout", + empty=False, + ) + points = points.add_params(highlight) + lines = lines.encode( + size=alt.condition(highlight, alt.value(4), alt.value(2)), + ) + if not overlay_point: + highlight_pts = alt.selection_point( + on="mouseover", + nearest=True, + clear="mouseout", + empty=False, + ) + points = points.add_params(highlight_pts) - lines = lines.encode( - size=alt.condition(highlight, alt.value(4), alt.value(1)), + points = points.encode( + opacity=alt.condition(highlight_pts, alt.value(1), alt.value(0)), + size=alt.condition(highlight_pts, alt.value(100), alt.value(0)), ) chart = (lines + points).properties(background="transparent", **properties) - if interactive: - chart = chart.interactive() + + selection = alt.selection_interval( + encodings=["x"], + mark=alt.BrushConfig(fill="gray", fillOpacity=0.3, stroke="none"), + name="brush", + ) + chart = chart.add_params(selection) return chart @@ -350,7 +371,6 @@ class LinePlot(Plot): x_lim=self.x_lim, y_lim=self.y_lim, stroke_dash=self.stroke_dash, - interactive=self.interactive_chart, height=self.height, width=self.width, ) diff --git a/gradio/events.py b/gradio/events.py index f9d90745e1..7981bc93e9 100644 --- a/gradio/events.py +++ b/gradio/events.py @@ -532,6 +532,7 @@ class EventListener(str): event_trigger.event_name = _event_name event_trigger.has_trigger = _has_trigger + event_trigger.callback = _callback return event_trigger @@ -600,10 +601,10 @@ def on( """ from gradio.components.base import Component - triggers_typed = cast(EventListener, triggers) + if not isinstance(triggers, Sequence) and triggers is not None: + triggers = [triggers] + triggers_typed = cast(Sequence[EventListener], triggers) - if isinstance(triggers_typed, EventListener): - triggers_typed = [triggers_typed] if isinstance(inputs, Component): inputs = [inputs] @@ -654,6 +655,10 @@ def on( EventListenerMethod(t.__self__ if t.has_trigger else None, t.event_name) # type: ignore for t in triggers_typed ] + if triggers: + for trigger in triggers: + if trigger.callback: + trigger.callback(trigger.__self__) if every is not None: from gradio.components import Timer diff --git a/js/_website/src/lib/templates/gradio/03_components/datetime.svx b/js/_website/src/lib/templates/gradio/03_components/datetime.svx new file mode 100644 index 0000000000..33b96183e5 --- /dev/null +++ b/js/_website/src/lib/templates/gradio/03_components/datetime.svx @@ -0,0 +1,89 @@ + + + + +# {obj.name} + + +```python +gradio.DateTime(···) +``` + + +
+ +import gradio as gr +with gr.Blocks() as demo: + gr.DateTime() +demo.launch() + +
+ + + +### Description +## {@html style_formatted_text(obj.description)} + + +### Behavior +## **As input component**: {@html style_formatted_text(obj.preprocess.return_doc.doc)} +##### Your function should accept one of these types: + +```python +def predict( + value: float | datetime | str | None +) + ... +``` + +
+ +## **As output component**: {@html style_formatted_text(obj.postprocess.parameter_doc[0].doc)} +##### Your function should return one of these types: + +```python +def predict(···) -> float | datetime | str | None + ... + return value +``` + + + +### Initialization + + + +{#if obj.string_shortcuts && obj.string_shortcuts.length > 0} + +### Shortcuts + +{/if} + +{#if obj.demos && obj.demos.length > 0} + +### Demos + +{/if} + +{#if obj.fns && obj.fns.length > 0} + +### Event Listeners + +{/if} + +{#if obj.guides && obj.guides.length > 0} + +### Guides + +{/if} diff --git a/js/app/package.json b/js/app/package.json index bd27878dc2..89f9f94be8 100644 --- a/js/app/package.json +++ b/js/app/package.json @@ -72,6 +72,7 @@ "@gradio/tabs": "workspace:^", "@gradio/textbox": "workspace:^", "@gradio/theme": "workspace:^", + "@gradio/datetime": "workspace:^", "@gradio/timer": "workspace:^", "@gradio/upload": "workspace:^", "@gradio/uploadbutton": "workspace:^", diff --git a/js/app/test/datetimes.spec.ts b/js/app/test/datetimes.spec.ts new file mode 100644 index 0000000000..d88e55209d --- /dev/null +++ b/js/app/test/datetimes.spec.ts @@ -0,0 +1,53 @@ +import { test, expect } from "@gradio/tootils"; + +test("gr.DateTime shows correct values", async ({ page }) => { + await page.locator("#date1").getByRole("textbox").first().click(); + await page + .locator("#date1") + .getByRole("textbox") + .first() + .fill("2020-10-01 10:50:00"); + await page.locator("body").first().click(); + await expect(page.getByLabel("Last Change")).toHaveValue( + "2020-10-01 10:50:00" + ); + await expect(page.getByLabel("Last Submit")).toHaveValue(""); + await page.locator("#date1").getByRole("textbox").first().press("Enter"); + await expect(page.getByLabel("Last Submit")).toHaveValue( + "2020-10-01 10:50:00" + ); + await expect(page.getByLabel("Last Load")).toHaveValue(""); + + await page.locator("#date2").getByRole("textbox").first().click(); + await page.locator("#date2").getByRole("textbox").first().fill("2000-02-22"); + await page.locator("body").first().click(); + await expect(page.getByLabel("Last Change")).toHaveValue("2000-02-22"); + + await page.getByRole("button", { name: "Load Date 1" }).click(); + await expect(page.getByLabel("Last Load")).toHaveValue("2020-10-01 10:50:00"); + await page.getByRole("button", { name: "Load Date 2" }).click(); + await expect(page.getByLabel("Last Load")).toHaveValue("2000-02-22"); + + await page.locator("#date2").getByRole("textbox").first().click(); + await page + .locator("#date2") + .getByRole("textbox") + .first() + .fill("2020-05-01xxx"); + await page.locator("body").first().click(); + await expect(page.getByLabel("Last Change")).toHaveValue("2000-02-22"); + + await page.locator("#date2").getByRole("textbox").first().click(); + await page.locator("#date2").getByRole("textbox").first().fill("2020-05-02"); + await page.locator("body").first().click(); + await expect(page.getByLabel("Last Change")).toHaveValue("2020-05-02"); + + await page.locator("#date3").getByRole("textbox").first().click(); + await page + .locator("#date3") + .getByRole("textbox") + .first() + .fill("2020-10-10 05:01:01"); + await page.getByRole("button", { name: "Load Date 3" }).click(); + await expect(page.getByLabel("Last Load")).toHaveValue("1602298861.0"); +}); diff --git a/js/datetime/CHANGELOG.md b/js/datetime/CHANGELOG.md new file mode 100644 index 0000000000..cae41448bc --- /dev/null +++ b/js/datetime/CHANGELOG.md @@ -0,0 +1,3 @@ +# @gradio/datetime + +## 0.1.0 diff --git a/js/datetime/Example.svelte b/js/datetime/Example.svelte new file mode 100644 index 0000000000..fa3904233f --- /dev/null +++ b/js/datetime/Example.svelte @@ -0,0 +1,5 @@ + + +{value || ""} diff --git a/js/datetime/Index.svelte b/js/datetime/Index.svelte new file mode 100644 index 0000000000..cdde9d6d2b --- /dev/null +++ b/js/datetime/Index.svelte @@ -0,0 +1,204 @@ + + + + + +
+ {label} +
+
+ { + if (evt.key === "Enter") { + submit_values(); + gradio.dispatch("submit"); + } + }} + on:blur={submit_values} + /> + {#if include_time} + { + const date = new Date(datevalue); + entered_value = format_date(date); + submit_values(); + }} + /> + {:else} + { + const date = new Date(datevalue); + entered_value = format_date(date); + submit_values(); + }} + /> + {/if} + + +
+
+ + diff --git a/js/datetime/package.json b/js/datetime/package.json new file mode 100644 index 0000000000..ef0702639b --- /dev/null +++ b/js/datetime/package.json @@ -0,0 +1,25 @@ +{ + "name": "@gradio/datetime", + "version": "0.0.1", + "description": "Gradio UI packages", + "type": "module", + "author": "", + "license": "ISC", + "private": false, + "main_changeset": true, + "main": "Index.svelte", + "exports": { + ".": "./Index.svelte", + "./example": "./Example.svelte", + "./package.json": "./package.json" + }, + "dependencies": { + "@gradio/atoms": "workspace:^", + "@gradio/icons": "workspace:^", + "@gradio/statustracker": "workspace:^", + "@gradio/utils": "workspace:^" + }, + "devDependencies": { + "@gradio/preview": "workspace:^" + } +} diff --git a/js/icons/src/Back.svelte b/js/icons/src/Back.svelte new file mode 100644 index 0000000000..624b612b09 --- /dev/null +++ b/js/icons/src/Back.svelte @@ -0,0 +1,17 @@ + + + diff --git a/js/icons/src/Calendar.svelte b/js/icons/src/Calendar.svelte new file mode 100644 index 0000000000..7c9278e7dc --- /dev/null +++ b/js/icons/src/Calendar.svelte @@ -0,0 +1,51 @@ + + + + + + diff --git a/js/icons/src/index.ts b/js/icons/src/index.ts index 9cdb438451..7a44222498 100644 --- a/js/icons/src/index.ts +++ b/js/icons/src/index.ts @@ -1,6 +1,8 @@ +export { default as Back } from "./Back.svelte"; export { default as Backward } from "./Backward.svelte"; export { default as Brush } from "./Brush.svelte"; export { default as BrushSize } from "./BrushSize.svelte"; +export { default as Calendar } from "./Calendar.svelte"; export { default as Camera } from "./Camera.svelte"; export { default as Chart } from "./Chart.svelte"; export { default as Chat } from "./Chat.svelte"; diff --git a/js/plot/Index.svelte b/js/plot/Index.svelte index 18221a2962..749b21bc4d 100644 --- a/js/plot/Index.svelte +++ b/js/plot/Index.svelte @@ -3,7 +3,7 @@ gradio.dispatch("change")} + on:select={(e) => gradio.dispatch("select", e.detail)} /> diff --git a/js/plot/shared/Plot.svelte b/js/plot/shared/Plot.svelte index 402afcbeb4..05450cfad7 100644 --- a/js/plot/shared/Plot.svelte +++ b/js/plot/shared/Plot.svelte @@ -3,6 +3,7 @@ import { Plot as PlotIcon } from "@gradio/icons"; import { Empty } from "@gradio/atoms"; import type { ThemeMode } from "js/app/src/components/types"; + import type { Gradio, SelectData } from "@gradio/utils"; export let value; export let target: HTMLElement; @@ -11,6 +12,11 @@ export let caption: string; export let bokeh_version: string | null; export let show_actions_button: bool; + export let gradio: Gradio<{ + select: SelectData; + }>; + export let x_lim: [number, number] | null = null; + export let _selectable: boolean; let PlotComponent: any = null; let _type = value?.type; @@ -45,7 +51,11 @@ {caption} {bokeh_version} {show_actions_button} + {gradio} + {_selectable} + {x_lim} on:load + on:select /> {:else} diff --git a/js/plot/shared/plot_types/AltairPlot.svelte b/js/plot/shared/plot_types/AltairPlot.svelte index 9966a98fb1..969c8a623b 100644 --- a/js/plot/shared/plot_types/AltairPlot.svelte +++ b/js/plot/shared/plot_types/AltairPlot.svelte @@ -1,17 +1,24 @@