mirror of
https://github.com/gradio-app/gradio.git
synced 2024-11-27 01:40:20 +08:00
Add event that's triggered when slider is released (#3353)
* Add code * Add unit test * CHANGELOG + notebook format * Clean up * Remove comment
This commit is contained in:
parent
5c01a029ce
commit
d8023d455f
11
CHANGELOG.md
11
CHANGELOG.md
@ -3,6 +3,17 @@
|
||||
|
||||
## New Features:
|
||||
|
||||
### Release event for Slider
|
||||
|
||||
Now you can trigger your python function to run when the slider is released as opposed to every slider change value!
|
||||
|
||||
Simply use the `release` method on the slider
|
||||
```python
|
||||
slider.release(function, inputs=[...], outputs=[...], api_name="predict")
|
||||
```
|
||||
|
||||
By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3353](https://github.com/gradio-app/gradio/pull/3353)
|
||||
|
||||
### Dropdown Component Updates
|
||||
|
||||
The standard dropdown component now supports searching for choices. Also when `multiselect` is `True`, you can specify `max_choices` to set the maximum number of choices you want the user to be able to select from the dropdown component.
|
||||
|
1
demo/slider_release/run.ipynb
Normal file
1
demo/slider_release/run.ipynb
Normal file
@ -0,0 +1 @@
|
||||
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: slider_release"]}, {"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", "\n", "def identity(x, state):\n", " state += 1\n", " return x, state, state\n", "\n", "\n", "with gr.Blocks() as demo:\n", " slider = gr.Slider(0, 100, step=0.1)\n", " state = gr.State(value=0)\n", " with gr.Row():\n", " number = gr.Number(label=\"On release\")\n", " number2 = gr.Number(label=\"Number of events fired\")\n", " slider.release(identity, inputs=[slider, state], outputs=[number, state, number2], api_name=\"predict\")\n", "\n", "if __name__ == \"__main__\":\n", " print(\"here\")\n", " demo.launch()\n", " print(demo.server_port)\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
20
demo/slider_release/run.py
Normal file
20
demo/slider_release/run.py
Normal file
@ -0,0 +1,20 @@
|
||||
import gradio as gr
|
||||
|
||||
|
||||
def identity(x, state):
|
||||
state += 1
|
||||
return x, state, state
|
||||
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
slider = gr.Slider(0, 100, step=0.1)
|
||||
state = gr.State(value=0)
|
||||
with gr.Row():
|
||||
number = gr.Number(label="On release")
|
||||
number2 = gr.Number(label="Number of events fired")
|
||||
slider.release(identity, inputs=[slider, state], outputs=[number, state, number2], api_name="predict")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("here")
|
||||
demo.launch()
|
||||
print(demo.server_port)
|
@ -40,6 +40,7 @@ from gradio.events import (
|
||||
Clickable,
|
||||
Editable,
|
||||
Playable,
|
||||
Releaseable,
|
||||
Streamable,
|
||||
Submittable,
|
||||
Uploadable,
|
||||
@ -616,7 +617,12 @@ class Number(
|
||||
|
||||
@document("change", "style")
|
||||
class Slider(
|
||||
FormComponent, Changeable, IOComponent, SimpleSerializable, NeighborInterpretable
|
||||
FormComponent,
|
||||
Changeable,
|
||||
Releaseable,
|
||||
IOComponent,
|
||||
SimpleSerializable,
|
||||
NeighborInterpretable,
|
||||
):
|
||||
"""
|
||||
Creates a slider that ranges from `minimum` to `maximum` with a step size of `step`.
|
||||
@ -624,7 +630,7 @@ class Slider(
|
||||
Postprocessing: expects an {int} or {float} returned from function and sets slider value to it as long as it is within range.
|
||||
Examples-format: A {float} or {int} representing the slider's value.
|
||||
|
||||
Demos: sentence_builder, generate_tone, titanic_survival, interface_random_slider, blocks_random_slider
|
||||
Demos: sentence_builder, slider_release, generate_tone, titanic_survival, interface_random_slider, blocks_random_slider
|
||||
Guides: create_your_own_friends_with_a_gan
|
||||
"""
|
||||
|
||||
|
@ -721,3 +721,60 @@ class Uploadable(EventListener):
|
||||
every=every,
|
||||
)
|
||||
set_cancel_events(self, "upload", cancels)
|
||||
|
||||
|
||||
class Releaseable(EventListener):
|
||||
def release(
|
||||
self,
|
||||
fn: Callable | None,
|
||||
inputs: Component | List[Component] | Set[Component] | None = None,
|
||||
outputs: Component | List[Component] | None = None,
|
||||
api_name: str | None = None,
|
||||
scroll_to_output: bool = False,
|
||||
show_progress: bool = True,
|
||||
queue: bool | None = None,
|
||||
batch: bool = False,
|
||||
max_batch_size: int = 4,
|
||||
preprocess: bool = True,
|
||||
postprocess: bool = True,
|
||||
cancels: Dict[str, Any] | List[Dict[str, Any]] | None = None,
|
||||
every: float | None = None,
|
||||
_js: str | None = None,
|
||||
):
|
||||
"""
|
||||
This event is triggered when the user releases the mouse on this component (e.g. when the user releases the slider). This method can be used when this component is in a Gradio Blocks.
|
||||
|
||||
Parameters:
|
||||
fn: Callable function
|
||||
inputs: List of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list.
|
||||
outputs: List of gradio.components to use as outputs. If the function returns no outputs, this should be an empty list.
|
||||
api_name: Defining this parameter exposes the endpoint in the api docs
|
||||
scroll_to_output: If True, will scroll to output component on completion
|
||||
show_progress: If True, will show progress animation while pending
|
||||
queue: If True, will place the request on the queue, if the queue exists
|
||||
batch: If True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component.
|
||||
max_batch_size: Maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True)
|
||||
preprocess: If False, will not run preprocessing of component data before running 'fn' (e.g. leaving it as a base64 string if this method is called with the `Image` component).
|
||||
postprocess: If False, will not run postprocessing of component data before returning 'fn' output to the browser.
|
||||
cancels: A list of other events to cancel when this event is triggered. For example, setting cancels=[click_event] will cancel the click_event, where click_event is the return value of another components .click method.
|
||||
every: Run this event 'every' number of seconds while the client connection is open. Interpreted in seconds. Queue must be enabled.
|
||||
"""
|
||||
# _js: Optional frontend js method to run before running 'fn'. Input arguments for js method are values of 'inputs' and 'outputs', return should be a list of values for output components.
|
||||
|
||||
self.set_event_trigger(
|
||||
"release",
|
||||
fn,
|
||||
inputs,
|
||||
outputs,
|
||||
preprocess=preprocess,
|
||||
postprocess=postprocess,
|
||||
scroll_to_output=scroll_to_output,
|
||||
show_progress=show_progress,
|
||||
api_name=api_name,
|
||||
js=_js,
|
||||
queue=queue,
|
||||
batch=batch,
|
||||
max_batch_size=max_batch_size,
|
||||
every=every,
|
||||
)
|
||||
set_cancel_events(self, "release", cancels)
|
||||
|
@ -886,11 +886,11 @@ def tex2svg(formula, *args):
|
||||
svg_start = xml_code.index("<svg ")
|
||||
svg_code = xml_code[svg_start:]
|
||||
svg_code = re.sub(r"<metadata>.*<\/metadata>", "", svg_code, flags=re.DOTALL)
|
||||
svg_code = re.sub(r' width="[^"]+"', '', svg_code)
|
||||
svg_code = re.sub(r' width="[^"]+"', "", svg_code)
|
||||
height_match = re.search(r'height="([\d.]+)pt"', svg_code)
|
||||
if height_match:
|
||||
height = float(height_match.group(1))
|
||||
new_height = height / FONTSIZE # conversion from pt to em
|
||||
new_height = height / FONTSIZE # conversion from pt to em
|
||||
svg_code = re.sub(r'height="[\d.]+pt"', f'height="{new_height}em"', svg_code)
|
||||
copy_code = f"<span style='font-size: 0px'>{formula}</span>"
|
||||
return f"{copy_code}{svg_code}"
|
||||
|
@ -37,5 +37,6 @@
|
||||
{step}
|
||||
disabled={mode === "static"}
|
||||
on:change
|
||||
on:release
|
||||
/>
|
||||
</Block>
|
||||
|
50
ui/packages/app/test/slider_release.spec.ts
Normal file
50
ui/packages/app/test/slider_release.spec.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { test, expect, Page, Locator } from "@playwright/test";
|
||||
|
||||
//taken from: https://github.com/microsoft/playwright/issues/20032
|
||||
async function changeSlider(
|
||||
page: Page,
|
||||
thumb: Locator,
|
||||
slider: Locator,
|
||||
targetPercentage: number
|
||||
) {
|
||||
const thumbBoundingBox = await thumb.boundingBox();
|
||||
const sliderBoundingBox = await slider.boundingBox();
|
||||
|
||||
if (thumbBoundingBox === null || sliderBoundingBox === null) {
|
||||
return; // NOTE it's probably better to throw an error here
|
||||
}
|
||||
|
||||
// Start from the middle of the slider's thumb
|
||||
const startPoint = {
|
||||
x: thumbBoundingBox.x + thumbBoundingBox.width / 2,
|
||||
y: thumbBoundingBox.y + thumbBoundingBox.height / 2
|
||||
};
|
||||
|
||||
// Slide it to some endpoint determined by the target percentage
|
||||
const endPoint = {
|
||||
x: sliderBoundingBox.x + sliderBoundingBox.width * targetPercentage,
|
||||
y: thumbBoundingBox.y + thumbBoundingBox.height / 2
|
||||
};
|
||||
|
||||
await page.mouse.move(startPoint.x, startPoint.y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(endPoint.x, endPoint.y);
|
||||
await page.mouse.up();
|
||||
}
|
||||
|
||||
test("slider release", async ({ page }) => {
|
||||
await page.goto("http://127.0.0.1:7888/");
|
||||
|
||||
const slider = page.getByLabel("Slider");
|
||||
|
||||
const responsePromise = page.waitForResponse(
|
||||
"http://127.0.0.1:7888/run/predict/"
|
||||
);
|
||||
await changeSlider(page, slider, slider, 0.7);
|
||||
const response = await responsePromise;
|
||||
const responseData = await response.json();
|
||||
|
||||
expect(responseData.data[0]).toBeGreaterThan(69.5);
|
||||
expect(responseData.data[0]).toBeLessThan(71.0);
|
||||
expect(responseData.data[2]).toEqual(1);
|
||||
});
|
@ -16,10 +16,16 @@
|
||||
export let show_label: boolean;
|
||||
|
||||
const id = `range_id_${_id++}`;
|
||||
const dispatch = createEventDispatcher<{ change: number }>();
|
||||
const dispatch = createEventDispatcher<{ change: number; release: number }>();
|
||||
|
||||
function handle_release(e: MouseEvent) {
|
||||
dispatch("release", value);
|
||||
}
|
||||
|
||||
$: dispatch("change", value);
|
||||
const clamp = () => (value = Math.min(Math.max(value, minimum), maximum));
|
||||
const clamp = () => {
|
||||
value = Math.min(Math.max(value, minimum), maximum);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="wrap">
|
||||
@ -48,6 +54,7 @@
|
||||
max={maximum}
|
||||
{step}
|
||||
{disabled}
|
||||
on:mouseup={handle_release}
|
||||
/>
|
||||
|
||||
<style>
|
||||
|
@ -3,5 +3,13 @@ export default {
|
||||
screenshot: "only-on-failure",
|
||||
trace: "retain-on-failure"
|
||||
},
|
||||
globalSetup: "./playwright-setup.js"
|
||||
globalSetup: "./playwright-setup.js",
|
||||
workers: 1,
|
||||
webServer: [
|
||||
{
|
||||
command: "GRADIO_SERVER_PORT=7888 python ../demo/slider_release/run.py",
|
||||
port: 7888,
|
||||
timeout: 120 * 1000
|
||||
}
|
||||
]
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user