mirror of
https://github.com/gradio-app/gradio.git
synced 2025-02-17 11:29:58 +08:00
Lite: Support the custom HTML element syntax <gradio-lite>
(#5953)
* Create a custom element `<gradio-app`> for Gradio-lite * Parse `<gradio-app`> attributes to configure the top level component * Move custom-element code to lite/custom-element module * add changeset * Apply formatter * Rename the custom element `<gradio-app>` to `<gradio-lite>` * Fix the .gradio-lite to be minimum * add changeset --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
This commit is contained in:
parent
6780d660bb
commit
921334526f
7
.changeset/brave-cats-show.md
Normal file
7
.changeset/brave-cats-show.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
"@gradio/app": minor
|
||||
"@gradio/theme": minor
|
||||
"gradio": minor
|
||||
---
|
||||
|
||||
feat:Lite: Support the custom HTML element syntax `<gradio-lite>`
|
163
js/app/src/lite/custom-element/index.ts
Normal file
163
js/app/src/lite/custom-element/index.ts
Normal file
@ -0,0 +1,163 @@
|
||||
import { create, type Options } from "..";
|
||||
|
||||
interface GradioComponentOptions {
|
||||
info: Options["info"];
|
||||
container: Options["container"];
|
||||
isEmbed: Options["isEmbed"];
|
||||
initialHeight?: Options["initialHeight"];
|
||||
eager: Options["eager"];
|
||||
themeMode: Options["themeMode"];
|
||||
autoScroll: Options["autoScroll"];
|
||||
controlPageTitle: Options["controlPageTitle"];
|
||||
appMode: Options["appMode"];
|
||||
}
|
||||
|
||||
interface GradioLiteAppOptions {
|
||||
files?: Options["files"];
|
||||
requirements?: Options["requirements"];
|
||||
code?: Options["code"];
|
||||
entrypoint?: Options["entrypoint"];
|
||||
}
|
||||
|
||||
function parseRequirementsTxt(content: string): string[] {
|
||||
return content
|
||||
.split("\n")
|
||||
.filter((r) => !r.startsWith("#"))
|
||||
.map((r) => r.trim())
|
||||
.filter((r) => r !== "");
|
||||
}
|
||||
|
||||
export function bootstrap_custom_element(): void {
|
||||
const CUSTOM_ELEMENT_NAME = "gradio-lite";
|
||||
|
||||
if (customElements.get(CUSTOM_ELEMENT_NAME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
class GradioLiteAppElement extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const gradioComponentOptions = this.parseGradioComponentOptions();
|
||||
const gradioLiteAppOptions = this.parseGradioLiteAppOptions();
|
||||
|
||||
this.innerHTML = "";
|
||||
|
||||
create({
|
||||
target: this, // Same as `js/app/src/main.ts`
|
||||
code: gradioLiteAppOptions.code,
|
||||
requirements: gradioLiteAppOptions.requirements,
|
||||
files: gradioLiteAppOptions.files,
|
||||
entrypoint: gradioLiteAppOptions.entrypoint,
|
||||
...gradioComponentOptions
|
||||
});
|
||||
}
|
||||
|
||||
parseGradioComponentOptions(): GradioComponentOptions {
|
||||
// Parse the options from the attributes of the <gradio-lite> element.
|
||||
// The following attributes are supported:
|
||||
// * info: boolean
|
||||
// * container: boolean
|
||||
// * embed: boolean
|
||||
// * initial-height: string
|
||||
// * eager: boolean
|
||||
// * theme: "light" | "dark" | null
|
||||
// * auto-scroll: boolean
|
||||
// * control-page-title: boolean
|
||||
// * app-mode: boolean
|
||||
|
||||
const info = this.hasAttribute("info");
|
||||
const container = this.hasAttribute("container");
|
||||
const isEmbed = this.hasAttribute("embed");
|
||||
const initialHeight = this.getAttribute("initial-height");
|
||||
const eager = this.hasAttribute("eager");
|
||||
const themeMode = this.getAttribute("theme");
|
||||
const autoScroll = this.hasAttribute("auto-scroll");
|
||||
const controlPageTitle = this.hasAttribute("control-page-title");
|
||||
const appMode = this.hasAttribute("app-mode");
|
||||
|
||||
return {
|
||||
info,
|
||||
container,
|
||||
isEmbed,
|
||||
initialHeight: initialHeight ?? undefined,
|
||||
eager,
|
||||
themeMode:
|
||||
themeMode != null && ["light", "dark"].includes(themeMode)
|
||||
? (themeMode as GradioComponentOptions["themeMode"])
|
||||
: null,
|
||||
autoScroll,
|
||||
controlPageTitle,
|
||||
appMode
|
||||
};
|
||||
}
|
||||
|
||||
parseGradioLiteAppOptions(): GradioLiteAppOptions {
|
||||
// When gradioLiteAppElement only contains text content, it is treated as the Python code.
|
||||
if (this.childElementCount === 0) {
|
||||
return { code: this.textContent ?? "" };
|
||||
}
|
||||
|
||||
// When it contains child elements, parse them as options. Available child elements are:
|
||||
// * <gradio-file />
|
||||
// Represents a file to be mounted in the virtual file system of the Wasm worker.
|
||||
// At least 1 <gradio-file> element must have the `entrypoint` attribute.
|
||||
// The following 2 forms are supported:
|
||||
// * <gradio-file name="{file name}" >{file content}</gradio-file>
|
||||
// * <gradio-file name="{file name}" url="{remote URL}" />
|
||||
// * <gradio-requirements>{requirements.txt}</gradio-requirements>
|
||||
// * <gradio-code>{Python code}</gradio-code>
|
||||
const options: GradioLiteAppOptions = {};
|
||||
|
||||
const fileElements = this.getElementsByTagName("gradio-file");
|
||||
for (const fileElement of fileElements) {
|
||||
const name = fileElement.getAttribute("name");
|
||||
if (name == null) {
|
||||
throw new Error("<gradio-file> must have the name attribute.");
|
||||
}
|
||||
|
||||
const entrypoint = fileElement.hasAttribute("entrypoint");
|
||||
const url = fileElement.getAttribute("url");
|
||||
|
||||
options.files ??= {};
|
||||
if (url != null) {
|
||||
options.files[name] = { url };
|
||||
} else {
|
||||
options.files[name] = { data: fileElement.textContent ?? "" };
|
||||
}
|
||||
|
||||
if (entrypoint) {
|
||||
if (options.entrypoint != null) {
|
||||
throw new Error("Multiple entrypoints are not allowed.");
|
||||
}
|
||||
options.entrypoint = name;
|
||||
}
|
||||
}
|
||||
|
||||
const codeElements = this.getElementsByTagName("gradio-code");
|
||||
if (codeElements.length > 1) {
|
||||
console.warn(
|
||||
"Multiple <gradio-code> elements are found. Only the first one will be used."
|
||||
);
|
||||
}
|
||||
const firstCodeElement = codeElements[0];
|
||||
options.code = firstCodeElement?.textContent ?? undefined;
|
||||
|
||||
const requirementsElements = this.getElementsByTagName(
|
||||
"gradio-requirements"
|
||||
);
|
||||
if (requirementsElements.length > 1) {
|
||||
console.warn(
|
||||
"Multiple <gradio-requirements> elements are found. Only the first one will be used."
|
||||
);
|
||||
}
|
||||
const firstRequirementsElement = requirementsElements[0];
|
||||
const requirementsTxt = firstRequirementsElement?.textContent ?? "";
|
||||
options.requirements = parseRequirementsTxt(requirementsTxt);
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(CUSTOM_ELEMENT_NAME, GradioLiteAppElement);
|
||||
}
|
@ -7,6 +7,7 @@ import { wasm_proxied_mount_css, mount_prebuilt_css } from "./css";
|
||||
import type { mount_css } from "../css";
|
||||
import Index from "../Index.svelte";
|
||||
import type { ThemeMode } from "../components/types";
|
||||
import { bootstrap_custom_element } from "./custom-element";
|
||||
|
||||
// These imports are aliased at built time with Vite. See the `resolve.alias` config in `vite.config.ts`.
|
||||
import gradioWheel from "gradio.whl";
|
||||
@ -43,7 +44,7 @@ interface GradioAppController {
|
||||
unmount: () => void;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
export interface Options {
|
||||
target: HTMLElement;
|
||||
files?: WorkerProxyOptions["files"];
|
||||
requirements?: WorkerProxyOptions["requirements"];
|
||||
@ -191,6 +192,8 @@ export function create(options: Options): GradioAppController {
|
||||
// @ts-ignore
|
||||
globalThis.createGradioApp = create;
|
||||
|
||||
bootstrap_custom_element();
|
||||
|
||||
declare let BUILD_MODE: string;
|
||||
if (BUILD_MODE === "dev") {
|
||||
(async function () {
|
||||
|
@ -108,6 +108,11 @@ export default defineConfig(({ mode }) => {
|
||||
fileName.indexOf(".svelte") > -1
|
||||
) {
|
||||
return selector;
|
||||
} else if (
|
||||
// For the custom element <gradio-lite>. See theme/src/global.css for the details.
|
||||
/^gradio-lite(\:[^\:]+)?/.test(selector)
|
||||
) {
|
||||
return selector;
|
||||
}
|
||||
return prefixedSelector;
|
||||
}
|
||||
|
@ -1,3 +1,20 @@
|
||||
/*
|
||||
Custom element styles
|
||||
*/
|
||||
gradio-lite {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/*
|
||||
To avoid FOUC of custom elements
|
||||
Refs:
|
||||
https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/#the-%3Adefined-selector
|
||||
https://github.com/pyscript/pypercard/blob/66d3550abd8e478f9389e6b87790d5985e55ef7f/static/pyscript.css#L6-L8
|
||||
*/
|
||||
gradio-lite:not(:defined) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scroll-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
|
Loading…
Reference in New Issue
Block a user