diff --git a/CHANGELOG.md b/CHANGELOG.md index 46d3d00d60..55d4ff355b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Use Gradio API server to send telemetry using `huggingface_hub` [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3488](https://github.com/gradio-app/gradio/pull/3488) - Fixes an an issue where if the Blocks scope was not exited, then State could be shared across sessions, by [@abidlabs](https://github.com/abidlabs) in [PR 3600](https://github.com/gradio-app/gradio/pull/3600) - Fixed bug where "or" was not being localized in file upload text by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3599](https://github.com/gradio-app/gradio/pull/3599) +- Ensure CSS has fully loaded before rendering the application, by [@pngwn](https://github.com/pngwn) in [PR 3573](https://github.com/gradio-app/gradio/pull/3573) ## Documentation Changes: diff --git a/demo/code/run.ipynb b/demo/code/run.ipynb index e5bd0e340e..815730225e 100644 --- a/demo/code/run.ipynb +++ b/demo/code/run.ipynb @@ -1 +1 @@ -{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: code"]}, {"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": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/code/file.css"]}, {"cell_type": "code", "execution_count": null, "id": 44380577570523278879349135829904343037, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "from time import sleep\n", "\n", "\n", "css_file = os.path.join(os.path.abspath(''), \"file.css\")\n", "\n", "\n", "def set_lang(language):\n", " print(language)\n", " return gr.Code.update(language=language)\n", "\n", "\n", "def set_lang_from_path():\n", " sleep(1)\n", " return gr.Code.update((css_file, ), language=\"css\")\n", "\n", "\n", "def code(language, code):\n", " return gr.Code.update(code, language=language)\n", "\n", "\n", "io = gr.Interface(lambda x: x, \"code\", \"code\")\n", "\n", "with gr.Blocks() as demo:\n", " lang = gr.Dropdown(value=\"python\", choices=gr.Code.languages)\n", " with gr.Row():\n", " code_in = gr.Code(language=\"python\", label=\"Input\")\n", " code_out = gr.Code(label=\"Ouput\")\n", " btn = gr.Button(\"Run\")\n", " btn_two = gr.Button(\"Load File\")\n", "\n", " lang.change(set_lang, inputs=lang, outputs=code_in)\n", " btn.click(code, inputs=[lang, code_in], outputs=code_out)\n", " btn_two.click(set_lang_from_path, inputs=None, outputs=code_out)\n", " io.render()\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: code"]}, {"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": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/code/file.css"]}, {"cell_type": "code", "execution_count": null, "id": 44380577570523278879349135829904343037, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "from time import sleep\n", "\n", "\n", "css_file = os.path.join(os.path.abspath(''), \"file.css\")\n", "\n", "\n", "def set_lang(language):\n", " print(language)\n", " return gr.Code.update(language=language)\n", "\n", "\n", "def set_lang_from_path():\n", " sleep(1)\n", " return gr.Code.update((css_file,), language=\"css\")\n", "\n", "\n", "def code(language, code):\n", " return gr.Code.update(code, language=language)\n", "\n", "\n", "io = gr.Interface(lambda x: x, \"code\", \"code\")\n", "\n", "with gr.Blocks() as demo:\n", " lang = gr.Dropdown(value=\"python\", choices=gr.Code.languages)\n", " with gr.Row():\n", " code_in = gr.Code(\n", " language=\"python\",\n", " label=\"Input\",\n", " value='def all_odd_elements(sequence):\\n \"\"\"Returns every odd element of the sequence.\"\"\"',\n", " )\n", " code_out = gr.Code(label=\"Ouput\")\n", " btn = gr.Button(\"Run\")\n", " btn_two = gr.Button(\"Load File\")\n", "\n", " lang.change(set_lang, inputs=lang, outputs=code_in)\n", " btn.click(code, inputs=[lang, code_in], outputs=code_out)\n", " btn_two.click(set_lang_from_path, inputs=None, outputs=code_out)\n", " io.render()\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/code/run.py b/demo/code/run.py index cbbe334204..f7a7f78d57 100644 --- a/demo/code/run.py +++ b/demo/code/run.py @@ -13,7 +13,7 @@ def set_lang(language): def set_lang_from_path(): sleep(1) - return gr.Code.update((css_file, ), language="css") + return gr.Code.update((css_file,), language="css") def code(language, code): @@ -25,7 +25,11 @@ io = gr.Interface(lambda x: x, "code", "code") with gr.Blocks() as demo: lang = gr.Dropdown(value="python", choices=gr.Code.languages) with gr.Row(): - code_in = gr.Code(language="python", label="Input") + code_in = gr.Code( + language="python", + label="Input", + value='def all_odd_elements(sequence):\n """Returns every odd element of the sequence."""', + ) code_out = gr.Code(label="Ouput") btn = gr.Button("Run") btn_two = gr.Button("Load File") diff --git a/style.md b/style.md index 37a54f3a37..4d299fff6b 100644 --- a/style.md +++ b/style.md @@ -157,4 +157,4 @@ Nothing ## Variable -Nothing +Nothing \ No newline at end of file diff --git a/ui/packages/app/src/Index.svelte b/ui/packages/app/src/Index.svelte index 34b4ba9c84..70d0e315cc 100644 --- a/ui/packages/app/src/Index.svelte +++ b/ui/packages/app/src/Index.svelte @@ -90,22 +90,28 @@ let config: Config; let loading_text: string = "Loading..."; - function mount_custom_css(target: HTMLElement, css_string: string | null) { + async function mount_custom_css( + target: HTMLElement, + css_string: string | null + ) { if (css_string) { let style = document.createElement("style"); style.innerHTML = css_string; target.appendChild(style); } - mount_css(config.root + "/theme.css", document.head); + await mount_css(config.root + "/theme.css", document.head); if (!config.stylesheets) return; - for (let stylesheet of config.stylesheets) { - let absolute_link = - stylesheet.startsWith("http:") || stylesheet.startsWith("https:"); - mount_css( - absolute_link ? stylesheet : config.root + "/" + stylesheet, - document.head - ); - } + + await Promise.all( + config.stylesheets.map((stylesheet) => { + let absolute_link = + stylesheet.startsWith("http:") || stylesheet.startsWith("https:"); + return mount_css( + absolute_link ? stylesheet : config.root + "/" + stylesheet, + document.head + ); + }) + ); } async function reload_check(root: string) { @@ -178,6 +184,7 @@ }; let app: Awaited>; + let css_ready = false; function handle_status(_status: SpaceStatus) { status = _status; } @@ -201,7 +208,8 @@ detail: "RUNNING" }; - mount_custom_css(wrapper, config.css); + await mount_custom_css(wrapper, config.css); + css_ready = true; window.__is_colab__ = config.is_colab; if (config.dev_mode) { @@ -314,7 +322,7 @@ is_space={config.is_space} {app_mode} /> - {:else if config && Blocks} + {:else if config && Blocks && css_ready} { @@ -28,7 +29,8 @@ function mock_api(page: Page, body: Array) { test("renders the correct elements", async ({ page }) => { await mock_demo(page, "blocks_inputs"); await mock_api(page, [["hi dawood"]]); - await page.goto("http://localhost:9876"); + await mock_theme(page); + await wait_for_page(page); const textboxes = await page.getByLabel("Input"); diff --git a/ui/packages/app/test/blocks_kinematics.spec.ts b/ui/packages/app/test/blocks_kinematics.spec.ts index 238704091a..ac6c479e8d 100644 --- a/ui/packages/app/test/blocks_kinematics.spec.ts +++ b/ui/packages/app/test/blocks_kinematics.spec.ts @@ -1,4 +1,5 @@ import { test, expect, Page } from "@playwright/test"; +import { mock_theme, wait_for_page } from "./utils"; function mock_demo(page: Page, demo: string) { return page.route("**/config", (route) => { @@ -28,7 +29,8 @@ function mock_api(page: Page, body: Array) { test("renders the correct elements", async ({ page }) => { await mock_demo(page, "blocks_kinematics"); await mock_api(page, [[25, 45]]); - await page.goto("http://localhost:9876"); + await mock_theme(page); + await wait_for_page(page); await Promise.all([ page.click("button:has-text('Run')"), diff --git a/ui/packages/app/test/blocks_page_load.spec.ts b/ui/packages/app/test/blocks_page_load.spec.ts index e246a4bbed..31ee16c501 100644 --- a/ui/packages/app/test/blocks_page_load.spec.ts +++ b/ui/packages/app/test/blocks_page_load.spec.ts @@ -1,4 +1,5 @@ import { test, expect, Page } from "@playwright/test"; +import { mock_theme, wait_for_page } from "./utils"; function mock_demo(page: Page, demo: string) { return page.route("**/config", (route) => { @@ -28,7 +29,8 @@ function mock_api(page: Page, body: Array) { test("renders the correct elements", async ({ page }) => { await mock_demo(page, "blocks_page_load"); await mock_api(page, [["Welcome! This page has loaded for Frank"]]); - await page.goto("http://localhost:9876"); + await mock_theme(page); + await wait_for_page(page); const textbox = await page.getByLabel("Name"); diff --git a/ui/packages/app/test/blocks_xray.spec.ts b/ui/packages/app/test/blocks_xray.spec.ts index 7ddd7b5eb7..dbf4343780 100644 --- a/ui/packages/app/test/blocks_xray.spec.ts +++ b/ui/packages/app/test/blocks_xray.spec.ts @@ -1,4 +1,5 @@ import { test, expect, Page } from "@playwright/test"; +import { mock_theme, wait_for_page } from "./utils"; function mock_demo(page: Page, demo: string) { return page.route("**/config", (route) => { @@ -27,7 +28,8 @@ function mock_api(page: Page, body: Array) { test("renders the correct elements", async ({ page }) => { await mock_demo(page, "blocks_xray"); - await page.goto("http://localhost:9876"); + await mock_theme(page); + await wait_for_page(page); const description = await page.getByTestId("markdown"); await expect(description).toContainText("Detect Disease From Scan"); @@ -56,7 +58,8 @@ test("can run an api request and display the data", async ({ page }) => { ] ]); - await page.goto("http://localhost:9876"); + await mock_theme(page); + await wait_for_page(page); await page.getByLabel("Covid").check(); await page.getByLabel("Lung Cancer").check(); diff --git a/ui/packages/app/test/input_output.spec.ts b/ui/packages/app/test/input_output.spec.ts index bb3b4dc128..39bc6a5536 100644 --- a/ui/packages/app/test/input_output.spec.ts +++ b/ui/packages/app/test/input_output.spec.ts @@ -1,4 +1,5 @@ import { test, expect, Page } from "@playwright/test"; +import { mock_theme, wait_for_page } from "./utils"; function mock_demo(page: Page, demo: string) { return page.route("**/config", (route) => { @@ -28,7 +29,8 @@ function mock_api(page: Page, body: Array) { test("a component acts as both input and output", async ({ page }) => { await mock_demo(page, "input_output"); await mock_api(page, [["tset"]]); - await page.goto("http://localhost:9876"); + await mock_theme(page); + await wait_for_page(page); const textbox = await page.getByLabel("Input-Output"); diff --git a/ui/packages/app/test/kitchen_sink.spec.ts b/ui/packages/app/test/kitchen_sink.spec.ts index 4383718683..46b4b45b73 100644 --- a/ui/packages/app/test/kitchen_sink.spec.ts +++ b/ui/packages/app/test/kitchen_sink.spec.ts @@ -1,5 +1,6 @@ import { test, expect, Page } from "@playwright/test"; import { BASE64_IMAGE, BASE64_AUDIO } from "./media_data"; +import { mock_theme, wait_for_page } from "./utils"; function mock_demo(page: Page, demo: string) { return page.route("**/config", (route) => { @@ -28,7 +29,8 @@ function mock_api(page: Page, body: Array) { test("test inputs", async ({ page }) => { await mock_demo(page, "kitchen_sink"); - await page.goto("http://localhost:9876"); + await mock_theme(page); + await wait_for_page(page); const textbox = await page.getByLabel("Textbox").nth(0); await expect(textbox).toHaveValue("Lorem ipsum"); @@ -209,7 +211,8 @@ test("test outputs", async ({ page }) => { ] ]); - await page.goto("http://localhost:9876"); + await mock_theme(page); + await wait_for_page(page); const submit_button = await page.locator("button", { hasText: /Submit/ }); diff --git a/ui/packages/app/test/outbreak_forecast.spec.ts b/ui/packages/app/test/outbreak_forecast.spec.ts index dd37214700..da988a8876 100644 --- a/ui/packages/app/test/outbreak_forecast.spec.ts +++ b/ui/packages/app/test/outbreak_forecast.spec.ts @@ -1,5 +1,6 @@ import { test, expect, Page } from "@playwright/test"; import { BASE64_PLOT_IMG } from "./media_data"; +import { mock_theme, wait_for_page } from "./utils"; function mock_demo(page: Page, demo: string) { return page.route("**/config", (route) => { @@ -12,17 +13,6 @@ function mock_demo(page: Page, demo: string) { }); } -function mock_theme(page: Page) { - return page.route("**/theme.css", (route) => { - return route.fulfill({ - headers: { - "Access-Control-Allow-Origin": "*" - }, - path: `./test/mocks/theme.css` - }); - }); -} - function mock_api(page: Page, body: Array) { return page.route("**/run/predict", (route) => { const id = JSON.parse(route.request().postData()!).fn_index; @@ -41,7 +31,7 @@ test("matplotlib", async ({ page }) => { await mock_demo(page, "outbreak_forecast"); await mock_api(page, [[{ type: "matplotlib", plot: BASE64_PLOT_IMG }]]); await mock_theme(page); - await page.goto("http://localhost:9876"); + await wait_for_page(page); await page.getByLabel("Plot Type").click(); await page.getByRole("button", { name: "Matplotlib" }).click(); diff --git a/ui/packages/app/test/slider_release.spec.ts b/ui/packages/app/test/slider_release.spec.ts index 1413df92ea..5f7beb57d4 100644 --- a/ui/packages/app/test/slider_release.spec.ts +++ b/ui/packages/app/test/slider_release.spec.ts @@ -1,4 +1,5 @@ import { test, expect, Page, Locator } from "@playwright/test"; +import { mock_theme, wait_for_page } from "./utils"; //taken from: https://github.com/microsoft/playwright/issues/20032 async function changeSlider( @@ -59,8 +60,8 @@ function mock_api(page: Page) { test("slider release", async ({ page }) => { await mock_demo(page, "slider_release"); await mock_api(page); - await page.goto("http://localhost:9876"); - + await mock_theme(page); + await wait_for_page(page); const slider = page.getByLabel("Slider"); await changeSlider(page, slider, slider, 0.7); diff --git a/ui/packages/app/test/utils.ts b/ui/packages/app/test/utils.ts new file mode 100644 index 0000000000..d89e0e0372 --- /dev/null +++ b/ui/packages/app/test/utils.ts @@ -0,0 +1,17 @@ +import type { Page } from "@playwright/test"; + +export function mock_theme(page: Page) { + return page.route("**/theme.css", (route) => { + return route.fulfill({ + headers: { + "Access-Control-Allow-Origin": "*" + }, + path: `./test/mocks/theme.css` + }); + }); +} + +export async function wait_for_page(page: Page) { + await page.goto("http://localhost:9876"); + await page.waitForResponse("**/theme.css"); +}