Skip to content

Commit

Permalink
Add render function to <gradio-app> (#5158)
Browse files Browse the repository at this point in the history
* add function on_load to gradio-app web element

* typo

* fix test

* add ready event

* add custom events and use onMount

* add changeset

* add changeset

* move event logic

* add changeset

* clean up whitespace

* add onloadcomplete to guide

* add changeset

* add changeset

* add highlight

* change event name to render and ensure it runs after DOM mounting is complete

* update docs

* rename var

* tweak

* renaming

* tweak

* tweaks

* mount statustracker nodes

* typo

* tweaks

* copy tweak

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
  • Loading branch information
hannahblair and gradio-pr-bot committed Aug 22, 2023
1 parent 92282ce commit 804fcc0
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 7 deletions.
20 changes: 20 additions & 0 deletions .changeset/stale-houses-grin.md
@@ -0,0 +1,20 @@
---
"@gradio/app": minor
"gradio": minor
---

highlight:

#### Add `render` function to `<gradio-app>`

We now have an event `render` on the <gradio-app> web component, which is triggered once the embedded space has finished rendering.

```html
<script>
function handleLoadComplete() {
console.log("Embedded space has finished rendering");
}
const gradioApp = document.querySelector("gradio-app");
gradioApp.addEventListener("render", handleLoadComplete);
</script>
```
14 changes: 14 additions & 0 deletions guides/01_getting-started/03_sharing-your-app.md
Expand Up @@ -108,6 +108,7 @@ You can also customize the appearance and behavior of your web component with at
- `autoscroll`: whether to autoscroll to the output when prediction has finished (by default `"false"`)
- `eager`: whether to load the Gradio app as soon as the page loads (by default `"false"`)
- `theme_mode`: whether to use the `dark`, `light`, or default `system` theme mode (by default `"system"`)
- `render`: an event that is triggered once the embedded space has finished rendering.

Here's an example of how to use these attributes to create a Gradio app that does not lazy load and has an initial height of 0px.

Expand All @@ -119,6 +120,19 @@ Here's an example of how to use these attributes to create a Gradio app that doe
></gradio-app>
```

Here's another example of how to use the `render` event. An event listener is used to capture the `render` event and will call the `handleLoadComplete()` function once rendering is complete.

```html
<script>
function handleLoadComplete() {
console.log("Embedded space has finished rendering");
}
const gradioApp = document.querySelector("gradio-app");
gradioApp.addEventListener("render", handleLoadComplete);
</script>
```

_Note: While Gradio's CSS will never impact the embedding page, the embedding page can affect the style of the embedded Gradio app. Make sure that any CSS in the parent page isn't so general that it could also apply to the embedded Gradio app and cause the styling to break. Element selectors such as `header { ... }` and `footer { ... }` will be the most likely to cause issues._

### Embedding with IFrames
Expand Down
19 changes: 18 additions & 1 deletion js/app/src/Blocks.svelte
Expand Up @@ -19,6 +19,7 @@
import { Toast } from "@gradio/statustracker";
import type { ToastMessage } from "@gradio/statustracker";
import type { ShareData } from "@gradio/utils";
import { dequal } from "dequal";
import logo from "./images/logo.svg";
import api_logo from "./api_docs/img/api-logo.svg";
Expand All @@ -44,6 +45,9 @@
let loading_status = create_loading_status_store();
const walked_node_ids = new Set();
const mounted_node_ids = new Set();
$: app_state.update((s) => ({ ...s, autoscroll }));
let rootNode: ComponentMeta = {
Expand Down Expand Up @@ -180,6 +184,8 @@
async function walk_layout(node: LayoutNode): Promise<void> {
let instance = instance_map[node.id];
walked_node_ids.add(node.id);
const _component = (await _component_map.get(
`${instance.type}_${_type_for_id.get(node.id) || "static"}`
))!.component;
Expand Down Expand Up @@ -209,6 +215,7 @@
});
export let ready = false;
export let render_complete = false;
Promise.all(Array.from(component_set)).then(() => {
walk_layout(layout)
.then(async () => {
Expand Down Expand Up @@ -490,7 +497,9 @@
let attached_error_listeners: number[] = [];
let shareable_components: number[] = [];
async function handle_mount(): Promise<void> {
async function handle_mount({ detail }: { detail: number }): Promise<void> {
mounted_node_ids.add(detail);
await tick();
var a = target.getElementsByTagName("a");
Expand Down Expand Up @@ -564,6 +573,8 @@
}
}
});
checkRenderCompletion();
}
function handle_destroy(id: number): void {
Expand Down Expand Up @@ -592,6 +603,12 @@
set_prop(instance_map[id], "pending", pending_status === "pending");
}
}
function checkRenderCompletion(): void {
if (dequal(walked_node_ids, mounted_node_ids)) {
render_complete = true;
}
}
</script>

<svelte:head>
Expand Down
12 changes: 12 additions & 0 deletions js/app/src/Index.svelte
Expand Up @@ -98,6 +98,7 @@
let app_id: string | null = null;
let wrapper: HTMLDivElement;
let ready = false;
let render_complete = false;
let config: Config;
let loading_text = $_("common.loading") + "...";
let active_theme_mode: ThemeMode;
Expand Down Expand Up @@ -285,6 +286,16 @@
onMount(async () => {
intersecting.register(_id, wrapper);
});
$: if (render_complete) {
wrapper.dispatchEvent(
new CustomEvent("render", {
bubbles: true,
cancelable: false,
composed: true,
})
);
}
</script>

<Embed
Expand Down Expand Up @@ -345,6 +356,7 @@
target={wrapper}
{autoscroll}
bind:ready
bind:render_complete
show_footer={!is_embed}
{app_mode}
/>
Expand Down
21 changes: 19 additions & 2 deletions js/app/src/Render.svelte
Expand Up @@ -17,16 +17,33 @@
export let theme_mode: ThemeMode;
const dispatch = createEventDispatcher<{ mount: number; destroy: number }>();
let filtered_children: ComponentMeta[] = [];
onMount(() => {
dispatch("mount", id);
return () => dispatch("destroy", id);
for (const child of filtered_children) {
dispatch("mount", child.id);
}
return () => {
dispatch("destroy", id);
for (const child of filtered_children) {
dispatch("mount", child.id);
}
};
});
$: children =
children &&
children.filter((v) => instance_map[v.id].type !== "statustracker");
children.filter((v) => {
const valid_node = instance_map[v.id].type !== "statustracker";
if (!valid_node) {
filtered_children.push(v);
}
return valid_node;
});
setContext("BLOCK_KEY", parent);
Expand Down
2 changes: 0 additions & 2 deletions js/app/src/main.ts
Expand Up @@ -73,8 +73,6 @@ function create_custom_element(): void {

observer.observe(this, { childList: true });

// if (this.)

this.app = new Index({
target: this,
props: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -56,6 +56,7 @@
"autoprefixer": "^10.4.4",
"babylonjs": "^5.17.1",
"babylonjs-loaders": "^5.17.1",
"dequal": "^2.0.2",
"eslint": "^8.46.0",
"eslint-plugin-svelte": "^2.32.4",
"globals": "^13.20.0",
Expand Down
7 changes: 5 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 804fcc0

Please sign in to comment.