mirror of
https://github.com/gradio-app/gradio.git
synced 2025-02-17 11:29:58 +08:00
batch UI updates on a per frame basis (#7564)
* changes * process fe fn tests * cleanup * cleanup * create_target_meta tests and abstraction * add interactivity detection and tests * more functions more tests * add tests for component loader, fix errors * fix everything * add changeset * add changeset * ci * cleanup * cleanup * test * fix again * tweaks * cleanup * add changeset * fix loading_status * cleanup * ensure updates have been flushed before making API requests * add changeset * df fix * fixes * fix dataframe updates * fix dataframe updates * remove $open var * add changeset * fix tests * extend timeout for lite --------- Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com> Co-authored-by: Abubakar Abid <abubakar@huggingface.co> Co-authored-by: Hannah <hannahblair@users.noreply.github.com>
This commit is contained in:
parent
aba44707af
commit
5d1e8dae5a
10
.changeset/itchy-boats-camp.md
Normal file
10
.changeset/itchy-boats-camp.md
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
"@gradio/accordion": patch
|
||||
"@gradio/app": patch
|
||||
"@gradio/client": patch
|
||||
"@gradio/dataframe": patch
|
||||
"@gradio/dataset": patch
|
||||
"gradio": patch
|
||||
---
|
||||
|
||||
fix:batch UI updates on a per frame basis
|
@ -16,7 +16,7 @@ const base = defineConfig({
|
||||
}
|
||||
},
|
||||
expect: { timeout: 15000 },
|
||||
timeout: 15000,
|
||||
timeout: 30000,
|
||||
testMatch: /.*.spec.ts/,
|
||||
testDir: "..",
|
||||
workers: process.env.CI ? 1 : undefined
|
||||
@ -37,7 +37,7 @@ const lite = defineConfig(base, {
|
||||
testMatch: [
|
||||
"**/file_component_events.spec.ts",
|
||||
"**/chatbot_multimodal.spec.ts",
|
||||
"**/kitchen_sink.spec.ts",
|
||||
// "**/kitchen_sink.spec.ts",
|
||||
"**/gallery_component_events.spec.ts"
|
||||
],
|
||||
workers: 1
|
||||
|
@ -42,7 +42,7 @@ type predict = (
|
||||
event_data?: unknown
|
||||
) => Promise<unknown>;
|
||||
|
||||
type client_return = {
|
||||
export type client_return = {
|
||||
predict: predict;
|
||||
config: Config;
|
||||
submit: (
|
||||
@ -194,6 +194,7 @@ export function api_factory(
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
try {
|
||||
var response = await fetch_implementation(url, {
|
||||
method: "POST",
|
||||
|
@ -5,5 +5,7 @@ export {
|
||||
duplicate,
|
||||
api_factory
|
||||
} from "./client.js";
|
||||
export type { client_return } from "./client.js";
|
||||
export type { SpaceStatus } from "./types.js";
|
||||
|
||||
export { FileData, upload, prepare_files } from "./upload.js";
|
||||
|
@ -1 +1 @@
|
||||
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: audio_debugger"]}, {"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/audio_debugger/cantina.wav"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import subprocess\n", "import os\n", "\n", "audio_file = os.path.join(os.path.abspath(''), \"cantina.wav\")\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Tab(\"Audio\"):\n", " gr.Audio(audio_file)\n", " with gr.Tab(\"Interface\"):\n", " gr.Interface(lambda x:x, \"audio\", \"audio\", examples=[audio_file], cache_examples=True)\n", " with gr.Tab(\"Streaming\"):\n", " gr.Interface(lambda x:x, gr.Audio(streaming=True), \"audio\", examples=[audio_file], cache_examples=True)\n", " with gr.Tab(\"console\"):\n", " ip = gr.Textbox(label=\"User IP Address\")\n", " gr.Interface(lambda cmd:subprocess.run([cmd], capture_output=True, shell=True).stdout.decode('utf-8').strip(), \"text\", \"text\")\n", " \n", " def get_ip(request: gr.Request):\n", " return request.client.host\n", " \n", " demo.load(get_ip, None, ip)\n", " \n", "if __name__ == \"__main__\":\n", " demo.queue()\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
||||
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: audio_debugger"]}, {"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/audio_debugger/cantina.wav"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import subprocess\n", "import os\n", "\n", "audio_file = os.path.join(os.path.abspath(''), \"cantina.wav\")\n", "\n", "\n", "with gr.Blocks() as demo:\n", " with gr.Tab(\"Audio\"):\n", " gr.Audio(audio_file)\n", " with gr.Tab(\"Interface\"):\n", " gr.Interface(\n", " lambda x: x, \"audio\", \"audio\", examples=[audio_file], cache_examples=True\n", " )\n", " with gr.Tab(\"Streaming\"):\n", " gr.Interface(\n", " lambda x: x,\n", " gr.Audio(streaming=True),\n", " \"audio\",\n", " examples=[audio_file],\n", " cache_examples=True,\n", " )\n", " with gr.Tab(\"console\"):\n", " ip = gr.Textbox(label=\"User IP Address\")\n", " gr.Interface(\n", " lambda cmd: subprocess.run([cmd], capture_output=True, shell=True)\n", " .stdout.decode(\"utf-8\")\n", " .strip(),\n", " \"text\",\n", " \"text\",\n", " )\n", "\n", " def get_ip(request: gr.Request):\n", " return request.client.host\n", "\n", " demo.load(get_ip, None, ip)\n", "\n", "if __name__ == \"__main__\":\n", " demo.queue()\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
@ -9,18 +9,32 @@ with gr.Blocks() as demo:
|
||||
with gr.Tab("Audio"):
|
||||
gr.Audio(audio_file)
|
||||
with gr.Tab("Interface"):
|
||||
gr.Interface(lambda x:x, "audio", "audio", examples=[audio_file], cache_examples=True)
|
||||
gr.Interface(
|
||||
lambda x: x, "audio", "audio", examples=[audio_file], cache_examples=True
|
||||
)
|
||||
with gr.Tab("Streaming"):
|
||||
gr.Interface(lambda x:x, gr.Audio(streaming=True), "audio", examples=[audio_file], cache_examples=True)
|
||||
gr.Interface(
|
||||
lambda x: x,
|
||||
gr.Audio(streaming=True),
|
||||
"audio",
|
||||
examples=[audio_file],
|
||||
cache_examples=True,
|
||||
)
|
||||
with gr.Tab("console"):
|
||||
ip = gr.Textbox(label="User IP Address")
|
||||
gr.Interface(lambda cmd:subprocess.run([cmd], capture_output=True, shell=True).stdout.decode('utf-8').strip(), "text", "text")
|
||||
|
||||
gr.Interface(
|
||||
lambda cmd: subprocess.run([cmd], capture_output=True, shell=True)
|
||||
.stdout.decode("utf-8")
|
||||
.strip(),
|
||||
"text",
|
||||
"text",
|
||||
)
|
||||
|
||||
def get_ip(request: gr.Request):
|
||||
return request.client.host
|
||||
|
||||
|
||||
demo.load(get_ip, None, ip)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo.queue()
|
||||
demo.launch()
|
||||
|
@ -1 +1 @@
|
||||
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: hello_blocks"]}, {"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", "def greet(name):\n", " return \"Hello \" + name + \"!\"\n", "\n", "with gr.Blocks() as demo:\n", " name = gr.Textbox(label=\"Name\")\n", " output = gr.Textbox(label=\"Output Box\")\n", " greet_btn = gr.Button(\"Greet\")\n", " greet_btn.click(fn=greet, inputs=name, outputs=output, api_name=\"greet\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
||||
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: hello_blocks"]}, {"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 greet(name):\n", " return \"Hello \" + name + \"!\"\n", "\n", "\n", "with gr.Blocks() as demo:\n", " name = gr.Textbox(label=\"Name\")\n", " output = gr.Textbox(label=\"Output Box\")\n", " greet_btn = gr.Button(\"Greet\")\n", " greet_btn.click(fn=greet, inputs=name, outputs=output, api_name=\"greet\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
|
@ -1,8 +1,10 @@
|
||||
import gradio as gr
|
||||
|
||||
|
||||
def greet(name):
|
||||
return "Hello " + name + "!"
|
||||
|
||||
|
||||
with gr.Blocks() as demo:
|
||||
name = gr.Textbox(label="Name")
|
||||
output = gr.Textbox(label="Output Box")
|
||||
@ -10,4 +12,4 @@ with gr.Blocks() as demo:
|
||||
greet_btn.click(fn=greet, inputs=name, outputs=output, api_name="greet")
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo.launch()
|
||||
demo.launch()
|
||||
|
@ -23,7 +23,7 @@
|
||||
{...loading_status}
|
||||
/>
|
||||
|
||||
<Accordion {label} initial_open={open}>
|
||||
<Accordion {label} bind:open>
|
||||
<Column>
|
||||
<slot />
|
||||
</Column>
|
||||
|
@ -1,23 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export let initial_open = true;
|
||||
export let open = true;
|
||||
export let label = "";
|
||||
|
||||
let open = writable(initial_open);
|
||||
|
||||
const toggle_open = (): void => {
|
||||
open.update((value) => !value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<button on:click={toggle_open} class="label-wrap" class:open={$open}>
|
||||
<button on:click={() => (open = !open)} class="label-wrap" class:open>
|
||||
<span>{label}</span>
|
||||
<span style:transform={$open ? "rotate(0)" : "rotate(90deg)"} class="icon">
|
||||
<span style:transform={open ? "rotate(0)" : "rotate(90deg)"} class="icon">
|
||||
▼
|
||||
</span>
|
||||
</button>
|
||||
<div style:display={$open ? "block" : "none"}>
|
||||
<div style:display={open ? "block" : "none"}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
|
@ -73,7 +73,7 @@ export function generate_dev_entry({ enable }: { enable: boolean }): Plugin {
|
||||
|
||||
const new_code = code.replace(RE_SVELTE_IMPORT, (str, $1, $2) => {
|
||||
return `const ${$1.replace(
|
||||
" as ",
|
||||
/ as /g,
|
||||
": "
|
||||
)} = window.__gradio__svelte__internal;`;
|
||||
});
|
||||
@ -255,17 +255,35 @@ function generate_component_imports(): string {
|
||||
return imports;
|
||||
}
|
||||
|
||||
function load_virtual_component_loader(): string {
|
||||
function load_virtual_component_loader(mode: string): string {
|
||||
const loader_path = join(__dirname, "component_loader.js");
|
||||
const component_map = `
|
||||
const component_map = {
|
||||
${generate_component_imports()}
|
||||
};
|
||||
`;
|
||||
let component_map = "";
|
||||
|
||||
if (mode === "test") {
|
||||
component_map = `
|
||||
const component_map = {
|
||||
"test-component-one": {
|
||||
component: () => import("@gradio-test/test-one"),
|
||||
example: () => import("@gradio-test/test-one/example")
|
||||
},
|
||||
"dataset": {
|
||||
component: () => import("@gradio-test/test-two"),
|
||||
example: () => import("@gradio-test/test-two/example")
|
||||
}
|
||||
};
|
||||
`;
|
||||
} else {
|
||||
component_map = `
|
||||
const component_map = {
|
||||
${generate_component_imports()}
|
||||
};
|
||||
`;
|
||||
}
|
||||
|
||||
return `${component_map}\n\n${readFileSync(loader_path, "utf8")}`;
|
||||
}
|
||||
|
||||
export function inject_component_loader(): Plugin {
|
||||
export function inject_component_loader({ mode }: { mode: string }): Plugin {
|
||||
const v_id = "virtual:component-loader";
|
||||
const resolved_v_id = "\0" + v_id;
|
||||
|
||||
@ -276,8 +294,9 @@ export function inject_component_loader(): Plugin {
|
||||
if (id === v_id) return resolved_v_id;
|
||||
},
|
||||
load(id: string) {
|
||||
this.addWatchFile(join(__dirname, "component_loader.js"));
|
||||
if (id === resolved_v_id) {
|
||||
return load_virtual_component_loader();
|
||||
return load_virtual_component_loader(mode);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -311,3 +330,39 @@ export function resolve_svelte(enable: boolean): Plugin {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function mock_modules(): Plugin {
|
||||
const v_id_1 = "@gradio-test/test-one";
|
||||
const v_id_2 = "@gradio-test/test-two";
|
||||
const v_id_1_example = "@gradio-test/test-one/example";
|
||||
const v_id_2_example = "@gradio-test/test-two/example";
|
||||
const resolved_v_id = "\0" + v_id_1;
|
||||
const resolved_v_id_2 = "\0" + v_id_2;
|
||||
const resolved_v_id_1_example = "\0" + v_id_1_example;
|
||||
const resolved_v_id_2_example = "\0" + v_id_2_example;
|
||||
const fallback_example = "@gradio/fallback/example";
|
||||
const resolved_fallback_example = "\0" + fallback_example;
|
||||
|
||||
return {
|
||||
name: "mock-modules",
|
||||
enforce: "pre",
|
||||
resolveId(id: string) {
|
||||
if (id === v_id_1) return resolved_v_id;
|
||||
if (id === v_id_2) return resolved_v_id_2;
|
||||
if (id === v_id_1_example) return resolved_v_id_1_example;
|
||||
if (id === v_id_2_example) return resolved_v_id_2_example;
|
||||
if (id === fallback_example) return resolved_fallback_example;
|
||||
},
|
||||
load(id: string) {
|
||||
if (
|
||||
id === resolved_v_id ||
|
||||
id === resolved_v_id_2 ||
|
||||
id === resolved_v_id_1_example ||
|
||||
id === resolved_v_id_2_example ||
|
||||
id === resolved_fallback_example
|
||||
) {
|
||||
return `export default {}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// @ts-nocheck
|
||||
|
||||
export async function load_component({ api_url, name, id, variant }) {
|
||||
const request_map = {};
|
||||
|
||||
export function load_component({ api_url, name, id, variant }) {
|
||||
const comps = window.__GRADIO__CC__;
|
||||
|
||||
const _component_map = {
|
||||
@ -9,31 +11,41 @@ export async function load_component({ api_url, name, id, variant }) {
|
||||
...(!comps ? {} : comps)
|
||||
};
|
||||
|
||||
if (request_map[`${id}-${variant}`]) {
|
||||
return { component: request_map[`${id}-${variant}`], name };
|
||||
}
|
||||
try {
|
||||
const c = await (
|
||||
if (!_component_map?.[id]?.[variant] && !_component_map?.[name]?.[variant])
|
||||
throw new Error();
|
||||
|
||||
request_map[`${id}-${variant}`] = (
|
||||
_component_map?.[id]?.[variant] || // for dev mode custom components
|
||||
_component_map?.[name]?.[variant]
|
||||
)();
|
||||
|
||||
return {
|
||||
name,
|
||||
component: c
|
||||
component: request_map[`${id}-${variant}`]
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
try {
|
||||
await load_css(`${api_url}/custom_component/${id}/${variant}/style.css`);
|
||||
const c = await import(
|
||||
/* @vite-ignore */ `${api_url}/custom_component/${id}/${variant}/index.js`
|
||||
request_map[`${id}-${variant}`] = get_component_with_css(
|
||||
api_url,
|
||||
id,
|
||||
variant
|
||||
);
|
||||
|
||||
return {
|
||||
name,
|
||||
component: c
|
||||
component: request_map[`${id}-${variant}`]
|
||||
};
|
||||
} catch (e) {
|
||||
if (variant === "example") {
|
||||
request_map[`${id}-${variant}`] = import("@gradio/fallback/example");
|
||||
|
||||
return {
|
||||
name,
|
||||
component: await import("@gradio/fallback/example")
|
||||
component: request_map[`${id}-${variant}`]
|
||||
};
|
||||
}
|
||||
console.error(`failed to load: ${name}`);
|
||||
@ -53,3 +65,14 @@ function load_css(url) {
|
||||
link.onerror = () => reject();
|
||||
});
|
||||
}
|
||||
|
||||
function get_component_with_css(api_url, id, variant) {
|
||||
return Promise.all([
|
||||
load_css(`${api_url}/custom_component/${id}/${variant}/style.css`),
|
||||
import(
|
||||
/* @vite-ignore */ `${api_url}/custom_component/${id}/${variant}/index.js`
|
||||
)
|
||||
]).then(([_, module]) => {
|
||||
return module;
|
||||
});
|
||||
}
|
||||
|
@ -1,14 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { load_component } from "virtual:component-loader";
|
||||
|
||||
import { tick } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import type { client } from "@gradio/client";
|
||||
import { client } from "@gradio/client";
|
||||
|
||||
import { create_loading_status_store } from "./stores";
|
||||
import type { LoadingStatusCollection } from "./stores";
|
||||
|
||||
import type { ComponentMeta, Dependency, LayoutNode } from "./types";
|
||||
import type { UpdateTransaction } from "./init";
|
||||
import { setupi18n } from "./i18n";
|
||||
import { ApiDocs } from "./api_docs/";
|
||||
import type { ThemeMode, Payload } from "./types";
|
||||
@ -19,6 +17,7 @@
|
||||
|
||||
import logo from "./images/logo.svg";
|
||||
import api_logo from "./api_docs/img/api-logo.svg";
|
||||
import { create_components, AsyncFunction } from "./init";
|
||||
|
||||
setupi18n();
|
||||
|
||||
@ -40,38 +39,23 @@
|
||||
export let version: string;
|
||||
export let js: string | null;
|
||||
export let fill_height = false;
|
||||
export let ready: boolean;
|
||||
|
||||
let loading_status = create_loading_status_store();
|
||||
|
||||
let rootNode: ComponentMeta = {
|
||||
id: layout.id,
|
||||
type: "column",
|
||||
props: { interactive: false, scale: fill_height ? 1 : null },
|
||||
has_modes: false,
|
||||
instance: null as unknown as ComponentMeta["instance"],
|
||||
component: null as unknown as ComponentMeta["component"],
|
||||
component_class_id: ""
|
||||
};
|
||||
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
||||
dependencies.forEach((d) => {
|
||||
if (d.js) {
|
||||
const wrap = d.backend_fn
|
||||
? d.inputs.length === 1
|
||||
: d.outputs.length === 1;
|
||||
try {
|
||||
d.frontend_fn = new AsyncFunction(
|
||||
"__fn_args",
|
||||
`let result = await (${d.js})(...__fn_args);
|
||||
return (${wrap} && !Array.isArray(result)) ? [result] : result;`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Could not parse custom js method.");
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
const {
|
||||
layout: _layout,
|
||||
targets,
|
||||
update_value,
|
||||
get_data,
|
||||
loading_status,
|
||||
scheduled_updates
|
||||
} = create_components(components, layout, dependencies, root, app, {
|
||||
fill_height
|
||||
});
|
||||
|
||||
$: {
|
||||
ready = !!$_layout;
|
||||
}
|
||||
|
||||
let params = new URLSearchParams(window.location.search);
|
||||
let api_docs_visible = params.get("view") === "api" && show_api;
|
||||
function set_api_docs_visible(visible: boolean): void {
|
||||
@ -85,276 +69,25 @@
|
||||
history.replaceState(null, "", "?" + params.toString());
|
||||
}
|
||||
|
||||
function is_dep(
|
||||
id: number,
|
||||
type: "inputs" | "outputs",
|
||||
deps: Dependency[]
|
||||
): boolean {
|
||||
for (const dep of deps) {
|
||||
for (const dep_item of dep[type]) {
|
||||
if (dep_item === id) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let dynamic_ids: Set<number> = new Set();
|
||||
|
||||
function has_no_default_value(value: any): boolean {
|
||||
return (
|
||||
(Array.isArray(value) && value.length === 0) ||
|
||||
value === "" ||
|
||||
value === 0 ||
|
||||
!value
|
||||
);
|
||||
}
|
||||
|
||||
let instance_map: { [id: number]: ComponentMeta };
|
||||
|
||||
type LoadedComponent = {
|
||||
default: ComponentMeta["component"];
|
||||
};
|
||||
|
||||
let component_set = new Set<
|
||||
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
|
||||
>();
|
||||
|
||||
let _component_map = new Map<
|
||||
`${ComponentMeta["type"]}_${ComponentMeta["props"]["interactive"]}`,
|
||||
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
|
||||
>();
|
||||
|
||||
async function walk_layout(
|
||||
node: LayoutNode,
|
||||
type_map: Map<number, ComponentMeta["props"]["interactive"]>,
|
||||
instance_map: { [id: number]: ComponentMeta },
|
||||
component_map: Map<
|
||||
`${ComponentMeta["type"]}_${ComponentMeta["props"]["interactive"]}`,
|
||||
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
|
||||
>
|
||||
): Promise<void> {
|
||||
ready = false;
|
||||
let instance = instance_map[node.id];
|
||||
|
||||
const _component = (await component_map.get(
|
||||
`${instance.type}_${type_map.get(node.id) || "false"}`
|
||||
))!.component;
|
||||
instance.component = _component.default;
|
||||
|
||||
if (node.children) {
|
||||
instance.children = node.children.map((v) => instance_map[v.id]);
|
||||
await Promise.all(
|
||||
node.children.map((v) =>
|
||||
walk_layout(v, type_map, instance_map, component_map)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export let ready = false;
|
||||
export let render_complete = false;
|
||||
|
||||
$: components, layout, prepare_components();
|
||||
|
||||
let target_map: Record<number, Record<string, number[]>> = {};
|
||||
|
||||
function prepare_components(): void {
|
||||
target_map = dependencies.reduce(
|
||||
(acc, dep, i) => {
|
||||
dep.targets.forEach(([id, trigger]) => {
|
||||
if (!acc[id]) {
|
||||
acc[id] = {};
|
||||
}
|
||||
if (acc[id]?.[trigger]) {
|
||||
acc[id][trigger].push(i);
|
||||
} else {
|
||||
acc[id][trigger] = [i];
|
||||
}
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<number, Record<string, number[]>>
|
||||
);
|
||||
loading_status = create_loading_status_store();
|
||||
|
||||
dependencies.forEach((v, i) => {
|
||||
loading_status.register(i, v.inputs, v.outputs);
|
||||
});
|
||||
|
||||
const _dynamic_ids = new Set<number>();
|
||||
for (const comp of components) {
|
||||
const { id, props } = comp;
|
||||
const is_input = is_dep(id, "inputs", dependencies);
|
||||
if (
|
||||
is_input ||
|
||||
(!is_dep(id, "outputs", dependencies) &&
|
||||
has_no_default_value(props?.value))
|
||||
) {
|
||||
_dynamic_ids.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
dynamic_ids = _dynamic_ids;
|
||||
|
||||
const _rootNode: typeof rootNode = {
|
||||
id: layout.id,
|
||||
type: "column",
|
||||
props: { interactive: false, scale: fill_height ? 1 : null },
|
||||
has_modes: false,
|
||||
instance: null as unknown as ComponentMeta["instance"],
|
||||
component: null as unknown as ComponentMeta["component"],
|
||||
component_class_id: ""
|
||||
};
|
||||
components.push(_rootNode);
|
||||
const _component_set = new Set<
|
||||
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
|
||||
>();
|
||||
const __component_map = new Map<
|
||||
`${ComponentMeta["type"]}_${ComponentMeta["props"]["interactive"]}`,
|
||||
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
|
||||
>();
|
||||
const __type_for_id = new Map<
|
||||
number,
|
||||
ComponentMeta["props"]["interactive"]
|
||||
>();
|
||||
const _instance_map = components.reduce(
|
||||
(acc, next) => {
|
||||
acc[next.id] = next;
|
||||
return acc;
|
||||
},
|
||||
{} as { [id: number]: ComponentMeta }
|
||||
);
|
||||
components.forEach((c) => {
|
||||
if ((c.props as any).interactive === false) {
|
||||
(c.props as any).interactive = false;
|
||||
} else if ((c.props as any).interactive === true) {
|
||||
(c.props as any).interactive = true;
|
||||
} else if (dynamic_ids.has(c.id)) {
|
||||
(c.props as any).interactive = true;
|
||||
} else {
|
||||
(c.props as any).interactive = false;
|
||||
}
|
||||
|
||||
if ((c.props as any).server_fns) {
|
||||
let server: Record<string, (...args: any[]) => Promise<any>> = {};
|
||||
(c.props as any).server_fns.forEach((fn: string) => {
|
||||
server[fn] = async (...args: any[]) => {
|
||||
if (args.length === 1) {
|
||||
args = args[0];
|
||||
}
|
||||
const result = await app.component_server(c.id, fn, args);
|
||||
return result;
|
||||
};
|
||||
});
|
||||
(c.props as any).server = server;
|
||||
}
|
||||
|
||||
if (target_map[c.id]) {
|
||||
c.props.attached_events = Object.keys(target_map[c.id]);
|
||||
}
|
||||
__type_for_id.set(c.id, c.props.interactive);
|
||||
|
||||
if (c.type === "dataset") {
|
||||
const example_component_map = new Map();
|
||||
|
||||
(c.props.components as string[]).forEach((name: string) => {
|
||||
if (example_component_map.has(name)) {
|
||||
return;
|
||||
}
|
||||
let _c;
|
||||
|
||||
const matching_component = components.find((c) => c.type === name);
|
||||
if (matching_component) {
|
||||
_c = load_component({
|
||||
api_url: root,
|
||||
name,
|
||||
id: matching_component.component_class_id,
|
||||
variant: "example"
|
||||
});
|
||||
example_component_map.set(name, _c);
|
||||
}
|
||||
});
|
||||
|
||||
c.props.component_map = example_component_map;
|
||||
}
|
||||
|
||||
// maybe load custom
|
||||
|
||||
const _c = load_component({
|
||||
api_url: root,
|
||||
name: c.type,
|
||||
id: c.component_class_id,
|
||||
variant: "component"
|
||||
});
|
||||
_component_set.add(_c);
|
||||
__component_map.set(`${c.type}_${c.props.interactive}`, _c);
|
||||
});
|
||||
|
||||
Promise.all(Array.from(_component_set)).then(() => {
|
||||
walk_layout(layout, __type_for_id, _instance_map, __component_map)
|
||||
.then(async () => {
|
||||
ready = true;
|
||||
component_set = _component_set;
|
||||
_component_map = __component_map;
|
||||
instance_map = _instance_map;
|
||||
rootNode = _rootNode;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function throttle<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
limit: number
|
||||
): (...funcArgs: Parameters<T>) => void {
|
||||
let lastFunc: ReturnType<typeof setTimeout>;
|
||||
let lastRan: number;
|
||||
let lastThis: any;
|
||||
let lastArgs: IArguments | null;
|
||||
|
||||
return function (this: any, ...args: Parameters<T>) {
|
||||
if (!lastRan) {
|
||||
func.apply(this, args);
|
||||
lastRan = Date.now();
|
||||
} else {
|
||||
clearTimeout(lastFunc);
|
||||
lastThis = this;
|
||||
lastArgs = arguments;
|
||||
|
||||
lastFunc = setTimeout(
|
||||
() => {
|
||||
if (Date.now() - lastRan >= limit) {
|
||||
if (lastArgs) {
|
||||
func.apply(lastThis, Array.prototype.slice.call(lastArgs));
|
||||
}
|
||||
lastRan = Date.now();
|
||||
}
|
||||
},
|
||||
Math.max(limit - (Date.now() - lastRan), 0)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const refresh = throttle(() => {
|
||||
rootNode = rootNode;
|
||||
}, 50);
|
||||
|
||||
async function handle_update(data: any, fn_index: number): Promise<void> {
|
||||
const outputs = dependencies[fn_index].outputs;
|
||||
|
||||
data?.forEach((value: any, i: number) => {
|
||||
const output = instance_map[outputs[i]];
|
||||
output.props.value_is_output = true;
|
||||
const meta_updates = data?.map((value: any, i: number) => {
|
||||
return {
|
||||
id: outputs[i],
|
||||
prop: "value_is_output",
|
||||
value: true
|
||||
};
|
||||
});
|
||||
|
||||
refresh();
|
||||
update_value(meta_updates);
|
||||
|
||||
await tick();
|
||||
|
||||
const updates: UpdateTransaction[] = [];
|
||||
|
||||
data?.forEach((value: any, i: number) => {
|
||||
const output = instance_map[outputs[i]];
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
@ -364,30 +97,26 @@
|
||||
if (update_key === "__type__") {
|
||||
continue;
|
||||
} else {
|
||||
output.props[update_key] = update_value;
|
||||
updates.push({
|
||||
id: outputs[i],
|
||||
prop: update_key,
|
||||
value: update_value
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output.props.value = value;
|
||||
updates.push({
|
||||
id: outputs[i],
|
||||
prop: "value",
|
||||
value
|
||||
});
|
||||
}
|
||||
});
|
||||
refresh();
|
||||
update_value(updates);
|
||||
}
|
||||
|
||||
let submit_map: Map<number, ReturnType<typeof app.submit>> = new Map();
|
||||
|
||||
function set_prop<T extends ComponentMeta>(
|
||||
obj: T,
|
||||
prop: string,
|
||||
val: any
|
||||
): void {
|
||||
if (!obj?.props) {
|
||||
// @ts-ignore
|
||||
obj.props = {};
|
||||
}
|
||||
obj.props[prop] = val;
|
||||
refresh();
|
||||
}
|
||||
let handled_dependencies: number[][] = [];
|
||||
|
||||
let messages: (ToastMessage & { fn_index: number })[] = [];
|
||||
@ -427,11 +156,26 @@
|
||||
let showed_duplicate_message = false;
|
||||
let showed_mobile_warning = false;
|
||||
|
||||
function get_data(comp: ComponentMeta): any | Promise<any> {
|
||||
if (comp.instance.get_value) {
|
||||
return comp.instance.get_value() as Promise<any>;
|
||||
// as state updates are not synchronous, we need to ensure updates are flushed before triggering any requests
|
||||
function wait_then_trigger_api_call(
|
||||
dep_index: number,
|
||||
trigger_id: number | null = null,
|
||||
event_data: unknown = null
|
||||
): void {
|
||||
let _unsub = (): void => {};
|
||||
function unsub(): void {
|
||||
_unsub();
|
||||
}
|
||||
if ($scheduled_updates) {
|
||||
_unsub = scheduled_updates.subscribe((updating) => {
|
||||
if (!updating) {
|
||||
trigger_api_call(dep_index, trigger_id, event_data);
|
||||
unsub();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
trigger_api_call(dep_index, trigger_id, event_data);
|
||||
}
|
||||
return comp.props.value;
|
||||
}
|
||||
|
||||
async function trigger_api_call(
|
||||
@ -440,6 +184,7 @@
|
||||
event_data: unknown = null
|
||||
): Promise<void> {
|
||||
let dep = dependencies[dep_index];
|
||||
|
||||
const current_status = loading_status.get_status_for_fn(dep_index);
|
||||
messages = messages.filter(({ fn_index }) => fn_index !== dep_index);
|
||||
if (dep.cancels) {
|
||||
@ -457,9 +202,7 @@
|
||||
|
||||
let payload: Payload = {
|
||||
fn_index: dep_index,
|
||||
data: await Promise.all(
|
||||
dep.inputs.map((id) => get_data(instance_map[id]))
|
||||
),
|
||||
data: await Promise.all(dep.inputs.map((id) => get_data(id))),
|
||||
event_data: dep.collects_event_data ? event_data : null,
|
||||
trigger_id: trigger_id
|
||||
};
|
||||
@ -468,9 +211,7 @@
|
||||
dep
|
||||
.frontend_fn(
|
||||
payload.data.concat(
|
||||
await Promise.all(
|
||||
dep.inputs.map((id) => get_data(instance_map[id]))
|
||||
)
|
||||
await Promise.all(dep.inputs.map((id) => get_data(id)))
|
||||
)
|
||||
)
|
||||
.then((v: unknown[]) => {
|
||||
@ -497,7 +238,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function make_prediction(payload: Payload): void {
|
||||
async function make_prediction(payload: Payload): Promise<void> {
|
||||
const submission = app
|
||||
.submit(
|
||||
payload.fn_index,
|
||||
@ -514,7 +255,7 @@
|
||||
handle_update(data, fn_index);
|
||||
})
|
||||
.on("status", ({ fn_index, ...status }) => {
|
||||
tick().then(() => {
|
||||
requestAnimationFrame(() => {
|
||||
//@ts-ignore
|
||||
loading_status.update({
|
||||
...status,
|
||||
@ -552,7 +293,7 @@
|
||||
if (status.stage === "complete") {
|
||||
dependencies.map(async (dep, i) => {
|
||||
if (dep.trigger_after === fn_index) {
|
||||
trigger_api_call(i, payload.trigger_id);
|
||||
wait_then_trigger_api_call(i, payload.trigger_id);
|
||||
}
|
||||
});
|
||||
|
||||
@ -565,7 +306,11 @@
|
||||
...messages
|
||||
];
|
||||
}, 0);
|
||||
trigger_api_call(dep_index, payload.trigger_id, event_data);
|
||||
wait_then_trigger_api_call(
|
||||
dep_index,
|
||||
payload.trigger_id,
|
||||
event_data
|
||||
);
|
||||
user_left_page = false;
|
||||
} else if (status.stage === "error") {
|
||||
if (status.message) {
|
||||
@ -583,7 +328,7 @@
|
||||
dep.trigger_after === fn_index &&
|
||||
!dep.trigger_only_on_success
|
||||
) {
|
||||
trigger_api_call(i, payload.trigger_id);
|
||||
wait_then_trigger_api_call(i, payload.trigger_id);
|
||||
}
|
||||
});
|
||||
|
||||
@ -627,7 +372,7 @@
|
||||
`let result = await (${js})();
|
||||
return (!Array.isArray(result)) ? [result] : result;`
|
||||
);
|
||||
blocks_frontend_fn();
|
||||
await blocks_frontend_fn();
|
||||
}
|
||||
|
||||
await tick();
|
||||
@ -646,11 +391,17 @@
|
||||
// handle load triggers
|
||||
dependencies.forEach((dep, i) => {
|
||||
if (dep.targets[0][1] === "load") {
|
||||
trigger_api_call(i);
|
||||
wait_then_trigger_api_call(i);
|
||||
}
|
||||
});
|
||||
|
||||
if (render_complete) return;
|
||||
|
||||
target.addEventListener("prop_change", (e: Event) => {
|
||||
if (!isCustomEvent(e)) throw new Error("not a custom event");
|
||||
const { id, prop, value } = e.detail;
|
||||
update_value([{ id, prop, value }]);
|
||||
});
|
||||
target.addEventListener("gradio", (e: Event) => {
|
||||
if (!isCustomEvent(e)) throw new Error("not a custom event");
|
||||
|
||||
@ -662,9 +413,10 @@
|
||||
} else if (event === "error" || event === "warning") {
|
||||
messages = [new_message(data, -1, event), ...messages];
|
||||
} else {
|
||||
const deps = target_map[id]?.[event];
|
||||
const deps = targets[id]?.[event];
|
||||
|
||||
deps?.forEach((dep_id) => {
|
||||
trigger_api_call(dep_id, id, data);
|
||||
wait_then_trigger_api_call(dep_id, id, data);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -681,18 +433,30 @@
|
||||
$: set_status($loading_status);
|
||||
|
||||
function set_status(statuses: LoadingStatusCollection): void {
|
||||
for (const id in statuses) {
|
||||
let loading_status = statuses[id];
|
||||
const updates = Object.entries(statuses).map(([id, loading_status]) => {
|
||||
let dependency = dependencies[loading_status.fn_index];
|
||||
loading_status.scroll_to_output = dependency.scroll_to_output;
|
||||
loading_status.show_progress = dependency.show_progress;
|
||||
return {
|
||||
id: parseInt(id),
|
||||
prop: "loading_status",
|
||||
value: loading_status
|
||||
};
|
||||
});
|
||||
|
||||
set_prop(instance_map[id], "loading_status", loading_status);
|
||||
}
|
||||
const inputs_to_update = loading_status.get_inputs_to_update();
|
||||
for (const [id, pending_status] of inputs_to_update) {
|
||||
set_prop(instance_map[id], "pending", pending_status === "pending");
|
||||
}
|
||||
|
||||
const additional_updates = Array.from(inputs_to_update).map(
|
||||
([id, pending_status]) => {
|
||||
return {
|
||||
id,
|
||||
prop: "pending",
|
||||
value: pending_status === "pending"
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
update_value([...updates, ...additional_updates]);
|
||||
}
|
||||
|
||||
function isCustomEvent(event: Event): event is CustomEvent {
|
||||
@ -725,11 +489,9 @@
|
||||
|
||||
<div class="wrap" style:min-height={app_mode ? "100%" : "auto"}>
|
||||
<div class="contain" style:flex-grow={app_mode ? "1" : "auto"}>
|
||||
{#if ready}
|
||||
{#if $_layout}
|
||||
<MountComponents
|
||||
{rootNode}
|
||||
{dynamic_ids}
|
||||
{instance_map}
|
||||
rootNode={$_layout}
|
||||
{root}
|
||||
{target}
|
||||
{theme_mode}
|
||||
@ -768,7 +530,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if api_docs_visible && ready}
|
||||
{#if api_docs_visible && $_layout}
|
||||
<div class="api-docs">
|
||||
<!-- TODO: fix -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events-->
|
||||
@ -781,10 +543,10 @@
|
||||
/>
|
||||
<div class="api-docs-wrap">
|
||||
<ApiDocs
|
||||
root_node={$_layout}
|
||||
on:close={() => {
|
||||
set_api_docs_visible(false);
|
||||
}}
|
||||
{instance_map}
|
||||
{dependencies}
|
||||
{root}
|
||||
{app}
|
||||
|
@ -3,8 +3,6 @@
|
||||
import Render from "./Render.svelte";
|
||||
|
||||
export let rootNode: any;
|
||||
export let dynamic_ids: any;
|
||||
export let instance_map: any;
|
||||
export let root: any;
|
||||
export let target: any;
|
||||
export let theme_mode: any;
|
||||
@ -17,16 +15,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<Render
|
||||
component={rootNode.component}
|
||||
id={rootNode.id}
|
||||
props={rootNode.props}
|
||||
children={rootNode.children}
|
||||
{dynamic_ids}
|
||||
{instance_map}
|
||||
{root}
|
||||
{target}
|
||||
{theme_mode}
|
||||
{version}
|
||||
{autoscroll}
|
||||
/>
|
||||
<Render node={rootNode} {root} {target} {theme_mode} {version} {autoscroll} />
|
||||
|
@ -2,16 +2,11 @@
|
||||
import { Gradio } from "./gradio_helper";
|
||||
import { onMount, createEventDispatcher, setContext } from "svelte";
|
||||
import type { ComponentMeta, ThemeMode } from "./types";
|
||||
import RenderComponent from "./RenderComponent.svelte";
|
||||
|
||||
export let root: string;
|
||||
export let component: ComponentMeta["component"];
|
||||
export let instance_map: Record<number, ComponentMeta>;
|
||||
|
||||
export let id: number;
|
||||
export let props: ComponentMeta["props"];
|
||||
|
||||
export let children: ComponentMeta["children"];
|
||||
export let dynamic_ids: Set<number>;
|
||||
export let node: ComponentMeta;
|
||||
export let parent: string | null = null;
|
||||
export let target: HTMLElement;
|
||||
export let theme_mode: ThemeMode;
|
||||
@ -22,14 +17,14 @@
|
||||
let filtered_children: ComponentMeta[] = [];
|
||||
|
||||
onMount(() => {
|
||||
dispatch("mount", id);
|
||||
dispatch("mount", node.id);
|
||||
|
||||
for (const child of filtered_children) {
|
||||
dispatch("mount", child.id);
|
||||
}
|
||||
|
||||
return () => {
|
||||
dispatch("destroy", id);
|
||||
dispatch("destroy", node.id);
|
||||
|
||||
for (const child of filtered_children) {
|
||||
dispatch("mount", child.id);
|
||||
@ -37,10 +32,10 @@
|
||||
};
|
||||
});
|
||||
|
||||
$: children =
|
||||
children &&
|
||||
children.filter((v) => {
|
||||
const valid_node = instance_map[v.id].type !== "statustracker";
|
||||
$: node.children =
|
||||
node.children &&
|
||||
node.children.filter((v) => {
|
||||
const valid_node = node.type !== "statustracker";
|
||||
if (!valid_node) {
|
||||
filtered_children.push(v);
|
||||
}
|
||||
@ -50,51 +45,49 @@
|
||||
setContext("BLOCK_KEY", parent);
|
||||
|
||||
function handle_prop_change(e: { detail: Record<string, any> }): void {
|
||||
for (const k in e.detail) {
|
||||
instance_map[id].props[k] = e.detail[k];
|
||||
}
|
||||
// for (const k in e.detail) {
|
||||
// instance_map[id].props[k] = e.detail[k];
|
||||
// }
|
||||
}
|
||||
|
||||
$: {
|
||||
if (instance_map[id].type === "form") {
|
||||
if (children?.every((c) => !c.props.visible)) {
|
||||
props.visible = false;
|
||||
if (node.type === "form") {
|
||||
if (node.children?.every((c) => !c.props.visible)) {
|
||||
node.props.visible = false;
|
||||
} else {
|
||||
props.visible = true;
|
||||
node.props.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:component
|
||||
this={component}
|
||||
bind:this={instance_map[id].instance}
|
||||
bind:value={instance_map[id].props.value}
|
||||
elem_id={("elem_id" in props && props.elem_id) || `component-${id}`}
|
||||
elem_classes={("elem_classes" in props && props.elem_classes) || []}
|
||||
<RenderComponent
|
||||
id={node.id}
|
||||
component={node.component}
|
||||
bind:instance={node.instance}
|
||||
bind:value={node.props.value}
|
||||
elem_id={("elem_id" in node.props && node.props.elem_id) ||
|
||||
`component-${node.id}`}
|
||||
elem_classes={("elem_classes" in node.props && node.props.elem_classes) || []}
|
||||
on:prop_change={handle_prop_change}
|
||||
{target}
|
||||
{...props}
|
||||
{...node.props}
|
||||
{theme_mode}
|
||||
{root}
|
||||
gradio={new Gradio(id, target, theme_mode, version, root, autoscroll)}
|
||||
gradio={new Gradio(node.id, target, theme_mode, version, root, autoscroll)}
|
||||
>
|
||||
{#if children && children.length}
|
||||
{#each children as { component, id: each_id, props, children: _children, has_modes } (each_id)}
|
||||
{#if node.children && node.children.length}
|
||||
{#each node.children as _node (_node.id)}
|
||||
<svelte:self
|
||||
{component}
|
||||
node={_node}
|
||||
component={_node.component}
|
||||
{target}
|
||||
id={each_id}
|
||||
{props}
|
||||
id={_node.id}
|
||||
{root}
|
||||
{instance_map}
|
||||
children={_children}
|
||||
{dynamic_ids}
|
||||
{has_modes}
|
||||
{theme_mode}
|
||||
on:destroy
|
||||
on:mount
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</svelte:component>
|
||||
</RenderComponent>
|
||||
|
69
js/app/src/RenderComponent.svelte
Normal file
69
js/app/src/RenderComponent.svelte
Normal file
@ -0,0 +1,69 @@
|
||||
<svelte:options immutable={true} />
|
||||
|
||||
<script lang="ts">
|
||||
import type { Gradio } from "./gradio_helper";
|
||||
import type { ComponentMeta, ThemeMode } from "./types";
|
||||
import type { SvelteComponent, ComponentType } from "svelte";
|
||||
// @ts-ignore
|
||||
import { bind, binding_callbacks } from "svelte/internal";
|
||||
|
||||
export let root: string;
|
||||
export let component: ComponentMeta["component"];
|
||||
export let target: HTMLElement;
|
||||
export let theme_mode: ThemeMode;
|
||||
export let instance: ComponentMeta["instance"];
|
||||
export let value: any;
|
||||
export let gradio: Gradio;
|
||||
export let elem_id: string;
|
||||
export let elem_classes: string[];
|
||||
export let id: number;
|
||||
|
||||
const s = (id: number, p: string, v: any): CustomEvent =>
|
||||
new CustomEvent("prop_change", { detail: { id, prop: p, value: v } });
|
||||
|
||||
function wrap(
|
||||
component: ComponentType<SvelteComponent>
|
||||
): ComponentType<SvelteComponent> {
|
||||
const ProxiedMyClass = new Proxy(component, {
|
||||
construct(_target, args: Record<string, any>[]) {
|
||||
//@ts-ignore
|
||||
const instance = new _target(...args);
|
||||
const props = Object.getOwnPropertyNames(instance).filter(
|
||||
(s) => !s.startsWith("$")
|
||||
);
|
||||
|
||||
function report(props: string) {
|
||||
return function (propargs: any) {
|
||||
const ev = s(id, props, propargs);
|
||||
target.dispatchEvent(ev);
|
||||
};
|
||||
}
|
||||
props.forEach((v) => {
|
||||
binding_callbacks.push(() => bind(instance, v, report(v)));
|
||||
});
|
||||
|
||||
return instance;
|
||||
}
|
||||
});
|
||||
|
||||
return ProxiedMyClass;
|
||||
}
|
||||
|
||||
const _component = wrap(component);
|
||||
</script>
|
||||
|
||||
<svelte:component
|
||||
this={_component}
|
||||
bind:this={instance}
|
||||
bind:value
|
||||
on:prop_change
|
||||
{elem_id}
|
||||
{elem_classes}
|
||||
{target}
|
||||
{...$$restProps}
|
||||
{theme_mode}
|
||||
{root}
|
||||
{gradio}
|
||||
>
|
||||
<slot />
|
||||
</svelte:component>
|
@ -16,14 +16,11 @@
|
||||
import python from "./img/python.svg";
|
||||
import javascript from "./img/javascript.svg";
|
||||
|
||||
export let instance_map: {
|
||||
[id: number]: ComponentMeta;
|
||||
};
|
||||
export let dependencies: Dependency[];
|
||||
export let root: string;
|
||||
export let app: Awaited<ReturnType<typeof client>>;
|
||||
export let space_id: string | null;
|
||||
|
||||
export let root_node: ComponentMeta;
|
||||
const js_docs =
|
||||
"https://www.gradio.app/guides/getting-started-with-the-js-client";
|
||||
const py_docs =
|
||||
@ -50,9 +47,27 @@
|
||||
|
||||
let is_running = false;
|
||||
|
||||
function find_recursive(
|
||||
node: ComponentMeta,
|
||||
id: number
|
||||
): ComponentMeta | null {
|
||||
if (node.id === id) {
|
||||
return node;
|
||||
}
|
||||
if (node.children) {
|
||||
for (let child of node.children) {
|
||||
let result = find_recursive(child, id);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let dependency_inputs = dependencies.map((dependency) =>
|
||||
dependency.inputs.map((_id) => {
|
||||
let default_data = instance_map[_id].documentation?.example_data;
|
||||
let default_data = find_recursive(root_node, _id)?.props?.default;
|
||||
if (default_data === undefined) {
|
||||
default_data = "";
|
||||
} else if (typeof default_data === "object") {
|
||||
@ -103,10 +118,9 @@
|
||||
let dependency = dependencies[index];
|
||||
let attempted_component_index = 0;
|
||||
try {
|
||||
var inputs = dependency_inputs[index].map((input_val, i) => {
|
||||
var inputs = dependency_inputs[index].map((input_val: any, i: number) => {
|
||||
attempted_component_index = i;
|
||||
let component = instance_map[dependency.inputs[i]];
|
||||
// @ts-ignore
|
||||
let component = find_recursive(root_node, dependency.inputs[i])!;
|
||||
input_val = represent_value(
|
||||
input_val,
|
||||
component.documentation?.type?.input_payload ||
|
||||
@ -130,7 +144,7 @@
|
||||
if (status_code == 200) {
|
||||
dependency_outputs[index] = response.data.map(
|
||||
(output_val: any, i: number) => {
|
||||
let component = instance_map[dependency.outputs[i]];
|
||||
let component = find_recursive(root_node, dependency.outputs[i])!;
|
||||
|
||||
return represent_value(
|
||||
output_val,
|
||||
|
@ -35,6 +35,7 @@ export class Gradio<T extends Record<string, any> = Record<string, any>> {
|
||||
bubbles: true,
|
||||
detail: { data, id: this.#id, event: event_name }
|
||||
});
|
||||
|
||||
this.#el.dispatchEvent(e);
|
||||
}
|
||||
}
|
||||
|
521
js/app/src/init.test.ts
Normal file
521
js/app/src/init.test.ts
Normal file
@ -0,0 +1,521 @@
|
||||
import { describe, test, expect, vi } from "vitest";
|
||||
import { spy } from "tinyspy";
|
||||
import { setupServer } from "msw/node";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import type { client_return } from "@gradio/client";
|
||||
import { Dependency, TargetMap } from "./types";
|
||||
import {
|
||||
process_frontend_fn,
|
||||
create_target_meta,
|
||||
determine_interactivity,
|
||||
process_server_fn,
|
||||
get_component
|
||||
} from "./init";
|
||||
|
||||
describe("process_frontend_fn", () => {
|
||||
test("empty source code returns null", () => {
|
||||
const source = "";
|
||||
|
||||
const fn = process_frontend_fn(source, false, 1, 1);
|
||||
expect(fn).toBe(null);
|
||||
});
|
||||
|
||||
test("falsey source code returns null: false", () => {
|
||||
const source = false;
|
||||
|
||||
const fn = process_frontend_fn(source, false, 1, 1);
|
||||
expect(fn).toBe(null);
|
||||
});
|
||||
|
||||
test("falsey source code returns null: undefined", () => {
|
||||
const source = undefined;
|
||||
|
||||
const fn = process_frontend_fn(source, false, 1, 1);
|
||||
expect(fn).toBe(null);
|
||||
});
|
||||
|
||||
test("falsey source code returns null: null", () => {
|
||||
const source = null;
|
||||
|
||||
const fn = process_frontend_fn(source, false, 1, 1);
|
||||
expect(fn).toBe(null);
|
||||
});
|
||||
|
||||
test("source code returns a function", () => {
|
||||
const source = "(arg) => arg";
|
||||
|
||||
const fn = process_frontend_fn(source, false, 1, 1);
|
||||
expect(typeof fn).toBe("function");
|
||||
});
|
||||
|
||||
test("arrays of values can be passed to the generated function", async () => {
|
||||
const source = "(arg) => arg";
|
||||
|
||||
const fn = process_frontend_fn(source, false, 1, 1);
|
||||
if (fn) {
|
||||
await expect(fn([1])).resolves.toEqual([1]);
|
||||
}
|
||||
});
|
||||
|
||||
test("arrays of many values can be passed", async () => {
|
||||
const source = "(...args) => args";
|
||||
|
||||
const fn = process_frontend_fn(source, false, 1, 1);
|
||||
if (fn) {
|
||||
await expect(fn([1, 2, 3, 4, 5, 6])).resolves.toEqual([1, 2, 3, 4, 5, 6]);
|
||||
}
|
||||
});
|
||||
|
||||
test("The generated function returns a promise", () => {
|
||||
const source = "(arg) => arg";
|
||||
|
||||
const fn = process_frontend_fn(source, false, 1, 1);
|
||||
if (fn) {
|
||||
expect(fn([1])).toBeInstanceOf(Promise);
|
||||
}
|
||||
});
|
||||
|
||||
test("The generated function is callable and returns the expected value", async () => {
|
||||
const source = "(arg) => arg";
|
||||
|
||||
const fn = process_frontend_fn(source, false, 1, 1);
|
||||
if (fn) {
|
||||
await expect(fn([1])).resolves.toEqual([1]);
|
||||
}
|
||||
});
|
||||
|
||||
test("The return value of the function is wrapped in an array if there is no backend function and the input length is 1", async () => {
|
||||
const source = "(arg) => arg";
|
||||
|
||||
const fn = process_frontend_fn(source, false, 1, 1);
|
||||
if (fn) {
|
||||
await expect(fn([1])).resolves.toEqual([1]);
|
||||
}
|
||||
});
|
||||
|
||||
test("The return value of the function is not wrapped in an array if there is no backend function and the input length is greater than 1", async () => {
|
||||
const source = "(arg) => arg";
|
||||
|
||||
const fn = process_frontend_fn(source, false, 2, 2);
|
||||
if (fn) {
|
||||
await expect(fn([1])).resolves.toEqual(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("The return value of the function is wrapped in an array if there is a backend function and the input length is 1", async () => {
|
||||
const source = "(arg) => arg";
|
||||
|
||||
const fn = process_frontend_fn(source, true, 1, 1);
|
||||
if (fn) {
|
||||
await expect(fn([1])).resolves.toEqual([1]);
|
||||
}
|
||||
});
|
||||
|
||||
test("The return value of the function is not wrapped in an array if there is a backend function and the input length is greater than 1", async () => {
|
||||
const source = "(arg) => arg";
|
||||
|
||||
const fn = process_frontend_fn(source, true, 2, 2);
|
||||
if (fn) {
|
||||
await expect(fn([1])).resolves.toEqual(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("create_target_meta", () => {
|
||||
test("creates a target map", () => {
|
||||
const targets: Dependency["targets"] = [
|
||||
[1, "change"],
|
||||
[2, "input"],
|
||||
[3, "load"]
|
||||
];
|
||||
const fn_index = 0;
|
||||
const target_map = {};
|
||||
|
||||
const result = create_target_meta(targets, fn_index, target_map);
|
||||
expect(result).toEqual({
|
||||
1: { change: [0] },
|
||||
2: { input: [0] },
|
||||
3: { load: [0] }
|
||||
});
|
||||
});
|
||||
|
||||
test("if the target already exists, it adds the new trigger to the list", () => {
|
||||
const targets: Dependency["targets"] = [
|
||||
[1, "change"],
|
||||
[1, "input"],
|
||||
[1, "load"]
|
||||
];
|
||||
const fn_index = 1;
|
||||
const target_map: TargetMap = {
|
||||
1: { change: [0] }
|
||||
};
|
||||
|
||||
const result = create_target_meta(targets, fn_index, target_map);
|
||||
expect(result).toEqual({
|
||||
1: { change: [0, 1], input: [1], load: [1] }
|
||||
});
|
||||
});
|
||||
|
||||
test("if the trigger already exists, it adds the new function to the list", () => {
|
||||
const targets: Dependency["targets"] = [
|
||||
[1, "change"],
|
||||
[2, "change"],
|
||||
[3, "change"]
|
||||
];
|
||||
const fn_index = 1;
|
||||
const target_map: TargetMap = {
|
||||
1: { change: [0] },
|
||||
2: { change: [0] },
|
||||
3: { change: [0] }
|
||||
};
|
||||
|
||||
const result = create_target_meta(targets, fn_index, target_map);
|
||||
expect(result).toEqual({
|
||||
1: { change: [0, 1] },
|
||||
2: { change: [0, 1] },
|
||||
3: { change: [0, 1] }
|
||||
});
|
||||
});
|
||||
|
||||
test("if the target and trigger already exist, it adds the new function to the list", () => {
|
||||
const targets: Dependency["targets"] = [[1, "change"]];
|
||||
const fn_index = 1;
|
||||
const target_map: TargetMap = {
|
||||
1: { change: [0] }
|
||||
};
|
||||
|
||||
const result = create_target_meta(targets, fn_index, target_map);
|
||||
expect(result).toEqual({
|
||||
1: { change: [0, 1] }
|
||||
});
|
||||
});
|
||||
|
||||
test("if the target, trigger and function id already exist, it does not add duplicates", () => {
|
||||
const targets: Dependency["targets"] = [[1, "change"]];
|
||||
const fn_index = 0;
|
||||
const target_map: TargetMap = {
|
||||
1: { change: [0] }
|
||||
};
|
||||
|
||||
const result = create_target_meta(targets, fn_index, target_map);
|
||||
expect(result).toEqual({
|
||||
1: { change: [0] }
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("determine_interactivity", () => {
|
||||
test("returns true if the prop is interactive = true", () => {
|
||||
const result = determine_interactivity(
|
||||
0,
|
||||
true,
|
||||
"hi",
|
||||
new Set([0]),
|
||||
new Set([2])
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("returns false if the prop is interactive = false", () => {
|
||||
const result = determine_interactivity(
|
||||
0,
|
||||
false,
|
||||
"hi",
|
||||
new Set([0]),
|
||||
new Set([2])
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
test("returns true if the component is an input", () => {
|
||||
const result = determine_interactivity(
|
||||
0,
|
||||
undefined,
|
||||
"hi",
|
||||
new Set([0]),
|
||||
new Set([2])
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("returns true if the component is not an input or output and the component has no default value: empty string", () => {
|
||||
const result = determine_interactivity(
|
||||
2,
|
||||
undefined,
|
||||
"",
|
||||
new Set([0]),
|
||||
new Set([1])
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("returns true if the component is not an input or output and the component has no default value: empty array", () => {
|
||||
const result = determine_interactivity(
|
||||
2,
|
||||
undefined,
|
||||
[],
|
||||
new Set([0]),
|
||||
new Set([1])
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("returns true if the component is not an input or output and the component has no default value: boolean", () => {
|
||||
const result = determine_interactivity(
|
||||
2,
|
||||
undefined,
|
||||
false,
|
||||
new Set([0]),
|
||||
new Set([1])
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("returns true if the component is not an input or output and the component has no default value: undefined", () => {
|
||||
const result = determine_interactivity(
|
||||
2,
|
||||
undefined,
|
||||
undefined,
|
||||
new Set([0]),
|
||||
new Set([1])
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("returns true if the component is not an input or output and the component has no default value: null", () => {
|
||||
const result = determine_interactivity(
|
||||
2,
|
||||
undefined,
|
||||
null,
|
||||
new Set([0]),
|
||||
new Set([1])
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("returns true if the component is not an input or output and the component has no default value: 0", () => {
|
||||
const result = determine_interactivity(
|
||||
2,
|
||||
undefined,
|
||||
0,
|
||||
new Set([0]),
|
||||
new Set([1])
|
||||
);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("returns false if the component is not an input or output and the component has a default value", () => {
|
||||
const result = determine_interactivity(
|
||||
2,
|
||||
undefined,
|
||||
"hello",
|
||||
new Set([0]),
|
||||
new Set([1])
|
||||
);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("process_server_fn", () => {
|
||||
test("returns an object", () => {
|
||||
const result = process_server_fn(1, ["fn1", "fn2"], {} as any);
|
||||
expect(result).toBeTypeOf("object");
|
||||
});
|
||||
|
||||
test("returns an object with the correct keys", () => {
|
||||
const result = process_server_fn(1, ["fn1", "fn2"], {} as any);
|
||||
expect(Object.keys(result)).toEqual(["fn1", "fn2"]);
|
||||
});
|
||||
|
||||
test("returns an object with the correct keys and values", () => {
|
||||
const app = {
|
||||
component_server: async (id: number, fn: string, args: any) => {
|
||||
return args;
|
||||
}
|
||||
} as client_return;
|
||||
|
||||
const result = process_server_fn(1, ["fn1", "fn2"], app);
|
||||
expect(Object.keys(result)).toEqual(["fn1", "fn2"]);
|
||||
|
||||
expect(result.fn1).toBeInstanceOf(Function);
|
||||
expect(result.fn2).toBeInstanceOf(Function);
|
||||
});
|
||||
|
||||
test("returned server functions should resolve to a promise", async () => {
|
||||
const app = {
|
||||
component_server: async (id: number, fn: string, args: any) => {
|
||||
return args;
|
||||
}
|
||||
} as client_return;
|
||||
|
||||
const result = process_server_fn(1, ["fn1", "fn2"], app);
|
||||
const response = result.fn1("hello");
|
||||
expect(response).toBeInstanceOf(Promise);
|
||||
});
|
||||
|
||||
test("the functions call the clients component_server function with the correct arguments ", async () => {
|
||||
const mock = spy(async (id: number, fn: string, args: any) => {
|
||||
return args;
|
||||
});
|
||||
const app = {
|
||||
component_server: mock as any
|
||||
} as client_return;
|
||||
|
||||
const result = process_server_fn(1, ["fn1", "fn2"], app as client_return);
|
||||
const response = await result.fn1("hello");
|
||||
expect(response).toBe("hello");
|
||||
expect(mock.calls).toEqual([[1, "fn1", "hello"]]);
|
||||
});
|
||||
|
||||
test("if there are no server functions, it returns an empty object", () => {
|
||||
const result = process_server_fn(1, undefined, {} as any);
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("get_component", () => {
|
||||
test("returns an object", () => {
|
||||
const result = get_component("test-component-one", "class_id", "root", []);
|
||||
expect(result.component).toBeTypeOf("object");
|
||||
});
|
||||
|
||||
test("returns an object with the correct keys", () => {
|
||||
const result = get_component("test-component-one", "class_id", "root", []);
|
||||
expect(Object.keys(result)).toEqual([
|
||||
"component",
|
||||
"name",
|
||||
"example_components"
|
||||
]);
|
||||
});
|
||||
|
||||
test("the component key is a promise", () => {
|
||||
const result = get_component("test-component-one", "class_id", "root", []);
|
||||
expect(result.component).toBeInstanceOf(Promise);
|
||||
});
|
||||
|
||||
test("the resolved component key is an object", async () => {
|
||||
const result = get_component("test-component-one", "class_id", "root", []);
|
||||
const o = await result.component;
|
||||
|
||||
expect(o).toBeTypeOf("object");
|
||||
});
|
||||
|
||||
test("getting the same component twice should return the same promise", () => {
|
||||
const result = get_component("test-component-one", "class_id", "root", []);
|
||||
const result_two = get_component(
|
||||
"test-component-one",
|
||||
"class_id",
|
||||
"root",
|
||||
[]
|
||||
);
|
||||
|
||||
expect(result.component).toBe(result_two.component);
|
||||
});
|
||||
|
||||
test("if example components are not provided, the example_components key is undefined", async () => {
|
||||
const result = get_component("dataset", "class_id", "root", []);
|
||||
expect(result.example_components).toBe(undefined);
|
||||
});
|
||||
|
||||
test("if the type is not a dataset, the example_components key is undefined", async () => {
|
||||
const result = get_component("test-component-one", "class_id", "root", []);
|
||||
expect(result.example_components).toBe(undefined);
|
||||
});
|
||||
|
||||
test("when the type is a dataset, returns an object with the correct keys and values and example components", () => {
|
||||
const result = get_component(
|
||||
"dataset",
|
||||
"class_id",
|
||||
"root",
|
||||
[
|
||||
{
|
||||
type: "test-component-one",
|
||||
component_class_id: "example_class_id",
|
||||
id: 1,
|
||||
props: {
|
||||
value: "hi",
|
||||
interactive: false
|
||||
},
|
||||
has_modes: false,
|
||||
instance: {} as any,
|
||||
component: {} as any
|
||||
}
|
||||
],
|
||||
["test-component-one"]
|
||||
);
|
||||
expect(result.component).toBeTypeOf("object");
|
||||
expect(result.example_components).toBeInstanceOf(Map);
|
||||
});
|
||||
|
||||
test("when example components are returned, returns an object with the correct keys and values and example components", () => {
|
||||
const result = get_component(
|
||||
"dataset",
|
||||
"class_id",
|
||||
"root",
|
||||
[
|
||||
{
|
||||
type: "test-component-one",
|
||||
component_class_id: "example_class_id",
|
||||
id: 1,
|
||||
props: {
|
||||
value: "hi",
|
||||
interactive: false
|
||||
},
|
||||
has_modes: false,
|
||||
instance: {} as any,
|
||||
component: {} as any
|
||||
}
|
||||
],
|
||||
["test-component-one"]
|
||||
);
|
||||
expect(result.example_components?.get("test-component-one")).toBeTypeOf(
|
||||
"object"
|
||||
);
|
||||
expect(result.example_components?.get("test-component-one")).toBeInstanceOf(
|
||||
Promise
|
||||
);
|
||||
});
|
||||
|
||||
test("if the component is not found then it should request the component from the server", async () => {
|
||||
const api_url = "example.com";
|
||||
const id = "test-random";
|
||||
const variant = "component";
|
||||
const handlers = [
|
||||
http.get(`${api_url}/custom_component/${id}/${variant}/style.css`, () => {
|
||||
return new HttpResponse('console.log("boo")', {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "text/css"
|
||||
}
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
// vi.mock calls are always hoisted out of the test function to the top of the file
|
||||
// so we need to use vi.hoisted to hoist the mock function above the vi.mock call
|
||||
const { mock } = vi.hoisted(() => {
|
||||
return { mock: vi.fn() };
|
||||
});
|
||||
|
||||
vi.mock(
|
||||
`example.com/custom_component/test-random/component/index.js`,
|
||||
async () => {
|
||||
mock();
|
||||
return {
|
||||
default: {
|
||||
default: "HELLO"
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const server = setupServer(...handlers);
|
||||
server.listen();
|
||||
|
||||
await get_component("test-random", id, api_url, []).component;
|
||||
|
||||
expect(mock).toHaveBeenCalled();
|
||||
|
||||
server.close();
|
||||
});
|
||||
});
|
438
js/app/src/init.ts
Normal file
438
js/app/src/init.ts
Normal file
@ -0,0 +1,438 @@
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import type {
|
||||
ComponentMeta,
|
||||
Dependency,
|
||||
LayoutNode,
|
||||
TargetMap,
|
||||
LoadingComponent
|
||||
} from "./types";
|
||||
import { load_component } from "virtual:component-loader";
|
||||
import type { client_return } from "@gradio/client";
|
||||
import { create_loading_status_store } from "./stores";
|
||||
|
||||
export interface UpdateTransaction {
|
||||
id: number;
|
||||
value: any;
|
||||
prop: string;
|
||||
}
|
||||
|
||||
let pending_updates: UpdateTransaction[][] = [];
|
||||
|
||||
/**
|
||||
* Create a store with the layout and a map of targets
|
||||
* @param components An array of component metadata
|
||||
* @param layout The layout tree
|
||||
* @param dependencies The events, triggers, inputs, and outputs
|
||||
* @param root The root url of the app
|
||||
* @param app The client instance
|
||||
* @param options Options for the layout
|
||||
* @returns A store with the layout and a map of targets
|
||||
*/
|
||||
export function create_components(
|
||||
components: ComponentMeta[],
|
||||
layout: LayoutNode,
|
||||
dependencies: Dependency[],
|
||||
root: string,
|
||||
app: client_return,
|
||||
options: {
|
||||
fill_height: boolean;
|
||||
}
|
||||
): {
|
||||
layout: Writable<ComponentMeta>;
|
||||
targets: TargetMap;
|
||||
update_value: (updates: UpdateTransaction[]) => void;
|
||||
get_data: (id: number) => any | Promise<any>;
|
||||
loading_status: ReturnType<typeof create_loading_status_store>;
|
||||
scheduled_updates: Writable<boolean>;
|
||||
} {
|
||||
const _component_map = new Map();
|
||||
|
||||
const target_map: TargetMap = {};
|
||||
const inputs = new Set<number>();
|
||||
const outputs = new Set<number>();
|
||||
|
||||
const _rootNode: ComponentMeta = {
|
||||
id: layout.id,
|
||||
type: "column",
|
||||
props: { interactive: false, scale: options.fill_height ? 1 : null },
|
||||
has_modes: false,
|
||||
instance: null as unknown as ComponentMeta["instance"],
|
||||
component: null as unknown as ComponentMeta["component"],
|
||||
component_class_id: ""
|
||||
};
|
||||
|
||||
components.push(_rootNode);
|
||||
const loading_status = create_loading_status_store();
|
||||
|
||||
dependencies.forEach((dep, fn_index) => {
|
||||
loading_status.register(fn_index, dep.inputs, dep.outputs);
|
||||
dep.frontend_fn = process_frontend_fn(
|
||||
dep.js,
|
||||
!!dep.backend_fn,
|
||||
dep.inputs.length,
|
||||
dep.outputs.length
|
||||
);
|
||||
create_target_meta(dep.targets, fn_index, target_map);
|
||||
get_inputs_outputs(dep, inputs, outputs);
|
||||
});
|
||||
|
||||
const constructor_map = preload_all_components(components, root);
|
||||
|
||||
let instance_map: { [id: number]: ComponentMeta } = components.reduce(
|
||||
(acc, c) => {
|
||||
acc[c.id] = c;
|
||||
return acc;
|
||||
},
|
||||
{} as { [id: number]: ComponentMeta }
|
||||
);
|
||||
|
||||
async function walk_layout(node: LayoutNode): Promise<ComponentMeta> {
|
||||
const instance = instance_map[node.id];
|
||||
|
||||
instance.component = (await constructor_map.get(
|
||||
instance.component_class_id
|
||||
))!?.default;
|
||||
|
||||
if (instance.type === "dataset") {
|
||||
instance.props.component_map = get_component(
|
||||
instance.type,
|
||||
instance.component_class_id,
|
||||
root,
|
||||
components,
|
||||
instance.props.components
|
||||
).example_components;
|
||||
}
|
||||
|
||||
if (target_map[instance.id]) {
|
||||
instance.props.attached_events = Object.keys(target_map[instance.id]);
|
||||
}
|
||||
|
||||
instance.props.interactive = determine_interactivity(
|
||||
instance.id,
|
||||
instance.props.interactive,
|
||||
instance.props.value,
|
||||
inputs,
|
||||
outputs
|
||||
);
|
||||
|
||||
instance.props.server = process_server_fn(
|
||||
instance.id,
|
||||
instance.props.server_fns,
|
||||
app
|
||||
);
|
||||
|
||||
_component_map.set(instance.id, instance);
|
||||
|
||||
if (node.children) {
|
||||
instance.children = await Promise.all(
|
||||
node.children.map((v) => walk_layout(v))
|
||||
);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
const layout_store: Writable<ComponentMeta> = writable();
|
||||
walk_layout(layout).then(() => {
|
||||
layout_store.set(_rootNode);
|
||||
});
|
||||
|
||||
let update_scheduled = false;
|
||||
let update_scheduled_store = writable(false);
|
||||
|
||||
function flush(): void {
|
||||
layout_store.update((layout) => {
|
||||
for (let i = 0; i < pending_updates.length; i++) {
|
||||
for (let j = 0; j < pending_updates[i].length; j++) {
|
||||
const update = pending_updates[i][j];
|
||||
const instance = instance_map[update.id];
|
||||
let new_value;
|
||||
if (Array.isArray(update.value)) new_value = [...update.value];
|
||||
else if (update.value === null) new_value = null;
|
||||
else if (typeof update.value === "object")
|
||||
new_value = { ...update.value };
|
||||
else new_value = update.value;
|
||||
instance.props[update.prop] = new_value;
|
||||
}
|
||||
}
|
||||
return layout;
|
||||
});
|
||||
|
||||
pending_updates = [];
|
||||
update_scheduled = false;
|
||||
update_scheduled_store.set(false);
|
||||
}
|
||||
|
||||
function update_value(updates: UpdateTransaction[]): void {
|
||||
pending_updates.push(updates);
|
||||
|
||||
if (!update_scheduled) {
|
||||
update_scheduled = true;
|
||||
update_scheduled_store.set(true);
|
||||
requestAnimationFrame(flush);
|
||||
}
|
||||
}
|
||||
|
||||
function get_data(id: number): any | Promise<any> {
|
||||
const comp = _component_map.get(id);
|
||||
if (comp.instance.get_value) {
|
||||
return comp.instance.get_value() as Promise<any>;
|
||||
}
|
||||
return comp.props.value;
|
||||
}
|
||||
|
||||
return {
|
||||
layout: layout_store,
|
||||
targets: target_map,
|
||||
update_value,
|
||||
get_data,
|
||||
loading_status,
|
||||
scheduled_updates: update_scheduled_store
|
||||
};
|
||||
}
|
||||
|
||||
/** An async version of 'new Function' */
|
||||
export const AsyncFunction: new (
|
||||
...args: string[]
|
||||
) => (...args: any[]) => Promise<any> = Object.getPrototypeOf(
|
||||
async function () {}
|
||||
).constructor;
|
||||
|
||||
/**
|
||||
* Takes a string of source code and returns a function that can be called with arguments
|
||||
* @param source the source code
|
||||
* @param backend_fn if there is also a backend function
|
||||
* @param input_length the number of inputs
|
||||
* @param output_length the number of outputs
|
||||
* @returns The function, or null if the source code is invalid or missing
|
||||
*/
|
||||
export function process_frontend_fn(
|
||||
source: string | null | undefined | false,
|
||||
backend_fn: boolean,
|
||||
input_length: number,
|
||||
output_length: number
|
||||
): ((...args: unknown[]) => Promise<unknown[]>) | null {
|
||||
if (!source) return null;
|
||||
|
||||
const wrap = backend_fn ? input_length === 1 : output_length === 1;
|
||||
try {
|
||||
return new AsyncFunction(
|
||||
"__fn_args",
|
||||
` let result = await (${source})(...__fn_args);
|
||||
return (${wrap} && !Array.isArray(result)) ? [result] : result;`
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Could not parse custom js method.");
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `Dependency.targets` is an array of `[id, trigger]` pairs with the indices as the `fn_index`.
|
||||
* This function take a single list of `Dependency.targets` and add those to the target_map.
|
||||
* @param targets the targets array
|
||||
* @param fn_index the function index
|
||||
* @param target_map the target map
|
||||
* @returns the tagert map
|
||||
*/
|
||||
export function create_target_meta(
|
||||
targets: Dependency["targets"],
|
||||
fn_index: number,
|
||||
target_map: TargetMap
|
||||
): TargetMap {
|
||||
targets.forEach(([id, trigger]) => {
|
||||
if (!target_map[id]) {
|
||||
target_map[id] = {};
|
||||
}
|
||||
if (
|
||||
target_map[id]?.[trigger] &&
|
||||
!target_map[id]?.[trigger].includes(fn_index)
|
||||
) {
|
||||
target_map[id][trigger].push(fn_index);
|
||||
} else {
|
||||
target_map[id][trigger] = [fn_index];
|
||||
}
|
||||
});
|
||||
|
||||
return target_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all component ids that are an input or output of a dependency
|
||||
* @param dep the dependency
|
||||
* @param inputs the set of inputs
|
||||
* @param outputs the set of outputs
|
||||
* @returns a tuple of the inputs and outputs
|
||||
*/
|
||||
export function get_inputs_outputs(
|
||||
dep: Dependency,
|
||||
inputs: Set<number>,
|
||||
outputs: Set<number>
|
||||
): [Set<number>, Set<number>] {
|
||||
dep.inputs.forEach((input) => inputs.add(input));
|
||||
dep.outputs.forEach((output) => outputs.add(output));
|
||||
return [inputs, outputs];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is not a default value
|
||||
* @param value the value to check
|
||||
* @returns default value boolean
|
||||
*/
|
||||
function has_no_default_value(value: any): boolean {
|
||||
return (
|
||||
(Array.isArray(value) && value.length === 0) ||
|
||||
value === "" ||
|
||||
value === 0 ||
|
||||
!value
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a component is interactive
|
||||
* @param id component id
|
||||
* @param interactive_prop value of the interactive prop
|
||||
* @param value the main value of the component
|
||||
* @param inputs set of ids that are inputs to a dependency
|
||||
* @param outputs set of ids that are outputs to a dependency
|
||||
* @returns if the component is interactive
|
||||
*/
|
||||
export function determine_interactivity(
|
||||
id: number,
|
||||
interactive_prop: boolean | undefined,
|
||||
value: any,
|
||||
inputs: Set<number>,
|
||||
outputs: Set<number>
|
||||
): boolean {
|
||||
if (interactive_prop === false) {
|
||||
return false;
|
||||
} else if (interactive_prop === true) {
|
||||
return true;
|
||||
} else if (
|
||||
inputs.has(id) ||
|
||||
(!outputs.has(id) && has_no_default_value(value))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
type ServerFunctions = Record<string, (...args: any[]) => Promise<any>>;
|
||||
|
||||
/**
|
||||
* Process the server function names and return a dictionary of functions
|
||||
* @param id the component id
|
||||
* @param server_fns the server function names
|
||||
* @param app the client instance
|
||||
* @returns the actual server functions
|
||||
*/
|
||||
export function process_server_fn(
|
||||
id: number,
|
||||
server_fns: string[] | undefined,
|
||||
app: client_return
|
||||
): ServerFunctions {
|
||||
if (!server_fns) {
|
||||
return {};
|
||||
}
|
||||
return server_fns.reduce((acc, fn: string) => {
|
||||
acc[fn] = async (...args: any[]) => {
|
||||
if (args.length === 1) {
|
||||
args = args[0];
|
||||
}
|
||||
const result = await app.component_server(id, fn, args);
|
||||
return result;
|
||||
};
|
||||
return acc;
|
||||
}, {} as ServerFunctions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a component from the backend
|
||||
* @param type the type of the component
|
||||
* @param class_id the class id of the component
|
||||
* @param root the root url of the app
|
||||
* @param components the list of component metadata
|
||||
* @param example_components the list of example components
|
||||
* @returns the component and its name
|
||||
*/
|
||||
export function get_component(
|
||||
type: string,
|
||||
class_id: string,
|
||||
root: string,
|
||||
components: ComponentMeta[],
|
||||
example_components?: string[]
|
||||
): {
|
||||
component: LoadingComponent;
|
||||
name: ComponentMeta["type"];
|
||||
example_components?: Map<ComponentMeta["type"], LoadingComponent>;
|
||||
} {
|
||||
let example_component_map: Map<ComponentMeta["type"], LoadingComponent> =
|
||||
new Map();
|
||||
if (type === "dataset" && example_components) {
|
||||
(example_components as string[]).forEach((name: string) => {
|
||||
if (example_component_map.has(name)) {
|
||||
return;
|
||||
}
|
||||
let _c;
|
||||
|
||||
const matching_component = components.find((c) => c.type === name);
|
||||
if (matching_component) {
|
||||
_c = load_component({
|
||||
api_url: root,
|
||||
name,
|
||||
id: matching_component.component_class_id,
|
||||
variant: "example"
|
||||
});
|
||||
example_component_map.set(name, _c.component);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const _c = load_component({
|
||||
api_url: root,
|
||||
name: type,
|
||||
id: class_id,
|
||||
variant: "component"
|
||||
});
|
||||
|
||||
return {
|
||||
component: _c.component,
|
||||
name: _c.name,
|
||||
example_components:
|
||||
example_component_map.size > 0 ? example_component_map : undefined
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload all components
|
||||
* @param components A list of component metadata
|
||||
* @param root The root url of the app
|
||||
* @returns A map of component ids to their constructors
|
||||
*/
|
||||
export function preload_all_components(
|
||||
components: ComponentMeta[],
|
||||
root: string
|
||||
): Map<ComponentMeta["type"], LoadingComponent> {
|
||||
let constructor_map: Map<ComponentMeta["type"], LoadingComponent> = new Map();
|
||||
|
||||
components.forEach((c) => {
|
||||
const { component, example_components } = get_component(
|
||||
c.type,
|
||||
c.component_class_id,
|
||||
root,
|
||||
components
|
||||
);
|
||||
|
||||
constructor_map.set(c.component_class_id, component);
|
||||
|
||||
if (example_components) {
|
||||
for (const [name, example_component] of example_components) {
|
||||
constructor_map.set(name, example_component);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return constructor_map;
|
||||
}
|
@ -1,17 +1,25 @@
|
||||
import type { ComponentType } from "svelte";
|
||||
import type { SvelteComponent } from "svelte";
|
||||
|
||||
interface ComponentImport {
|
||||
interactive: SvelteComponent;
|
||||
static: SvelteComponent;
|
||||
example: SvelteComponent;
|
||||
/** The props that are always present on a component */
|
||||
interface SharedProps {
|
||||
elem_id?: string;
|
||||
elem_classes?: string[];
|
||||
components?: string[];
|
||||
server_fns?: string[];
|
||||
interactive: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/** The metadata for a component
|
||||
* The non optional fields are what are received from the backend
|
||||
* The optional fields are what are added by the frontend
|
||||
*/
|
||||
export interface ComponentMeta {
|
||||
type: string;
|
||||
id: number;
|
||||
has_modes: boolean;
|
||||
props: Record<string, unknown> & { interactive: boolean };
|
||||
props: SharedProps;
|
||||
instance: SvelteComponent;
|
||||
component: ComponentType<SvelteComponent>;
|
||||
documentation?: Documentation;
|
||||
@ -20,11 +28,13 @@ export interface ComponentMeta {
|
||||
component_class_id: string;
|
||||
}
|
||||
|
||||
/** Dictates whether a dependency is continous and/or a generator */
|
||||
export interface DependencyTypes {
|
||||
continuous: boolean;
|
||||
generator: boolean;
|
||||
}
|
||||
|
||||
/** An event payload that is sent with an API request */
|
||||
export interface Payload {
|
||||
fn_index: number;
|
||||
data: unknown[];
|
||||
@ -32,6 +42,7 @@ export interface Payload {
|
||||
trigger_id: number | null;
|
||||
}
|
||||
|
||||
/** A dependency as received from the backend */
|
||||
export interface Dependency {
|
||||
targets: [number, string][];
|
||||
inputs: number[];
|
||||
@ -40,7 +51,7 @@ export interface Dependency {
|
||||
js: string | null;
|
||||
scroll_to_output: boolean;
|
||||
show_progress: "full" | "minimal" | "hidden";
|
||||
frontend_fn?: (...args: unknown[]) => Promise<unknown[]>;
|
||||
frontend_fn: ((...args: unknown[]) => Promise<unknown[]>) | null;
|
||||
status?: string;
|
||||
queue: boolean | null;
|
||||
api_name: string | null;
|
||||
@ -67,9 +78,24 @@ export interface Documentation {
|
||||
example_data?: string;
|
||||
}
|
||||
|
||||
/** A layout node as recived from the backend */
|
||||
export interface LayoutNode {
|
||||
id: number;
|
||||
children: LayoutNode[];
|
||||
}
|
||||
|
||||
/** The system theme mode */
|
||||
export type ThemeMode = "system" | "light" | "dark";
|
||||
|
||||
/** the target map is an object mapping the target id to a series of events (another object), those events are a mapping of the event name to the function id's they trigger */
|
||||
export type TargetMap = Record<number, Record<string, number[]>>;
|
||||
|
||||
/** A component that has been loaded via dynamic import */
|
||||
export type LoadedComponent = {
|
||||
default: ComponentMeta["component"];
|
||||
};
|
||||
|
||||
/**A component that is loading */
|
||||
export type LoadingComponent = Promise<{
|
||||
default: ComponentMeta["component"];
|
||||
}>;
|
||||
|
4
js/app/src/vite-env-override.d.ts
vendored
4
js/app/src/vite-env-override.d.ts
vendored
@ -13,8 +13,8 @@ declare module "virtual:component-loader" {
|
||||
id: string;
|
||||
variant: "component" | "example";
|
||||
}
|
||||
export function load_component(args: Args): Promise<{
|
||||
export function load_component(args: Args): {
|
||||
name: ComponentMeta["type"];
|
||||
component: LoadedComponent;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ test("chatinterface works with streaming functions and all buttons behave as exp
|
||||
const expected_text_el_2 = page.locator(".bot p", {
|
||||
hasText: "Run 3 - You typed: hello"
|
||||
});
|
||||
expect(textbox).toHaveValue("");
|
||||
await expect(expected_text_el_2).toBeVisible();
|
||||
|
||||
await expect
|
||||
|
@ -32,7 +32,8 @@ import {
|
||||
generate_dev_entry,
|
||||
handle_ce_css,
|
||||
inject_component_loader,
|
||||
resolve_svelte
|
||||
resolve_svelte,
|
||||
mock_modules
|
||||
} from "./build_plugins";
|
||||
|
||||
const GRADIO_VERSION = version_raw || "asd_stub_asd";
|
||||
@ -41,7 +42,6 @@ const TEST_MODE = process.env.TEST_MODE || "happy-dom";
|
||||
|
||||
//@ts-ignore
|
||||
export default defineConfig(({ mode }) => {
|
||||
console.log(mode);
|
||||
const targets = {
|
||||
production: "../../gradio/templates/frontend",
|
||||
"dev:custom": "../../gradio/templates/frontend"
|
||||
@ -161,7 +161,8 @@ export default defineConfig(({ mode }) => {
|
||||
inject_ejs(),
|
||||
generate_cdn_entry({ version: GRADIO_VERSION, cdn_base: CDN_BASE }),
|
||||
handle_ce_css(),
|
||||
inject_component_loader()
|
||||
inject_component_loader({ mode }),
|
||||
mode === "test" && mock_modules()
|
||||
],
|
||||
optimizeDeps: {
|
||||
exclude: ["@ffmpeg/ffmpeg", "@ffmpeg/util"]
|
||||
@ -179,6 +180,7 @@ export default defineConfig(({ mode }) => {
|
||||
if (log.includes("was created with unknown prop")) return false;
|
||||
}
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
// For the Wasm app to import the wheel file URLs.
|
||||
|
@ -4,7 +4,7 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { afterUpdate } from "svelte";
|
||||
import { afterUpdate, tick } from "svelte";
|
||||
import type { Gradio, SelectData } from "@gradio/utils";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import Table from "./shared/Table.svelte";
|
||||
@ -20,7 +20,7 @@
|
||||
headers: ["1", "2", "3"],
|
||||
metadata: null
|
||||
};
|
||||
let old_value: string = JSON.stringify(value);
|
||||
let old_value = "";
|
||||
export let value_is_output = false;
|
||||
export let col_count: [number, "fixed" | "dynamic"];
|
||||
export let row_count: [number, "fixed" | "dynamic"];
|
||||
@ -49,21 +49,44 @@
|
||||
export let loading_status: LoadingStatus;
|
||||
export let interactive: boolean;
|
||||
|
||||
function handle_change(): void {
|
||||
let _headers: Headers;
|
||||
let display_value: string[][] | null;
|
||||
let styling: string[][] | null;
|
||||
let values: (string | number)[][];
|
||||
async function handle_change(data?: {
|
||||
data: Data;
|
||||
headers: Headers;
|
||||
metadata: Metadata;
|
||||
}): Promise<void> {
|
||||
let _data = data || value;
|
||||
|
||||
_headers = [...(_data.headers || headers)];
|
||||
values = _data.data ? [..._data.data] : [];
|
||||
display_value = _data?.metadata?.display_value
|
||||
? [..._data?.metadata?.display_value]
|
||||
: null;
|
||||
styling = _data?.metadata?.styling ? [..._data?.metadata?.styling] : null;
|
||||
await tick();
|
||||
|
||||
gradio.dispatch("change");
|
||||
if (!value_is_output) {
|
||||
gradio.dispatch("input");
|
||||
}
|
||||
}
|
||||
|
||||
handle_change();
|
||||
|
||||
afterUpdate(() => {
|
||||
value_is_output = false;
|
||||
});
|
||||
|
||||
$: {
|
||||
if (JSON.stringify(value) !== old_value) {
|
||||
if (old_value && JSON.stringify(value) !== old_value) {
|
||||
old_value = JSON.stringify(value);
|
||||
handle_change();
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(Array.isArray(value) && value?.[0]?.length === 0) ||
|
||||
value.data?.[0]?.length === 0
|
||||
@ -76,6 +99,18 @@
|
||||
metadata: null
|
||||
};
|
||||
}
|
||||
|
||||
async function handle_value_change(data: {
|
||||
data: Data;
|
||||
headers: Headers;
|
||||
metadata: Metadata;
|
||||
}): Promise<void> {
|
||||
if (JSON.stringify(data) !== old_value) {
|
||||
value = { ...data };
|
||||
old_value = JSON.stringify(value);
|
||||
handle_change(data);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Block
|
||||
@ -99,9 +134,11 @@
|
||||
{show_label}
|
||||
{row_count}
|
||||
{col_count}
|
||||
{value}
|
||||
{headers}
|
||||
on:change={interactive ? (e) => (value = e.detail) : () => {}}
|
||||
{values}
|
||||
{display_value}
|
||||
{styling}
|
||||
headers={_headers}
|
||||
on:change={(e) => handle_value_change(e.detail)}
|
||||
on:select={(e) => gradio.dispatch("select", e.detail)}
|
||||
{wrap}
|
||||
{datatype}
|
||||
|
@ -21,8 +21,7 @@
|
||||
export let label: string | null = null;
|
||||
export let show_label = true;
|
||||
export let headers: Headers = [];
|
||||
let values: (string | number)[][];
|
||||
export let value: { data: Data; headers: Headers; metadata: Metadata } | null;
|
||||
export let values: (string | number)[][] = [];
|
||||
export let col_count: [number, "fixed" | "dynamic"];
|
||||
export let row_count: [number, "fixed" | "dynamic"];
|
||||
export let latex_delimiters: {
|
||||
@ -41,21 +40,10 @@
|
||||
export let column_widths: string[] = [];
|
||||
|
||||
let selected: false | [number, number] = false;
|
||||
let display_value: string[][] | null = value?.metadata?.display_value ?? null;
|
||||
let styling: string[][] | null = value?.metadata?.styling ?? null;
|
||||
export let display_value: string[][] | null = null;
|
||||
export let styling: string[][] | null = null;
|
||||
let t_rect: DOMRectReadOnly;
|
||||
|
||||
$: {
|
||||
if (value) {
|
||||
headers = value.headers;
|
||||
values = value.data;
|
||||
display_value = value?.metadata?.display_value ?? null;
|
||||
styling = value?.metadata?.styling ?? null;
|
||||
} else if (values === null) {
|
||||
values = [];
|
||||
}
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: {
|
||||
data: (string | number)[][];
|
||||
@ -69,6 +57,7 @@
|
||||
|
||||
const get_data_at = (row: number, col: number): string | number =>
|
||||
data?.[row]?.[col]?.value;
|
||||
|
||||
$: {
|
||||
if (selected !== false) {
|
||||
const [row, col] = selected;
|
||||
@ -77,6 +66,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let els: Record<
|
||||
string,
|
||||
{ cell: null | HTMLTableCellElement; input: null | HTMLInputElement }
|
||||
@ -87,6 +77,7 @@
|
||||
function make_id(): string {
|
||||
return Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
|
||||
function make_headers(_head: Headers): HeadersWithIDs {
|
||||
let _h = _head || [];
|
||||
if (col_count[1] === "fixed" && _h.length < col_count[0]) {
|
||||
@ -105,6 +96,7 @@
|
||||
return { id: _id, value: JSON.stringify(i + 1) };
|
||||
});
|
||||
}
|
||||
|
||||
return _h.map((h, i) => {
|
||||
const _id = make_id();
|
||||
els[_id] = { cell: null, input: null };
|
||||
@ -157,7 +149,9 @@
|
||||
_headers = make_headers(headers);
|
||||
|
||||
old_headers = headers.slice();
|
||||
trigger_change();
|
||||
}
|
||||
|
||||
$: if (!dequal(values, old_val)) {
|
||||
data = process_data(values as (string | number)[][]);
|
||||
old_val = values as (string | number)[][];
|
||||
@ -167,7 +161,7 @@
|
||||
|
||||
let old_val: undefined | (string | number)[][] = undefined;
|
||||
|
||||
$: _headers &&
|
||||
async function trigger_change(): Promise<void> {
|
||||
dispatch("change", {
|
||||
data: data.map((r) => r.map(({ value }) => value)),
|
||||
headers: _headers.map((h) => h.value),
|
||||
@ -175,6 +169,7 @@
|
||||
? null
|
||||
: { display_value: display_value, styling: styling }
|
||||
});
|
||||
}
|
||||
|
||||
function get_sort_status(
|
||||
name: string,
|
||||
@ -425,6 +420,8 @@
|
||||
selected = [index ? index + 1 : data.length - 1, 0];
|
||||
}
|
||||
|
||||
$: data && trigger_change();
|
||||
|
||||
async function add_col(): Promise<void> {
|
||||
parent.focus();
|
||||
if (col_count[1] !== "dynamic") return;
|
||||
|
@ -7,8 +7,7 @@
|
||||
export let component_map: Map<
|
||||
string,
|
||||
Promise<{
|
||||
name: string;
|
||||
component: { default: ComponentType<SvelteComponent> };
|
||||
default: ComponentType<SvelteComponent>;
|
||||
}>
|
||||
>;
|
||||
export let label = "Examples";
|
||||
@ -46,6 +45,7 @@
|
||||
function handle_mouseenter(i: number): void {
|
||||
current_hover = i;
|
||||
}
|
||||
|
||||
function handle_mouseleave(): void {
|
||||
current_hover = -1;
|
||||
}
|
||||
@ -89,7 +89,7 @@
|
||||
sample_row.map(async (sample_cell, j) => {
|
||||
return {
|
||||
value: sample_cell,
|
||||
component: (await component_map.get(components[j]))?.component
|
||||
component: (await component_map.get(components[j]))
|
||||
?.default as ComponentType<SvelteComponent>
|
||||
};
|
||||
})
|
||||
|
@ -57,10 +57,10 @@
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-plugin-svelte": "^2.32.4",
|
||||
"globals": "^14.0.0",
|
||||
"happy-dom": "^13.3.8",
|
||||
"happy-dom": "^13.6.2",
|
||||
"jsdom": "^24.0.0",
|
||||
"kleur": "^4.1.5",
|
||||
"msw": "^2.0.0",
|
||||
"msw": "^2.2.1",
|
||||
"node-html-parser": "^6.0.0",
|
||||
"npm-run-all2": "^6.0.0",
|
||||
"playwright": "^1.39.0",
|
||||
@ -86,7 +86,7 @@
|
||||
"typescript-svelte-plugin": "^0.3.34",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "^1.2.2"
|
||||
"vitest": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-a11y": "^7.5.1",
|
||||
|
278
pnpm-lock.yaml
278
pnpm-lock.yaml
@ -43,7 +43,7 @@ importers:
|
||||
version: 9.0.0
|
||||
'@testing-library/jest-dom':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0(vitest@1.2.2)
|
||||
version: 6.0.0(vitest@1.3.1)
|
||||
'@testing-library/user-event':
|
||||
specifier: ^14.0.0
|
||||
version: 14.0.0(@testing-library/dom@9.0.0)
|
||||
@ -81,8 +81,8 @@ importers:
|
||||
specifier: ^14.0.0
|
||||
version: 14.0.0
|
||||
happy-dom:
|
||||
specifier: ^13.3.8
|
||||
version: 13.3.8
|
||||
specifier: ^13.6.2
|
||||
version: 13.6.2
|
||||
jsdom:
|
||||
specifier: ^24.0.0
|
||||
version: 24.0.0
|
||||
@ -90,8 +90,8 @@ importers:
|
||||
specifier: ^4.1.5
|
||||
version: 4.1.5
|
||||
msw:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(typescript@5.0.2)
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1(typescript@5.0.2)
|
||||
node-html-parser:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
@ -168,8 +168,8 @@ importers:
|
||||
specifier: 1.0.3
|
||||
version: 1.0.3
|
||||
vitest:
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2(@types/node@20.3.2)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1(@types/node@20.3.2)(happy-dom@13.6.2)(jsdom@24.0.0)(less@4.2.0)
|
||||
devDependencies:
|
||||
'@storybook/addon-a11y':
|
||||
specifier: ^7.5.1
|
||||
@ -2906,12 +2906,6 @@ packages:
|
||||
cookie: 0.5.0
|
||||
dev: false
|
||||
|
||||
/@bundled-es-modules/js-levenshtein@2.0.1:
|
||||
resolution: {integrity: sha512-DERMS3yfbAljKsQc0U2wcqGKUWpdFjwqWuoMugEJlqBnKO180/n+4SR/J8MRDt1AN48X1ovgoD9KrdVXcaa3Rg==}
|
||||
dependencies:
|
||||
js-levenshtein: 1.1.6
|
||||
dev: false
|
||||
|
||||
/@bundled-es-modules/statuses@1.0.1:
|
||||
resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==}
|
||||
dependencies:
|
||||
@ -4297,6 +4291,39 @@ packages:
|
||||
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
|
||||
dev: false
|
||||
|
||||
/@inquirer/confirm@3.0.0:
|
||||
resolution: {integrity: sha512-LHeuYP1D8NmQra1eR4UqvZMXwxEdDXyElJmmZfU44xdNLL6+GcQBS0uE16vyfZVjH8c22p9e+DStROfE/hyHrg==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
'@inquirer/core': 7.0.0
|
||||
'@inquirer/type': 1.2.0
|
||||
dev: false
|
||||
|
||||
/@inquirer/core@7.0.0:
|
||||
resolution: {integrity: sha512-g13W5yEt9r1sEVVriffJqQ8GWy94OnfxLCreNSOTw0HPVcszmc/If1KIf7YBmlwtX4klmvwpZHnQpl3N7VX2xA==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
'@inquirer/type': 1.2.0
|
||||
'@types/mute-stream': 0.0.4
|
||||
'@types/node': 20.11.20
|
||||
'@types/wrap-ansi': 3.0.0
|
||||
ansi-escapes: 4.3.2
|
||||
chalk: 4.1.2
|
||||
cli-spinners: 2.9.2
|
||||
cli-width: 4.1.0
|
||||
figures: 3.2.0
|
||||
mute-stream: 1.0.0
|
||||
run-async: 3.0.0
|
||||
signal-exit: 4.1.0
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 6.2.0
|
||||
dev: false
|
||||
|
||||
/@inquirer/type@1.2.0:
|
||||
resolution: {integrity: sha512-/vvkUkYhrjbm+RolU7V1aUFDydZVKNKqKHR5TsE+j5DXgXFwrsOPcoGUJ02K0O7q7O53CU2DOTMYCHeGZ25WHA==}
|
||||
engines: {node: '>=18'}
|
||||
dev: false
|
||||
|
||||
/@isaacs/cliui@8.0.2:
|
||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||
engines: {node: '>=12'}
|
||||
@ -4577,15 +4604,15 @@ packages:
|
||||
engines: {node: '>=18'}
|
||||
dev: false
|
||||
|
||||
/@mswjs/interceptors@0.25.11:
|
||||
resolution: {integrity: sha512-27aonWAjdeoZN4j4j6QvePOSOacQUucFRUESAU8FUXsmmagDjmyOi4/NHizxzY/NHSk/HAyqF/IMhl+9puhqNw==}
|
||||
/@mswjs/interceptors@0.25.16:
|
||||
resolution: {integrity: sha512-8QC8JyKztvoGAdPgyZy49c9vSHHAZjHagwl4RY9E8carULk8ym3iTaiawrT1YoLF/qb449h48f71XDPgkUSOUg==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
'@open-draft/deferred-promise': 2.2.0
|
||||
'@open-draft/logger': 0.3.0
|
||||
'@open-draft/until': 2.1.0
|
||||
is-node-process: 1.2.0
|
||||
outvariant: 1.4.0
|
||||
outvariant: 1.4.2
|
||||
strict-event-emitter: 0.5.1
|
||||
dev: false
|
||||
|
||||
@ -4623,7 +4650,7 @@ packages:
|
||||
resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==}
|
||||
dependencies:
|
||||
is-node-process: 1.2.0
|
||||
outvariant: 1.4.0
|
||||
outvariant: 1.4.2
|
||||
dev: false
|
||||
|
||||
/@open-draft/until@2.1.0:
|
||||
@ -7165,7 +7192,7 @@ packages:
|
||||
lz-string: 1.5.0
|
||||
pretty-format: 27.5.1
|
||||
|
||||
/@testing-library/jest-dom@6.0.0(vitest@1.2.2):
|
||||
/@testing-library/jest-dom@6.0.0(vitest@1.3.1):
|
||||
resolution: {integrity: sha512-Ye2R3+/oM27jir8CzYPmuWdavTaKwNZcu0d22L9pO/vnOYE0wmrtpw79TQJa8H6gV8/i7yd+pLaqeLlA0rTMfg==}
|
||||
engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
|
||||
peerDependencies:
|
||||
@ -7191,7 +7218,7 @@ packages:
|
||||
dom-accessibility-api: 0.5.16
|
||||
lodash: 4.17.21
|
||||
redent: 3.0.0
|
||||
vitest: 1.2.2(@types/node@20.3.2)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0)
|
||||
vitest: 1.3.1(@types/node@20.3.2)(happy-dom@13.6.2)(jsdom@24.0.0)(less@4.2.0)
|
||||
dev: false
|
||||
|
||||
/@testing-library/user-event@14.0.0(@testing-library/dom@9.0.0):
|
||||
@ -7271,13 +7298,13 @@ packages:
|
||||
'@types/node': 20.9.0
|
||||
dev: true
|
||||
|
||||
/@types/cookie@0.4.1:
|
||||
resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
|
||||
dev: false
|
||||
|
||||
/@types/cookie@0.5.4:
|
||||
resolution: {integrity: sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==}
|
||||
|
||||
/@types/cookie@0.6.0:
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
dev: false
|
||||
|
||||
/@types/cross-spawn@6.0.5:
|
||||
resolution: {integrity: sha512-wsIMP68FvGXk+RaWhraz6Xp4v7sl4qwzHAmtPaJEN2NRTXXI9LtFawUpeTsBNL/pd6QoLStdytCaAyiK7AEd/Q==}
|
||||
dependencies:
|
||||
@ -7407,10 +7434,6 @@ packages:
|
||||
pretty-format: 29.7.0
|
||||
dev: false
|
||||
|
||||
/@types/js-levenshtein@1.1.3:
|
||||
resolution: {integrity: sha512-jd+Q+sD20Qfu9e2aEXogiO3vpOC1PYJOUdyN9gvs4Qrvkg4wF43L5OhqrPeokdv8TL0/mXoYfpkcoGZMNN2pkQ==}
|
||||
dev: false
|
||||
|
||||
/@types/json-schema@7.0.15:
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
@ -7442,6 +7465,12 @@ packages:
|
||||
resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
|
||||
dev: false
|
||||
|
||||
/@types/mute-stream@0.0.4:
|
||||
resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==}
|
||||
dependencies:
|
||||
'@types/node': 20.11.20
|
||||
dev: false
|
||||
|
||||
/@types/node-fetch@2.6.9:
|
||||
resolution: {integrity: sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==}
|
||||
dependencies:
|
||||
@ -7463,7 +7492,6 @@ packages:
|
||||
resolution: {integrity: sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
dev: true
|
||||
|
||||
/@types/node@20.3.2:
|
||||
resolution: {integrity: sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw==}
|
||||
@ -7587,6 +7615,10 @@ packages:
|
||||
resolution: {integrity: sha512-ASCxdbsrwNfSMXALlC3Decif9rwDMu+80KGp5zI2RLRotfMsTv7fHL8W8VDp24wymzDyIFudhUeSCugrgRFfHQ==}
|
||||
dev: false
|
||||
|
||||
/@types/wrap-ansi@3.0.0:
|
||||
resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==}
|
||||
dev: false
|
||||
|
||||
/@types/ws@8.5.4:
|
||||
resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
|
||||
dependencies:
|
||||
@ -7760,38 +7792,38 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@vitest/expect@1.2.2:
|
||||
resolution: {integrity: sha512-3jpcdPAD7LwHUUiT2pZTj2U82I2Tcgg2oVPvKxhn6mDI2On6tfvPQTjAI4628GUGDZrCm4Zna9iQHm5cEexOAg==}
|
||||
/@vitest/expect@1.3.1:
|
||||
resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==}
|
||||
dependencies:
|
||||
'@vitest/spy': 1.2.2
|
||||
'@vitest/utils': 1.2.2
|
||||
'@vitest/spy': 1.3.1
|
||||
'@vitest/utils': 1.3.1
|
||||
chai: 4.3.10
|
||||
dev: false
|
||||
|
||||
/@vitest/runner@1.2.2:
|
||||
resolution: {integrity: sha512-JctG7QZ4LSDXr5CsUweFgcpEvrcxOV1Gft7uHrvkQ+fsAVylmWQvnaAr/HDp3LAH1fztGMQZugIheTWjaGzYIg==}
|
||||
/@vitest/runner@1.3.1:
|
||||
resolution: {integrity: sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==}
|
||||
dependencies:
|
||||
'@vitest/utils': 1.2.2
|
||||
'@vitest/utils': 1.3.1
|
||||
p-limit: 5.0.0
|
||||
pathe: 1.1.1
|
||||
dev: false
|
||||
|
||||
/@vitest/snapshot@1.2.2:
|
||||
resolution: {integrity: sha512-SmGY4saEw1+bwE1th6S/cZmPxz/Q4JWsl7LvbQIky2tKE35US4gd0Mjzqfr84/4OD0tikGWaWdMja/nWL5NIPA==}
|
||||
/@vitest/snapshot@1.3.1:
|
||||
resolution: {integrity: sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==}
|
||||
dependencies:
|
||||
magic-string: 0.30.5
|
||||
pathe: 1.1.1
|
||||
pretty-format: 29.7.0
|
||||
dev: false
|
||||
|
||||
/@vitest/spy@1.2.2:
|
||||
resolution: {integrity: sha512-k9Gcahssw8d7X3pSLq3e3XEu/0L78mUkCjivUqCQeXJm9clfXR/Td8+AP+VC1O6fKPIDLcHDTAmBOINVuv6+7g==}
|
||||
/@vitest/spy@1.3.1:
|
||||
resolution: {integrity: sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==}
|
||||
dependencies:
|
||||
tinyspy: 2.2.0
|
||||
dev: false
|
||||
|
||||
/@vitest/utils@1.2.2:
|
||||
resolution: {integrity: sha512-WKITBHLsBHlpjnDQahr+XK6RE7MiAsgrIkr0pGhQ9ygoxBfUeG0lUG5iLlzqjmKSlBv3+j5EGsriBzh+C3Tq9g==}
|
||||
/@vitest/utils@1.3.1:
|
||||
resolution: {integrity: sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==}
|
||||
dependencies:
|
||||
diff-sequences: 29.6.3
|
||||
estree-walker: 3.0.3
|
||||
@ -8395,6 +8427,7 @@ packages:
|
||||
|
||||
/base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
dev: true
|
||||
|
||||
/better-opn@3.0.2:
|
||||
resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==}
|
||||
@ -8435,6 +8468,7 @@ packages:
|
||||
buffer: 5.7.1
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/body-parser@1.20.1:
|
||||
resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
|
||||
@ -8548,6 +8582,7 @@ packages:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
dev: true
|
||||
|
||||
/bufferutil@4.0.7:
|
||||
resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==}
|
||||
@ -8734,10 +8769,17 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
restore-cursor: 3.1.0
|
||||
dev: true
|
||||
|
||||
/cli-spinners@2.9.1:
|
||||
resolution: {integrity: sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/cli-spinners@2.9.2:
|
||||
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/cli-table3@0.6.3:
|
||||
resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==}
|
||||
@ -8748,9 +8790,9 @@ packages:
|
||||
'@colors/colors': 1.5.0
|
||||
dev: true
|
||||
|
||||
/cli-width@3.0.0:
|
||||
resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
|
||||
engines: {node: '>= 10'}
|
||||
/cli-width@4.1.0:
|
||||
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
||||
engines: {node: '>= 12'}
|
||||
dev: false
|
||||
|
||||
/cliui@6.0.0:
|
||||
@ -10753,14 +10795,6 @@ packages:
|
||||
combined-stream: 1.0.8
|
||||
mime-types: 2.1.35
|
||||
|
||||
/formdata-node@4.4.1:
|
||||
resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
|
||||
engines: {node: '>= 12.20'}
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 4.0.0-beta.3
|
||||
dev: false
|
||||
|
||||
/forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -11106,8 +11140,8 @@ packages:
|
||||
uglify-js: 3.17.4
|
||||
dev: true
|
||||
|
||||
/happy-dom@13.3.8:
|
||||
resolution: {integrity: sha512-RAbq4oYfJNkVan1m1F3jfA4YEyRY0/ASoNvZsNJbuX85jIypidmsz9jQZD7Tqz0VXA2MhAGfcsh5oshwmwNYSg==}
|
||||
/happy-dom@13.6.2:
|
||||
resolution: {integrity: sha512-Ku+wDqcF/KwFA0dI+xIMZd9Jn020RXjuSil/Vz7gu2yhDC3FsDYZ55qqV9k+SGC4opwb4acisXqVSRxUJMlPbQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
dependencies:
|
||||
entities: 4.5.0
|
||||
@ -11282,6 +11316,7 @@ packages:
|
||||
|
||||
/ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
dev: true
|
||||
|
||||
/ignore@4.0.6:
|
||||
resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==}
|
||||
@ -11334,27 +11369,6 @@ packages:
|
||||
/inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
||||
/inquirer@8.2.6:
|
||||
resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
dependencies:
|
||||
ansi-escapes: 4.3.2
|
||||
chalk: 4.1.2
|
||||
cli-cursor: 3.1.0
|
||||
cli-width: 3.0.0
|
||||
external-editor: 3.1.0
|
||||
figures: 3.2.0
|
||||
lodash: 4.17.21
|
||||
mute-stream: 0.0.8
|
||||
ora: 5.4.1
|
||||
run-async: 2.4.1
|
||||
rxjs: 7.8.1
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
through: 2.3.8
|
||||
wrap-ansi: 6.2.0
|
||||
dev: false
|
||||
|
||||
/internal-slot@1.0.6:
|
||||
resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -11522,6 +11536,7 @@ packages:
|
||||
/is-interactive@1.0.0:
|
||||
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/is-module@1.0.0:
|
||||
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
|
||||
@ -11649,6 +11664,7 @@ packages:
|
||||
/is-unicode-supported@0.1.0:
|
||||
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/is-weakref@1.0.2:
|
||||
resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
|
||||
@ -11849,11 +11865,6 @@ packages:
|
||||
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
|
||||
dev: false
|
||||
|
||||
/js-levenshtein@1.1.6:
|
||||
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/js-stringify@1.0.2:
|
||||
resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==}
|
||||
dev: false
|
||||
@ -11861,6 +11872,10 @@ packages:
|
||||
/js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
/js-tokens@8.0.3:
|
||||
resolution: {integrity: sha512-UfJMcSJc+SEXEl9lH/VLHSZbThQyLpw1vLO1Lb+j4RWDvG3N2f7yj3PVQA3cmkTBNldJ9eFnM+xEXxHIXrYiJw==}
|
||||
dev: false
|
||||
|
||||
/js-yaml@3.14.1:
|
||||
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
|
||||
hasBin: true
|
||||
@ -12266,6 +12281,7 @@ packages:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
is-unicode-supported: 0.1.0
|
||||
dev: true
|
||||
|
||||
/loose-envify@1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
@ -12567,6 +12583,7 @@ packages:
|
||||
/mimic-fn@2.1.0:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/mimic-fn@4.0.0:
|
||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||
@ -12686,43 +12703,35 @@ packages:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
dev: true
|
||||
|
||||
/msw@2.0.0(typescript@5.0.2):
|
||||
resolution: {integrity: sha512-lw9UHuzNCWoODHaThGeLLIIuzEBUQkj3fJXQnChHifMKbB2UmF2msHd4d/lnyqjAyD0XWoibdviW9wlstFPpkA==}
|
||||
/msw@2.2.1(typescript@5.0.2):
|
||||
resolution: {integrity: sha512-DCsZAQwan+2onEcpD86fiEnCKW4IvYzqcwDq/2TIoeNrmBqNp/mJW4wHQyxcoYrRPwgujin7wDFflqiSO1iT/w==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
peerDependencies:
|
||||
typescript: '>= 4.7.x <= 5.2.x'
|
||||
typescript: '>= 4.7.x <= 5.3.x'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@bundled-es-modules/cookie': 2.0.0
|
||||
'@bundled-es-modules/js-levenshtein': 2.0.1
|
||||
'@bundled-es-modules/statuses': 1.0.1
|
||||
'@inquirer/confirm': 3.0.0
|
||||
'@mswjs/cookies': 1.1.0
|
||||
'@mswjs/interceptors': 0.25.11
|
||||
'@mswjs/interceptors': 0.25.16
|
||||
'@open-draft/until': 2.1.0
|
||||
'@types/cookie': 0.4.1
|
||||
'@types/js-levenshtein': 1.1.3
|
||||
'@types/cookie': 0.6.0
|
||||
'@types/statuses': 2.0.4
|
||||
chalk: 4.1.2
|
||||
chokidar: 3.5.3
|
||||
formdata-node: 4.4.1
|
||||
graphql: 16.8.1
|
||||
headers-polyfill: 4.0.2
|
||||
inquirer: 8.2.6
|
||||
is-node-process: 1.2.0
|
||||
js-levenshtein: 1.1.6
|
||||
node-fetch: 2.7.0
|
||||
outvariant: 1.4.0
|
||||
outvariant: 1.4.2
|
||||
path-to-regexp: 6.2.1
|
||||
strict-event-emitter: 0.5.1
|
||||
type-fest: 2.19.0
|
||||
type-fest: 4.10.3
|
||||
typescript: 5.0.2
|
||||
yargs: 17.7.2
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
dev: false
|
||||
|
||||
/multi-buffer-data-view@5.0.11:
|
||||
@ -12733,8 +12742,9 @@ packages:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/mute-stream@0.0.8:
|
||||
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
|
||||
/mute-stream@1.0.0:
|
||||
resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
dev: false
|
||||
|
||||
/mz@2.7.0:
|
||||
@ -12790,11 +12800,6 @@ packages:
|
||||
minimatch: 3.1.2
|
||||
dev: true
|
||||
|
||||
/node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
dev: false
|
||||
|
||||
/node-fetch-native@1.4.1:
|
||||
resolution: {integrity: sha512-NsXBU0UgBxo2rQLOeWNZqS3fvflWePMECr8CoSWoSTqCqGbVVsvl9vZu1HfQicYN0g5piV9Gh8RTEvo/uP752w==}
|
||||
dev: true
|
||||
@ -12964,6 +12969,7 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
dev: true
|
||||
|
||||
/onetime@6.0.0:
|
||||
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
|
||||
@ -13005,6 +13011,7 @@ packages:
|
||||
log-symbols: 4.1.0
|
||||
strip-ansi: 6.0.1
|
||||
wcwidth: 1.0.1
|
||||
dev: true
|
||||
|
||||
/os-tmpdir@1.0.2:
|
||||
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
|
||||
@ -13015,8 +13022,8 @@ packages:
|
||||
resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==}
|
||||
dev: false
|
||||
|
||||
/outvariant@1.4.0:
|
||||
resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==}
|
||||
/outvariant@1.4.2:
|
||||
resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==}
|
||||
dev: false
|
||||
|
||||
/p-filter@2.1.0:
|
||||
@ -14216,6 +14223,7 @@ packages:
|
||||
dependencies:
|
||||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
dev: true
|
||||
|
||||
/reusify@1.0.4:
|
||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||
@ -14289,8 +14297,8 @@ packages:
|
||||
resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
|
||||
dev: false
|
||||
|
||||
/run-async@2.4.1:
|
||||
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
|
||||
/run-async@3.0.0:
|
||||
resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: false
|
||||
|
||||
@ -14307,12 +14315,6 @@ packages:
|
||||
resolution: {integrity: sha512-ASEq9atUw7lualXB+knvgtvwkCEvGWV2gDD/8qnASzBkzEARZck9JAyxmY8OS6Nc1pCPEgDTKNcx+YqqYfzArw==}
|
||||
dev: false
|
||||
|
||||
/rxjs@7.8.1:
|
||||
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/sade@1.8.1:
|
||||
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
||||
engines: {node: '>=6'}
|
||||
@ -14819,10 +14821,10 @@ packages:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
/strip-literal@1.3.0:
|
||||
resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==}
|
||||
/strip-literal@2.0.0:
|
||||
resolution: {integrity: sha512-f9vHgsCWBq2ugHAkGMiiYY+AYG0D/cbloKKg0nhaaaSNsujdGIpVXCNsrJpCKr5M0f4aI31mr13UjY6GAuXCKA==}
|
||||
dependencies:
|
||||
acorn: 8.11.2
|
||||
js-tokens: 8.0.3
|
||||
dev: false
|
||||
|
||||
/style-loader@3.3.3(webpack@5.90.3):
|
||||
@ -15442,10 +15444,6 @@ packages:
|
||||
xtend: 4.0.2
|
||||
dev: true
|
||||
|
||||
/through@2.3.8:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
dev: false
|
||||
|
||||
/timers-ext@0.1.7:
|
||||
resolution: {integrity: sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==}
|
||||
dependencies:
|
||||
@ -15656,6 +15654,12 @@ packages:
|
||||
/type-fest@2.19.0:
|
||||
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: true
|
||||
|
||||
/type-fest@4.10.3:
|
||||
resolution: {integrity: sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==}
|
||||
engines: {node: '>=16'}
|
||||
dev: false
|
||||
|
||||
/type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
@ -16398,8 +16402,8 @@ packages:
|
||||
unist-util-stringify-position: 2.0.3
|
||||
dev: false
|
||||
|
||||
/vite-node@1.2.2(@types/node@20.3.2)(less@4.2.0):
|
||||
resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==}
|
||||
/vite-node@1.3.1(@types/node@20.3.2)(less@4.2.0):
|
||||
resolution: {integrity: sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@ -16590,15 +16594,15 @@ packages:
|
||||
dependencies:
|
||||
vite: 4.5.2(@types/node@20.3.2)(less@4.2.0)(lightningcss@1.21.7)(sass@1.66.1)(stylus@0.63.0)(sugarss@4.0.1)
|
||||
|
||||
/vitest@1.2.2(@types/node@20.3.2)(happy-dom@13.3.8)(jsdom@24.0.0)(less@4.2.0):
|
||||
resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==}
|
||||
/vitest@1.3.1(@types/node@20.3.2)(happy-dom@13.6.2)(jsdom@24.0.0)(less@4.2.0):
|
||||
resolution: {integrity: sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@types/node': ^18.0.0 || >=20.0.0
|
||||
'@vitest/browser': ^1.0.0
|
||||
'@vitest/ui': ^1.0.0
|
||||
'@vitest/browser': 1.3.1
|
||||
'@vitest/ui': 1.3.1
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
peerDependenciesMeta:
|
||||
@ -16616,28 +16620,27 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 20.3.2
|
||||
'@vitest/expect': 1.2.2
|
||||
'@vitest/runner': 1.2.2
|
||||
'@vitest/snapshot': 1.2.2
|
||||
'@vitest/spy': 1.2.2
|
||||
'@vitest/utils': 1.2.2
|
||||
'@vitest/expect': 1.3.1
|
||||
'@vitest/runner': 1.3.1
|
||||
'@vitest/snapshot': 1.3.1
|
||||
'@vitest/spy': 1.3.1
|
||||
'@vitest/utils': 1.3.1
|
||||
acorn-walk: 8.3.2
|
||||
cac: 6.7.14
|
||||
chai: 4.3.10
|
||||
debug: 4.3.4
|
||||
execa: 8.0.1
|
||||
happy-dom: 13.3.8
|
||||
happy-dom: 13.6.2
|
||||
jsdom: 24.0.0
|
||||
local-pkg: 0.5.0
|
||||
magic-string: 0.30.5
|
||||
pathe: 1.1.1
|
||||
picocolors: 1.0.0
|
||||
std-env: 3.7.0
|
||||
strip-literal: 1.3.0
|
||||
strip-literal: 2.0.0
|
||||
tinybench: 2.5.1
|
||||
tinypool: 0.8.2
|
||||
vite: 5.1.1(@types/node@20.3.2)(less@4.2.0)
|
||||
vite-node: 1.2.2(@types/node@20.3.2)(less@4.2.0)
|
||||
vite-node: 1.3.1(@types/node@20.3.2)(less@4.2.0)
|
||||
why-is-node-running: 2.2.2
|
||||
transitivePeerDependencies:
|
||||
- less
|
||||
@ -16688,11 +16691,6 @@ packages:
|
||||
dependencies:
|
||||
defaults: 1.0.4
|
||||
|
||||
/web-streams-polyfill@4.0.0-beta.3:
|
||||
resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
|
||||
engines: {node: '>= 14'}
|
||||
dev: false
|
||||
|
||||
/webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user