mirror of
https://github.com/gradio-app/gradio.git
synced 2025-01-06 10:25:17 +08:00
Merge branch 'gradio-dash-fe' of https://github.com/gradio-app/gradio into gradio-dash-fe
This commit is contained in:
commit
26cdff3d84
@ -7,94 +7,6 @@
|
||||
import { tick } from "svelte";
|
||||
setupi18n();
|
||||
|
||||
// const json = {
|
||||
// mode: "blocks",
|
||||
// components: [
|
||||
// {
|
||||
// id: 1,
|
||||
// type: "markdown",
|
||||
// props: {
|
||||
// label:
|
||||
// "\n\t# Detect Disease From Scan\n\tWith this model you can lorem ipsum\n\t- ipsum 1\n\t- ipsum 2\n\t"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// id: 2,
|
||||
// type: "checkboxgroup",
|
||||
// props: {
|
||||
// choices: ["Covid", "Malaria", "Lung Cancer"],
|
||||
// default: [],
|
||||
// label: "Disease to Scan For"
|
||||
// }
|
||||
// },
|
||||
// { id: 3, type: "tabs" },
|
||||
// { id: 4, type: "tabitem", props: { label: "X-ray" } },
|
||||
// { id: 5, type: "row" },
|
||||
// {
|
||||
// id: 6,
|
||||
// type: "image",
|
||||
// props: {
|
||||
// image_mode: "RGB",
|
||||
// shape: null,
|
||||
// source: "upload",
|
||||
// tool: "editor",
|
||||
// optional: false,
|
||||
// label: null
|
||||
// }
|
||||
// },
|
||||
// { id: 7, type: "json" },
|
||||
// { id: 8, type: "button", props: { label: "Run" } },
|
||||
// { id: 9, type: "tabitem", props: { label: "CT Scan" } },
|
||||
// { id: 10, type: "row" },
|
||||
|
||||
// {
|
||||
// id: 11,
|
||||
// type: "image",
|
||||
// props: {
|
||||
// image_mode: "RGB",
|
||||
// shape: null,
|
||||
// source: "upload",
|
||||
// tool: "editor",
|
||||
// optional: false,
|
||||
// label: null
|
||||
// }
|
||||
// },
|
||||
// { id: 12, type: "json", props: { label: null } },
|
||||
// { id: 13, type: "button", props: { label: "Run" } },
|
||||
// { id: 14, type: "textbox", props: { label: null } }
|
||||
// ],
|
||||
// theme: "default",
|
||||
// layout: {
|
||||
// children: [
|
||||
// { id: 1 },
|
||||
// { id: 2 },
|
||||
// {
|
||||
// id: 3,
|
||||
// children: [
|
||||
// {
|
||||
// id: 4,
|
||||
// children: [{ id: 5, children: [{ id: 6 }, { id: 7 }] }, { id: 8 }]
|
||||
// },
|
||||
// {
|
||||
// id: 9,
|
||||
// children: [
|
||||
// { id: 10, children: [{ id: 11 }, { id: 12 }] },
|
||||
// { id: 13 }
|
||||
// ]
|
||||
// }
|
||||
// ]
|
||||
// },
|
||||
// { id: 14 }
|
||||
// ],
|
||||
// type: "root"
|
||||
// },
|
||||
// dependencies: [
|
||||
// { targets: [8], trigger: "click", inputs: [2, 6], outputs: [7] },
|
||||
// { targets: [13], trigger: "click", inputs: [2, 11], outputs: [12] }
|
||||
// ]
|
||||
// };
|
||||
|
||||
|
||||
interface Component {
|
||||
id: string;
|
||||
type: string;
|
||||
@ -122,7 +34,6 @@
|
||||
export let layout: Layout;
|
||||
export let dependencies: Array<Dependency>;
|
||||
export let theme: string;
|
||||
export let static_src: string;
|
||||
|
||||
const dynamic_ids = dependencies.reduce((acc, next) => {
|
||||
next.inputs.forEach((i) => acc.add(i));
|
||||
@ -148,7 +59,7 @@
|
||||
|
||||
function load_component<T extends keyof typeof component_map>(
|
||||
name: T
|
||||
): Promise<{ name: T; component: SvelteComponentDev }> {
|
||||
): Promise<{ name: T; component: SvelteComponentTyped }> {
|
||||
return new Promise(async (res, rej) => {
|
||||
try {
|
||||
const c = await component_map[name]();
|
||||
@ -190,53 +101,14 @@
|
||||
let tree;
|
||||
Promise.all(Array.from(component_set)).then((v) => {
|
||||
Promise.all(layout.children.map((c) => walk_layout(c))).then((v) => {
|
||||
// console.log(v);
|
||||
console.log(v);
|
||||
tree = v;
|
||||
});
|
||||
});
|
||||
|
||||
// let values: Record<string, unknown> = {};
|
||||
// let component_id_map: Record<string, Component> = {};
|
||||
// let event_listener_map: Record<string, Array<number>> = {};
|
||||
// for (let component of components) {
|
||||
// component_id_map[component.id] = component;
|
||||
// if (component.props && "default" in component.props) {
|
||||
// values[component.id] = component.props.default;
|
||||
// } else {
|
||||
// values[component.id] = null;
|
||||
// }
|
||||
// event_listener_map[component.id] = [];
|
||||
// }
|
||||
// dependencies.forEach((dependency, i) => {
|
||||
// if (dependency.trigger === "click") {
|
||||
// for (let target of dependency.targets) {
|
||||
// event_listener_map[target].push(i);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// const setValues = (i: string, value: unknown) => {
|
||||
// values[i] = value;
|
||||
// };
|
||||
// const triggerTarget = (i: string) => {
|
||||
// event_listener_map[i].forEach((fn_index: number) => {
|
||||
// let dependency = dependencies[fn_index];
|
||||
// fn("predict", {
|
||||
// fn_index: fn_index,
|
||||
// data: dependency.inputs.map((i) => values[i])
|
||||
// }).then((output) => {
|
||||
// output["data"].forEach((value, i) => {
|
||||
// values[dependency.outputs[i]] = value;
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// };
|
||||
|
||||
let handled_dependencies: Array<number[]> = [];
|
||||
|
||||
async function handle_mount({ detail }) {
|
||||
console.log("mount", detail);
|
||||
// console.log("boo");
|
||||
await tick();
|
||||
dependencies.forEach(({ targets, trigger, inputs, outputs }, i) => {
|
||||
const target_instances: [number, Instance][] = targets.map((t) => [
|
||||
@ -254,7 +126,6 @@
|
||||
fn_index: i,
|
||||
data: inputs.map((id) => instance_map[id].value)
|
||||
}).then((output) => {
|
||||
console.log(output);
|
||||
output.data.forEach((value, i) => {
|
||||
instance_map[outputs[i]].value = value;
|
||||
});
|
||||
@ -268,14 +139,6 @@
|
||||
}
|
||||
|
||||
function handle_destroy(id: number) {
|
||||
console.log("destroy", id);
|
||||
// console.log(
|
||||
// id,
|
||||
// handled_dependencies,
|
||||
// handled_dependencies.map((dep) => {
|
||||
// return dep.filter((_id) => _id !== id);
|
||||
// })
|
||||
// );
|
||||
handled_dependencies = handled_dependencies.map((dep) => {
|
||||
return dep.filter((_id) => _id !== id);
|
||||
});
|
||||
@ -295,7 +158,7 @@
|
||||
{props}
|
||||
{children}
|
||||
{instance_map}
|
||||
theme={theme}
|
||||
{theme}
|
||||
on:mount={handle_mount}
|
||||
on:destroy={({ detail }) => handle_destroy(detail)}
|
||||
/>
|
||||
|
@ -7,15 +7,21 @@
|
||||
export let theme: string;
|
||||
export let name: string;
|
||||
export let source: "microphone" | "upload";
|
||||
export let type: "normal" | "numpy" = "normal";
|
||||
</script>
|
||||
|
||||
{#if mode === "static"}
|
||||
{#if mode === "dynamic"}
|
||||
<Audio
|
||||
{value}
|
||||
{theme}
|
||||
{name}
|
||||
{source}
|
||||
{type}
|
||||
on:change={({ detail }) => (value = detail)}
|
||||
on:edit
|
||||
on:play
|
||||
on:pause
|
||||
on:ended
|
||||
/>
|
||||
{:else if value}
|
||||
<audio {theme} controls>
|
||||
|
@ -4,6 +4,6 @@
|
||||
export let theme: string;
|
||||
</script>
|
||||
|
||||
<Carousel {theme}>
|
||||
<Carousel {theme} on:change>
|
||||
<slot />
|
||||
</Carousel>
|
||||
|
@ -3,4 +3,4 @@
|
||||
export let value: Array<[string, string]>;
|
||||
</script>
|
||||
|
||||
<ChatBot {value} />
|
||||
<ChatBot {value} on:change />
|
||||
|
@ -7,8 +7,8 @@
|
||||
export let mode: "static" | "dynamic";
|
||||
</script>
|
||||
|
||||
{#if mode === "static"}
|
||||
<FileUpload bind:value {theme} on:change />
|
||||
{#if mode === "dynamic"}
|
||||
<FileUpload bind:value {theme} on:change on:clear />
|
||||
{:else if value}
|
||||
<File {value} {theme} />
|
||||
{/if}
|
||||
|
@ -1,7 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { HTML } from "@gradio/html";
|
||||
export let label: string;
|
||||
export let theme: string;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
$: label, dispatch("change");
|
||||
</script>
|
||||
|
||||
<HTML value={label} {theme} />
|
||||
<HTML value={label} {theme} on:change />
|
||||
|
@ -1,10 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { HighlightedText } from "@gradio/highlighted-text";
|
||||
|
||||
export let value: Array<[string, string | number]>;
|
||||
export let theme: string;
|
||||
export let show_legend: boolean;
|
||||
export let color_map: Record<string, string> = {};
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
$: value, dispatch("change");
|
||||
</script>
|
||||
|
||||
<HighlightedText {value} {theme} {show_legend} {color_map} />
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { Image } from "@gradio/image";
|
||||
|
||||
export let value: null | string = null;
|
||||
@ -7,6 +8,10 @@
|
||||
export let tool: "editor" | "select" = "editor";
|
||||
|
||||
export let mode: "static" | "dynamic";
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
$: value, dispatch("change");
|
||||
</script>
|
||||
|
||||
{#if mode === "static"}
|
||||
@ -18,5 +23,5 @@
|
||||
<img class="w-full h-full object-contain" src={value} />
|
||||
</div>
|
||||
{:else}
|
||||
<Image bind:value {theme} {source} {tool} on:change />
|
||||
<Image bind:value {theme} {source} {tool} on:edit on:clear on:change />
|
||||
{/if}
|
||||
|
@ -1,8 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { JSON } from "@gradio/json";
|
||||
|
||||
export let value: any;
|
||||
export let theme: string;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
$: value, dispatch("change");
|
||||
</script>
|
||||
|
||||
<JSON {theme} {value} />
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { Label } from "@gradio/label";
|
||||
|
||||
export let value: {
|
||||
@ -6,6 +7,10 @@
|
||||
confidences?: Array<{ label: string; confidence: number }>;
|
||||
};
|
||||
export let theme: string;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
$: value, dispatch("change");
|
||||
</script>
|
||||
|
||||
<Label {theme} {value} />
|
||||
|
@ -5,4 +5,4 @@
|
||||
export let mode: "static" | "dynamic";
|
||||
</script>
|
||||
|
||||
<Number bind:value {theme} on:change disabled={mode === "static"} />
|
||||
<Number bind:value {theme} disabled={mode === "static"} on:change on:submit />
|
||||
|
@ -17,7 +17,7 @@
|
||||
{value}
|
||||
</div>
|
||||
{:else}
|
||||
<TextBox bind:value {theme} {lines} {placeholder} on:change />
|
||||
<TextBox bind:value {theme} {lines} {placeholder} on:change on:submit />
|
||||
{/if}
|
||||
|
||||
<style lang="postcss" global>
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { Upload } from "@gradio/upload";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { Chart } from "@gradio/chart";
|
||||
@ -10,6 +11,8 @@
|
||||
);
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
interface StaticData {
|
||||
data: Array<Array<number>>;
|
||||
headers: Array<string>;
|
||||
@ -94,6 +97,8 @@
|
||||
|
||||
$: _value = value == null ? null : _value;
|
||||
$: static_data = is_static && format_value(value as StaticData);
|
||||
|
||||
$: value, dispatch("change");
|
||||
</script>
|
||||
|
||||
{#if is_static && static_data}
|
||||
|
@ -44,5 +44,8 @@
|
||||
or_text={$_("interface.or")}
|
||||
upload_text={$_("interface.click_to_upload")}
|
||||
on:change
|
||||
on:clear
|
||||
on:play
|
||||
on:pause
|
||||
/>
|
||||
{/if}
|
||||
|
@ -1,9 +0,0 @@
|
||||
import Component from "./Textbox.svelte";
|
||||
import ExampleComponent from "./Example.svelte";
|
||||
import Interpretation from "./Interpretation.svelte";
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
example: ExampleComponent,
|
||||
interpretation: Interpretation
|
||||
};
|
@ -1,7 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let label: string;
|
||||
</script>
|
||||
|
||||
<button class="px-4 py-2 rounded bg-gray-100 hover:bg-gray-200 transition">
|
||||
{label}
|
||||
</button>
|
@ -1,5 +0,0 @@
|
||||
import Component from "./Button.svelte";
|
||||
|
||||
export default {
|
||||
component: Component
|
||||
};
|
@ -1,5 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let label: string;
|
||||
</script>
|
||||
|
||||
<div>{@html label}</div>
|
@ -1,5 +0,0 @@
|
||||
import Component from "./Markdown.svelte";
|
||||
|
||||
export default {
|
||||
component: Component
|
||||
};
|
@ -20,6 +20,9 @@
|
||||
export let or_text: string = "or";
|
||||
export let upload_text: string = "click to upload";
|
||||
|
||||
// TODO: make use of this
|
||||
export let type: "normal" | "numpy" = "normal";
|
||||
|
||||
let recording = false;
|
||||
let recorder: MediaRecorder;
|
||||
let mode = "";
|
||||
@ -31,6 +34,10 @@
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: AudioData;
|
||||
edit: AudioData;
|
||||
play: undefined;
|
||||
pause: undefined;
|
||||
ended: undefined;
|
||||
}>();
|
||||
|
||||
function blob_to_data_url(blob: Blob): Promise<string> {
|
||||
@ -123,6 +130,8 @@
|
||||
crop_min: values[0],
|
||||
crop_max: values[1]
|
||||
});
|
||||
|
||||
dispatch("edit");
|
||||
}
|
||||
|
||||
function handle_load({
|
||||
@ -176,6 +185,9 @@
|
||||
bind:this={player}
|
||||
preload="metadata"
|
||||
src={value.data}
|
||||
on:play
|
||||
on:pause
|
||||
on:ended
|
||||
/>
|
||||
|
||||
{#if mode === "edit" && player?.duration}
|
||||
|
@ -3,11 +3,14 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { setContext } from "svelte";
|
||||
import { setContext, createEventDispatcher } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export let theme: string = "default";
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: undefined;
|
||||
}>();
|
||||
const items = writable<Array<number>>([]);
|
||||
const current = writable<number>();
|
||||
|
||||
@ -33,10 +36,12 @@
|
||||
|
||||
const next = () => {
|
||||
carousel_index = (carousel_index + 1) % $items.length;
|
||||
dispatch("change");
|
||||
};
|
||||
|
||||
const prev = () => {
|
||||
carousel_index = (carousel_index - 1 + $items.length) % $items.length;
|
||||
dispatch("change");
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { beforeUpdate, afterUpdate } from "svelte";
|
||||
import { beforeUpdate, afterUpdate, createEventDispatcher } from "svelte";
|
||||
export let value: Array<[string, string]>;
|
||||
let div: HTMLDivElement;
|
||||
let autoscroll: Boolean;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
beforeUpdate(() => {
|
||||
autoscroll =
|
||||
div && div.offsetHeight + div.scrollTop > div.scrollHeight - 20;
|
||||
@ -12,6 +14,8 @@
|
||||
afterUpdate(() => {
|
||||
if (autoscroll) div.scrollTo(0, div.scrollHeight);
|
||||
});
|
||||
|
||||
$: value && dispatch("change");
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
@ -10,6 +10,7 @@
|
||||
export let drop_text: string = "Drop an audio file";
|
||||
export let or_text: string = "or";
|
||||
export let upload_text: string = "click to upload";
|
||||
let file_count: string;
|
||||
|
||||
function handle_upload({ detail }: CustomEvent<FileData>) {
|
||||
value = detail;
|
||||
@ -17,9 +18,11 @@
|
||||
|
||||
function handle_clear({ detail }: CustomEvent<null>) {
|
||||
value = null;
|
||||
dispatch("clear");
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: FileData | null }>();
|
||||
const dispatch =
|
||||
createEventDispatcher<{ change: FileData | null; clear: undefined }>();
|
||||
|
||||
$: dispatch("change", value);
|
||||
</script>
|
||||
@ -55,7 +58,7 @@
|
||||
<div class="file-size text-2xl p-2">
|
||||
{prettyBytes(value.size || 0)}
|
||||
</div>
|
||||
{#if file_count === "single" && "size" in value}
|
||||
{#if file_count === "single" && value.size}
|
||||
<div class="file-size text-2xl p-2">
|
||||
{prettyBytes(value.size)}
|
||||
</div>
|
||||
|
@ -6,13 +6,22 @@
|
||||
export let theme: string = "default";
|
||||
export let disabled: boolean = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: number }>();
|
||||
const dispatch =
|
||||
createEventDispatcher<{ change: number; submit: undefined }>();
|
||||
|
||||
function handle_change(n: number) {
|
||||
dispatch("change", n);
|
||||
}
|
||||
|
||||
function handle_keypress(e: KeyboardEvent) {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
dispatch("submit");
|
||||
}
|
||||
}
|
||||
|
||||
const debounced_handle_change = debounce(handle_change, 500);
|
||||
const debounced_handle_keypress = debounce(handle_keypress, 500);
|
||||
|
||||
$: debounced_handle_change(value);
|
||||
</script>
|
||||
@ -22,6 +31,7 @@
|
||||
class="input-number w-full rounded box-border p-2 focus:outline-none appearance-none"
|
||||
bind:value
|
||||
{theme}
|
||||
on:keypress={debounced_handle_keypress}
|
||||
/>
|
||||
|
||||
<style lang="postcss" global>
|
||||
|
@ -7,7 +7,8 @@
|
||||
export let lines: number = 1;
|
||||
export let placeholder: string = "";
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: string }>();
|
||||
const dispatch =
|
||||
createEventDispatcher<{ change: string; submit: undefined }>();
|
||||
|
||||
type CustomInputEvent =
|
||||
| (Event & {
|
||||
@ -21,7 +22,15 @@
|
||||
dispatch("change", event?.target?.value);
|
||||
}
|
||||
|
||||
function handle_keypress(e: KeyboardEvent) {
|
||||
if (e.key === "Enter" && lines === 1) {
|
||||
e.preventDefault();
|
||||
dispatch("submit");
|
||||
}
|
||||
}
|
||||
|
||||
const debounced_handle_change = debounce(handle_change, 500);
|
||||
const debounced_handle_keypress = debounce(handle_keypress, 500);
|
||||
</script>
|
||||
|
||||
{#if lines > 1}
|
||||
@ -40,6 +49,7 @@
|
||||
{placeholder}
|
||||
on:input={debounced_handle_change}
|
||||
{theme}
|
||||
on:keypress={debounced_handle_keypress}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let value: string;
|
||||
export let theme: string = "default";
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: undefined }>();
|
||||
|
||||
$: value, dispatch("change");
|
||||
</script>
|
||||
|
||||
<div class="output-html" {theme}>
|
||||
|
@ -27,14 +27,20 @@
|
||||
|
||||
function handle_clear({ detail }: CustomEvent<null>) {
|
||||
value = null;
|
||||
dispatch("clear");
|
||||
}
|
||||
|
||||
function handle_save({ detail }: { detail: string }) {
|
||||
value = detail;
|
||||
mode = "view";
|
||||
dispatch("edit");
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: string | null }>();
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: string | null;
|
||||
edit: undefined;
|
||||
clear: undefined;
|
||||
}>();
|
||||
|
||||
$: dispatch("change", value);
|
||||
</script>
|
||||
|
@ -12,7 +12,13 @@
|
||||
export let or_text: string = "or";
|
||||
export let upload_text: string = "click to upload";
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: FileData | null }>();
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: FileData | null;
|
||||
clear: undefined;
|
||||
play: undefined;
|
||||
pause: undefined;
|
||||
ended: undefined;
|
||||
}>();
|
||||
|
||||
function handle_load({ detail }: CustomEvent<FileData | null>) {
|
||||
dispatch("change", detail);
|
||||
@ -20,7 +26,7 @@
|
||||
}
|
||||
|
||||
function handle_clear({ detail }: CustomEvent<FileData | null>) {
|
||||
dispatch("change", null);
|
||||
dispatch("clear");
|
||||
value = null;
|
||||
}
|
||||
</script>
|
||||
@ -51,8 +57,11 @@
|
||||
playsInline
|
||||
preload="auto"
|
||||
src={value.data}
|
||||
on:play
|
||||
on:pause
|
||||
on:ended
|
||||
/>
|
||||
{:else}
|
||||
{:else if value.size}
|
||||
<div class="file-name text-4xl p-6 break-all">{value.name}</div>
|
||||
<div class="file-size text-2xl p-2">
|
||||
{prettyBytes(value.size)}
|
||||
|
Loading…
Reference in New Issue
Block a user