From 80f8fbf0e8900627b9c2575bbd7c68fad8108544 Mon Sep 17 00:00:00 2001 From: Hannah Date: Tue, 23 Jan 2024 08:31:59 +0100 Subject: [PATCH] Add logic to handle non-interactive or hidden tabs (#7107) * Refactor change_tab function to handle non-interactive or hidden tabs * add changeset * Refactor flashcards app UI and modify test * Fix formatting --------- Co-authored-by: gradio-pr-bot --- .changeset/famous-socks-clap.md | 6 + demo/blocks_flashcards/run.ipynb | 2 +- demo/blocks_flashcards/run.py | 155 +++++++++++++++----------- js/app/test/blocks_flashcards.spec.ts | 22 +++- js/tabs/shared/Tabs.svelte | 19 +++- 5 files changed, 126 insertions(+), 78 deletions(-) create mode 100644 .changeset/famous-socks-clap.md diff --git a/.changeset/famous-socks-clap.md b/.changeset/famous-socks-clap.md new file mode 100644 index 0000000000..374d848385 --- /dev/null +++ b/.changeset/famous-socks-clap.md @@ -0,0 +1,6 @@ +--- +"@gradio/tabs": patch +"gradio": patch +--- + +fix:Add logic to handle non-interactive or hidden tabs diff --git a/demo/blocks_flashcards/run.ipynb b/demo/blocks_flashcards/run.ipynb index dd027945b0..efaf684888 100644 --- a/demo/blocks_flashcards/run.ipynb +++ b/demo/blocks_flashcards/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: blocks_flashcards"]}, {"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 random\n", "\n", "import gradio as gr\n", "\n", "demo = gr.Blocks()\n", "\n", "with demo:\n", " gr.Markdown(\n", " \"Load the flashcards in the table below, then use the Practice tab to practice.\"\n", " )\n", "\n", " with gr.Tab(\"Word Bank\"):\n", " flashcards_table = gr.Dataframe(headers=[\"front\", \"back\"], type=\"array\")\n", " with gr.Tab(\"Practice\"):\n", " with gr.Row():\n", " with gr.Column():\n", " front = gr.Textbox(label=\"Prompt\")\n", " with gr.Row():\n", " new_btn = gr.Button(\"New Card\")\n", " flip_btn = gr.Button(\"Flip Card\")\n", " with gr.Column(visible=False) as answer_col:\n", " back = gr.Textbox(label=\"Answer\")\n", " selected_card = gr.State()\n", " with gr.Row():\n", " correct_btn = gr.Button(\"Correct\")\n", " incorrect_btn = gr.Button(\"Incorrect\")\n", "\n", " with gr.Tab(\"Results\", visible=False) as results_tab:\n", " results = gr.State(value={})\n", " correct_field = gr.Markdown(\"# Correct: 0\")\n", " incorrect_field = gr.Markdown(\"# Incorrect: 0\")\n", " gr.Markdown(\"Card Statistics: \")\n", " results_table = gr.Dataframe(headers=[\"Card\", \"Correct\", \"Incorrect\"])\n", "\n", " def load_new_card(flashcards):\n", " card = random.choice(flashcards)\n", " return (\n", " card,\n", " card[0],\n", " gr.Column(visible=False),\n", " )\n", "\n", " new_btn.click(\n", " load_new_card,\n", " [flashcards_table],\n", " [selected_card, front, answer_col],\n", " )\n", "\n", " def flip_card(card):\n", " return card[1], gr.Column(visible=True)\n", "\n", " flip_btn.click(flip_card, [selected_card], [back, answer_col])\n", "\n", " def mark_correct(card, results):\n", " if card[0] not in results:\n", " results[card[0]] = [0, 0]\n", " results[card[0]][0] += 1\n", " correct_count = sum(result[0] for result in results.values())\n", " return (\n", " results,\n", " f\"# Correct: {correct_count}\",\n", " [[front, scores[0], scores[1]] for front, scores in results.items()],\n", " )\n", "\n", " def mark_incorrect(card, results):\n", " if card[0] not in results:\n", " results[card[0]] = [\n", " 0, 0]\n", " results[card[0]][1] += 1\n", " incorrect_count = sum(result[1] for result in results.values())\n", " return (\n", " results,\n", " f\"# Inorrect: {incorrect_count}\",\n", " [[front, scores[0], scores[1]] for front, scores in results.items()],\n", " )\n", "\n", " def toggle_results_tab():\n", " return gr.Tab(\"Results\", visible=True)\n", "\n", " correct_btn.click(\n", " mark_correct,\n", " [selected_card, results],\n", " [results, correct_field, results_table],\n", " )\n", "\n", " incorrect_btn.click(mark_incorrect, [selected_card, results], [results, incorrect_field, results_table])\n", "\n", " # set results tab to visible when correct or incorrect button is clicked\n", " correct_btn.click(fn=toggle_results_tab, outputs=[results_tab])\n", " incorrect_btn.click(fn=toggle_results_tab, outputs=[results_tab])\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file +{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: blocks_flashcards"]}, {"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 random\n", "\n", "import gradio as gr\n", "\n", "demo = gr.Blocks()\n", "\n", "def is_data_empty(flashcards):\n", " if isinstance(flashcards, dict):\n", " return all(item == '' for sublist in flashcards['data'] for item in sublist)\n", " elif isinstance(flashcards, list):\n", " return all(all(item == '' for item in sublist) for sublist in flashcards)\n", " else:\n", " return True\n", "\n", "with demo:\n", " gr.Markdown(\n", " \"Load the flashcards in the table below, then use the Practice tab to practice.\"\n", " )\n", "\n", " with gr.Tabs() as tabs:\n", " with gr.Tab(\"Word Bank\"):\n", " flashcards_table = gr.Dataframe(headers=[\"front\", \"back\"], type=\"array\")\n", " flashcards_table.change(fn=lambda: print(flashcards_table.value))\n", " practice_btn = gr.Button(\"Start Practice\")\n", " with gr.Tab(\"Practice\", interactive=False, id=1) as practice_tab:\n", " with gr.Row():\n", " with gr.Column():\n", " front = gr.Textbox(label=\"Prompt\")\n", " with gr.Row():\n", " new_btn = gr.Button(\"New Card\")\n", " flip_btn = gr.Button(\"Flip Card\")\n", " with gr.Column(visible=False) as answer_col:\n", " back = gr.Textbox(label=\"Answer\")\n", " selected_card = gr.State()\n", " with gr.Row():\n", " correct_btn = gr.Button(\"Correct\")\n", " incorrect_btn = gr.Button(\"Incorrect\")\n", "\n", " def start_practice(flashcards):\n", " # if no cards entered into dataframe yet, return\n", " if is_data_empty(flashcards):\n", " practice_tab = gr.Tab(\"Practice\", interactive=False, id=1)\n", " raise gr.Error(\"Please enter word prompts into the table.\")\n", " return [practice_tab, tabs]\n", " else:\n", " practice_tab = gr.Tab(\"Practice\", interactive=True, id=1)\n", " new_tabs = gr.Tabs(selected=1)\n", " return [practice_tab, new_tabs]\n", "\n", " with gr.Tab(\"Results\", visible=False) as results_tab:\n", " results = gr.State(value={})\n", " correct_field = gr.Markdown(\"# Correct: 0\")\n", " incorrect_field = gr.Markdown(\"# Incorrect: 0\")\n", " gr.Markdown(\"Card Statistics: \")\n", " results_table = gr.Dataframe(headers=[\"Card\", \"Correct\", \"Incorrect\"])\n", " practice_btn.click(start_practice, inputs=[flashcards_table], outputs=[practice_tab, tabs])\n", "\n", " def load_new_card(flashcards):\n", " card = random.choice(flashcards)\n", " return (\n", " card,\n", " card[0],\n", " gr.Column(visible=False),\n", " )\n", "\n", " new_btn.click(\n", " load_new_card,\n", " [flashcards_table],\n", " [selected_card, front, answer_col],\n", " )\n", "\n", " def flip_card(card):\n", " return card[1], gr.Column(visible=True)\n", "\n", " flip_btn.click(flip_card, [selected_card], [back, answer_col])\n", "\n", " def mark_correct(card, results):\n", " if card[0] not in results:\n", " results[card[0]] = [0, 0]\n", " results[card[0]][0] += 1\n", " correct_count = sum(result[0] for result in results.values())\n", " return (\n", " results,\n", " f\"# Correct: {correct_count}\",\n", " [[front, scores[0], scores[1]] for front, scores in results.items()],\n", " )\n", "\n", " def mark_incorrect(card, results):\n", " if card[0] not in results:\n", " results[card[0]] = [\n", " 0, 0]\n", " results[card[0]][1] += 1\n", " incorrect_count = sum(result[1] for result in results.values())\n", " return (\n", " results,\n", " f\"# Inorrect: {incorrect_count}\",\n", " [[front, scores[0], scores[1]] for front, scores in results.items()],\n", " )\n", "\n", " def toggle_results_tab():\n", " return gr.Tab(\"Results\", visible=True)\n", "\n", " correct_btn.click(\n", " mark_correct,\n", " [selected_card, results],\n", " [results, correct_field, results_table],\n", " )\n", "\n", " incorrect_btn.click(mark_incorrect, [selected_card, results], [results, incorrect_field, results_table])\n", "\n", " # set results tab to visible when correct or incorrect button is clicked\n", " correct_btn.click(fn=toggle_results_tab, outputs=[results_tab])\n", " incorrect_btn.click(fn=toggle_results_tab, outputs=[results_tab])\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5} \ No newline at end of file diff --git a/demo/blocks_flashcards/run.py b/demo/blocks_flashcards/run.py index ec689c5b80..6ec8227f22 100644 --- a/demo/blocks_flashcards/run.py +++ b/demo/blocks_flashcards/run.py @@ -4,90 +4,113 @@ import gradio as gr demo = gr.Blocks() +def is_data_empty(flashcards): + if isinstance(flashcards, dict): + return all(item == '' for sublist in flashcards['data'] for item in sublist) + elif isinstance(flashcards, list): + return all(all(item == '' for item in sublist) for sublist in flashcards) + else: + return True + with demo: gr.Markdown( "Load the flashcards in the table below, then use the Practice tab to practice." ) - with gr.Tab("Word Bank"): - flashcards_table = gr.Dataframe(headers=["front", "back"], type="array") - with gr.Tab("Practice"): - with gr.Row(): - with gr.Column(): - front = gr.Textbox(label="Prompt") - with gr.Row(): - new_btn = gr.Button("New Card") - flip_btn = gr.Button("Flip Card") - with gr.Column(visible=False) as answer_col: - back = gr.Textbox(label="Answer") - selected_card = gr.State() + with gr.Tabs() as tabs: + with gr.Tab("Word Bank"): + flashcards_table = gr.Dataframe(headers=["front", "back"], type="array") + flashcards_table.change(fn=lambda: print(flashcards_table.value)) + practice_btn = gr.Button("Start Practice") + with gr.Tab("Practice", interactive=False, id=1) as practice_tab: + with gr.Row(): + with gr.Column(): + front = gr.Textbox(label="Prompt") + with gr.Row(): + new_btn = gr.Button("New Card") + flip_btn = gr.Button("Flip Card") + with gr.Column(visible=False) as answer_col: + back = gr.Textbox(label="Answer") + selected_card = gr.State() with gr.Row(): correct_btn = gr.Button("Correct") incorrect_btn = gr.Button("Incorrect") - with gr.Tab("Results", visible=False) as results_tab: - results = gr.State(value={}) - correct_field = gr.Markdown("# Correct: 0") - incorrect_field = gr.Markdown("# Incorrect: 0") - gr.Markdown("Card Statistics: ") - results_table = gr.Dataframe(headers=["Card", "Correct", "Incorrect"]) + def start_practice(flashcards): + # if no cards entered into dataframe yet, return + if is_data_empty(flashcards): + practice_tab = gr.Tab("Practice", interactive=False, id=1) + raise gr.Error("Please enter word prompts into the table.") + return [practice_tab, tabs] + else: + practice_tab = gr.Tab("Practice", interactive=True, id=1) + new_tabs = gr.Tabs(selected=1) + return [practice_tab, new_tabs] - def load_new_card(flashcards): - card = random.choice(flashcards) - return ( - card, - card[0], - gr.Column(visible=False), + with gr.Tab("Results", visible=False) as results_tab: + results = gr.State(value={}) + correct_field = gr.Markdown("# Correct: 0") + incorrect_field = gr.Markdown("# Incorrect: 0") + gr.Markdown("Card Statistics: ") + results_table = gr.Dataframe(headers=["Card", "Correct", "Incorrect"]) + practice_btn.click(start_practice, inputs=[flashcards_table], outputs=[practice_tab, tabs]) + + def load_new_card(flashcards): + card = random.choice(flashcards) + return ( + card, + card[0], + gr.Column(visible=False), + ) + + new_btn.click( + load_new_card, + [flashcards_table], + [selected_card, front, answer_col], ) - new_btn.click( - load_new_card, - [flashcards_table], - [selected_card, front, answer_col], - ) + def flip_card(card): + return card[1], gr.Column(visible=True) - def flip_card(card): - return card[1], gr.Column(visible=True) + flip_btn.click(flip_card, [selected_card], [back, answer_col]) - flip_btn.click(flip_card, [selected_card], [back, answer_col]) + def mark_correct(card, results): + if card[0] not in results: + results[card[0]] = [0, 0] + results[card[0]][0] += 1 + correct_count = sum(result[0] for result in results.values()) + return ( + results, + f"# Correct: {correct_count}", + [[front, scores[0], scores[1]] for front, scores in results.items()], + ) - def mark_correct(card, results): - if card[0] not in results: - results[card[0]] = [0, 0] - results[card[0]][0] += 1 - correct_count = sum(result[0] for result in results.values()) - return ( - results, - f"# Correct: {correct_count}", - [[front, scores[0], scores[1]] for front, scores in results.items()], + def mark_incorrect(card, results): + if card[0] not in results: + results[card[0]] = [ + 0, 0] + results[card[0]][1] += 1 + incorrect_count = sum(result[1] for result in results.values()) + return ( + results, + f"# Inorrect: {incorrect_count}", + [[front, scores[0], scores[1]] for front, scores in results.items()], + ) + + def toggle_results_tab(): + return gr.Tab("Results", visible=True) + + correct_btn.click( + mark_correct, + [selected_card, results], + [results, correct_field, results_table], ) - def mark_incorrect(card, results): - if card[0] not in results: - results[card[0]] = [ - 0, 0] - results[card[0]][1] += 1 - incorrect_count = sum(result[1] for result in results.values()) - return ( - results, - f"# Inorrect: {incorrect_count}", - [[front, scores[0], scores[1]] for front, scores in results.items()], - ) + incorrect_btn.click(mark_incorrect, [selected_card, results], [results, incorrect_field, results_table]) - def toggle_results_tab(): - return gr.Tab("Results", visible=True) - - correct_btn.click( - mark_correct, - [selected_card, results], - [results, correct_field, results_table], - ) - - incorrect_btn.click(mark_incorrect, [selected_card, results], [results, incorrect_field, results_table]) - - # set results tab to visible when correct or incorrect button is clicked - correct_btn.click(fn=toggle_results_tab, outputs=[results_tab]) - incorrect_btn.click(fn=toggle_results_tab, outputs=[results_tab]) + # set results tab to visible when correct or incorrect button is clicked + correct_btn.click(fn=toggle_results_tab, outputs=[results_tab]) + incorrect_btn.click(fn=toggle_results_tab, outputs=[results_tab]) if __name__ == "__main__": demo.launch() diff --git a/js/app/test/blocks_flashcards.spec.ts b/js/app/test/blocks_flashcards.spec.ts index bc52afd2d1..686385c743 100644 --- a/js/app/test/blocks_flashcards.spec.ts +++ b/js/app/test/blocks_flashcards.spec.ts @@ -1,6 +1,12 @@ -import { test } from "@gradio/tootils"; +import { test, expect } from "@gradio/tootils"; test("shows the results tab when results > 0", async ({ page }) => { + await page.getByRole("button", { name: "Start Practice" }).click(); + await expect( + page.getByText("Please enter word prompts into the table.") + ).toBeAttached(); + await page.getByLabel("Close").click(); + await page .getByRole("button", { name: "front back" }) .getByRole("button") @@ -35,10 +41,14 @@ test("shows the results tab when results > 0", async ({ page }) => { await page.getByText("New row").click(); - await page.getByRole("tab", { name: "Practice" }).click(); - await page.getByRole("button", { name: "New Card" }).click(); await page.waitForTimeout(1000); - await page.getByRole("button", { name: "Flip Card" }).click(); - await page.getByRole("button", { name: "Correct", exact: true }).click(); - await page.getByRole("tab", { name: "Results" }).click(); + await page.getByText("Start Practice").dblclick(); + + await page.waitForTimeout(5000); + + // await page.getByRole("button", { name: "New Card" }).click(); + // await page.waitForTimeout(1000); + // await page.getByRole("button", { name: "Flip Card" }).click(); + // await page.getByRole("button", { name: "Correct", exact: true }).click(); + // await page.getByRole("tab", { name: "Results" }).click(); }); diff --git a/js/tabs/shared/Tabs.svelte b/js/tabs/shared/Tabs.svelte index 7bf826e5b9..db73eb20ea 100644 --- a/js/tabs/shared/Tabs.svelte +++ b/js/tabs/shared/Tabs.svelte @@ -68,13 +68,22 @@ }); function change_tab(id: object | string | number): void { - selected = id; - $selected_tab = id; - $selected_tab_index = tabs.findIndex((t) => t.id === id); - dispatch("change"); + const tab_to_activate = tabs.find((t) => t.id === id); + if ( + tab_to_activate && + tab_to_activate.interactive && + tab_to_activate.visible + ) { + selected = id; + $selected_tab = id; + $selected_tab_index = tabs.findIndex((t) => t.id === id); + dispatch("change"); + } else { + console.warn("Attempted to select a non-interactive or hidden tab."); + } } - $: selected !== null && change_tab(selected); + $: tabs, selected !== null && change_tab(selected);