|
<script lang="ts">
|
|
import type { ThemeMode } from "@gradio/core";
|
|
import type { WorkerProxy } from "@gradio/wasm";
|
|
import { createEventDispatcher, onMount } from "svelte";
|
|
import { Block } from "@gradio/atoms";
|
|
import { BaseCode as Code } from "@gradio/code";
|
|
import lightning from "./images/lightning.svg";
|
|
|
|
export let is_embed: boolean;
|
|
export let theme_mode: ThemeMode | null = "system";
|
|
export let worker_proxy: WorkerProxy | undefined = undefined;
|
|
|
|
export let code: string | undefined;
|
|
export let layout: string | null = null;
|
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
let loading_text = "";
|
|
export let loaded = false;
|
|
worker_proxy?.addEventListener("progress-update", (event) => {
|
|
loading_text = (event as CustomEvent).detail + "...";
|
|
});
|
|
worker_proxy?.addEventListener("initialization-completed", (_) => {
|
|
loaded = true;
|
|
});
|
|
|
|
function shortcut_run(e: KeyboardEvent): void {
|
|
if (e.key == "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
dispatch("code", { code });
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
|
|
function handle_theme_mode(target: HTMLDivElement): "light" | "dark" {
|
|
const force_light = window.__gradio_mode__ === "website";
|
|
|
|
let new_theme_mode: ThemeMode;
|
|
if (force_light) {
|
|
new_theme_mode = "light";
|
|
} else {
|
|
const url = new URL(window.location.toString());
|
|
const url_color_mode: ThemeMode | null = url.searchParams.get(
|
|
"__theme"
|
|
) as ThemeMode | null;
|
|
new_theme_mode = theme_mode || url_color_mode || "system";
|
|
}
|
|
|
|
if (new_theme_mode === "dark" || new_theme_mode === "light") {
|
|
apply_theme(target, new_theme_mode);
|
|
} else {
|
|
new_theme_mode = sync_system_theme(target);
|
|
}
|
|
return new_theme_mode;
|
|
}
|
|
|
|
function sync_system_theme(target: HTMLDivElement): "light" | "dark" {
|
|
const theme = update_scheme();
|
|
window
|
|
?.matchMedia("(prefers-color-scheme: dark)")
|
|
?.addEventListener("change", update_scheme);
|
|
|
|
function update_scheme(): "light" | "dark" {
|
|
let _theme: "light" | "dark" = window?.matchMedia?.(
|
|
"(prefers-color-scheme: dark)"
|
|
).matches
|
|
? "dark"
|
|
: "light";
|
|
|
|
apply_theme(target, _theme);
|
|
return _theme;
|
|
}
|
|
return theme;
|
|
}
|
|
|
|
function apply_theme(target: HTMLDivElement, theme: "dark" | "light"): void {
|
|
const dark_class_element = is_embed ? target.parentElement! : document.body;
|
|
if (theme === "dark") {
|
|
dark_class_element.classList.add("dark");
|
|
} else {
|
|
dark_class_element.classList.remove("dark");
|
|
}
|
|
}
|
|
|
|
let active_theme_mode: Exclude<ThemeMode, "system"> = "light";
|
|
let parent_container: HTMLDivElement;
|
|
|
|
let code_editor_container: HTMLDivElement;
|
|
onMount(() => {
|
|
active_theme_mode = handle_theme_mode(parent_container);
|
|
|
|
code_editor_container.addEventListener("keydown", shortcut_run, true);
|
|
|
|
return () => {
|
|
code_editor_container.removeEventListener("keydown", shortcut_run, true);
|
|
};
|
|
});
|
|
|
|
$: loading_text;
|
|
$: loaded;
|
|
$: code;
|
|
</script>
|
|
|
|
<div class="parent-container" bind:this={parent_container}>
|
|
<div class="wrapper">
|
|
<div class="loading-panel">
|
|
<div class="code-header">app.py</div>
|
|
{#if !loaded}
|
|
<div style="display: flex;"></div>
|
|
<div class="loading-section">
|
|
<div class="loading-dot"></div>
|
|
{loading_text}
|
|
</div>
|
|
{:else}
|
|
<div class="buttons">
|
|
<div class="run">
|
|
<button
|
|
class="button"
|
|
on:click={() => {
|
|
dispatch("code", { code });
|
|
}}
|
|
>
|
|
Run
|
|
<div class="shortcut">⌘+↵</div>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div style="flex-grow: 1"></div>
|
|
<div class="loading-section">
|
|
<img src={lightning} alt="lightning icon" class="lightning-logo" />
|
|
Interactive
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
<div
|
|
class:horizontal={layout === "horizontal"}
|
|
class:vertical={layout === "vertical"}
|
|
class="child-container"
|
|
>
|
|
<div
|
|
class:code-editor-border={loaded}
|
|
class="code-editor"
|
|
bind:this={code_editor_container}
|
|
>
|
|
<Block variant={"solid"} padding={false}>
|
|
<Code
|
|
bind:value={code}
|
|
language="python"
|
|
lines={10}
|
|
readonly={!loaded}
|
|
dark_mode={active_theme_mode === "dark"}
|
|
/>
|
|
</Block>
|
|
</div>
|
|
{#if loaded}
|
|
<div class="preview">
|
|
<slot></slot>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.wrapper {
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow-y: scroll;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.parent-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
border: 1px solid rgb(229 231 235);
|
|
border-radius: 0.375rem;
|
|
}
|
|
:global(.dark .parent-container) {
|
|
border-color: #374151 !important;
|
|
color-scheme: dark !important;
|
|
}
|
|
|
|
.child-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.horizontal {
|
|
flex-direction: row !important;
|
|
}
|
|
|
|
.vertical {
|
|
flex-direction: column !important;
|
|
}
|
|
|
|
.vertical .code-editor-border {
|
|
border-right: none !important;
|
|
}
|
|
|
|
.horizontal .code-editor-border {
|
|
border-right: 1px solid rgb(229 231 235);
|
|
border-bottom: none;
|
|
}
|
|
:global(.dark .horizontal .code-editor-border) {
|
|
border-right: 1px solid #374151 !important;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.child-container {
|
|
flex-direction: row;
|
|
}
|
|
.code-editor-border {
|
|
border-right: 1px solid rgb(229 231 235);
|
|
}
|
|
:global(.dark .code-editor-border) {
|
|
border-right: 1px solid #374151 !important;
|
|
}
|
|
}
|
|
|
|
.code-editor {
|
|
flex: 1 1 50%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
border-bottom: 1px solid;
|
|
border-color: rgb(229 231 235);
|
|
}
|
|
:global(.dark .code-editor) {
|
|
border-color: #374151 !important;
|
|
}
|
|
|
|
.loading-panel {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
vertical-align: middle;
|
|
height: 2rem;
|
|
padding-left: 0.5rem;
|
|
padding-right: 0.5rem;
|
|
border-bottom: 1px solid rgb(229 231 235);
|
|
}
|
|
|
|
:global(.dark .loading-panel) {
|
|
background: #1f2937 !important;
|
|
border-color: #374151 !important;
|
|
}
|
|
|
|
.code-header {
|
|
align-self: center;
|
|
font-family: monospace;
|
|
font-size: 14px;
|
|
font-weight: lighter;
|
|
margin-right: 4px;
|
|
color: #535d6d;
|
|
}
|
|
:global(.dark .code-header) {
|
|
color: white !important;
|
|
}
|
|
|
|
.loading-section {
|
|
align-items: center;
|
|
display: flex;
|
|
margin-left: 0.5rem;
|
|
margin-right: 0.5rem;
|
|
color: #999b9e;
|
|
font-family: sans-serif;
|
|
font-size: 15px;
|
|
align-self: center;
|
|
}
|
|
:global(.dark .loading-section) {
|
|
color: white !important;
|
|
}
|
|
|
|
.lightning-logo {
|
|
width: 1rem;
|
|
height: 1rem;
|
|
margin: 0.125rem;
|
|
}
|
|
|
|
.preview {
|
|
flex: 1 1 50%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.buttons {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: middle;
|
|
height: 2rem;
|
|
}
|
|
|
|
.run {
|
|
display: flex;
|
|
align-items: center;
|
|
color: #999b9e;
|
|
font-size: 15px;
|
|
}
|
|
|
|
.button {
|
|
display: flex;
|
|
height: 80%;
|
|
align-items: center;
|
|
font-weight: 600;
|
|
padding-left: 0.8rem;
|
|
padding-right: 0.8rem;
|
|
border-radius: 0.375rem;
|
|
float: right;
|
|
margin: 0.25rem;
|
|
border: 1px solid #e5e7eb;
|
|
background: linear-gradient(to bottom right, #f3f4f6, #e5e7eb);
|
|
color: #374151;
|
|
cursor: pointer;
|
|
font-family: sans-serif;
|
|
}
|
|
:global(.dark .button) {
|
|
border-color: #374151 !important;
|
|
background: linear-gradient(to bottom right, #4b5563, #374151) !important;
|
|
color: white !important;
|
|
}
|
|
.shortcut {
|
|
align-self: center;
|
|
margin-top: 2px;
|
|
font-size: 10px;
|
|
font-weight: lighter;
|
|
padding-left: 0.15rem;
|
|
color: #374151;
|
|
}
|
|
:global(.dark .shortcut) {
|
|
color: white !important;
|
|
}
|
|
|
|
:global(div.code-editor div.block) {
|
|
border-radius: 0;
|
|
border: none;
|
|
}
|
|
|
|
:global(div.code-editor div.block .cm-gutters) {
|
|
background-color: white;
|
|
}
|
|
:global(.dark div.code-editor div.block .cm-gutters) {
|
|
background: #1f2937 !important;
|
|
}
|
|
|
|
:global(div.code-editor div.block .cm-content) {
|
|
width: 0;
|
|
}
|
|
|
|
:global(div.lite-demo div.gradio-container) {
|
|
height: 100%;
|
|
overflow-y: scroll;
|
|
margin: 0 !important;
|
|
}
|
|
|
|
:global(.gradio-container) {
|
|
max-width: none !important;
|
|
}
|
|
|
|
.code-editor :global(label) {
|
|
display: none;
|
|
}
|
|
|
|
.code-editor :global(.codemirror-wrappper) {
|
|
border-radius: var(--block-radius);
|
|
}
|
|
|
|
.code-editor :global(> .block) {
|
|
border: none !important;
|
|
}
|
|
|
|
.code-editor :global(.cm-scroller) {
|
|
height: 100% !important;
|
|
}
|
|
:global(.code-editor .block) {
|
|
border-style: none !important;
|
|
height: 100%;
|
|
}
|
|
:global(.code-editor .container) {
|
|
padding: 2px;
|
|
padding-right: 0;
|
|
height: 100%;
|
|
}
|
|
|
|
:global(.code-editor .container a) {
|
|
display: block;
|
|
width: 65%;
|
|
color: #9095a0;
|
|
margin: auto;
|
|
}
|
|
|
|
:global(.code-editor .block button) {
|
|
background-color: transparent;
|
|
border: none;
|
|
color: #9095a0;
|
|
height: 100%;
|
|
padding: 5px;
|
|
padding-left: 0;
|
|
}
|
|
|
|
:global(.code-editor .block .check) {
|
|
width: 65%;
|
|
color: #ff7c00;
|
|
margin: auto;
|
|
}
|
|
:global(.gradio-container) {
|
|
overflow-y: hidden;
|
|
}
|
|
|
|
.loading-dot {
|
|
position: relative;
|
|
left: -9999px;
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 5px;
|
|
background-color: #fd7b00;
|
|
color: #fd7b00;
|
|
box-shadow: 9999px 0 0 -1px;
|
|
animation: loading-dot 2s infinite linear;
|
|
animation-delay: 0.25s;
|
|
margin-left: 0.5rem;
|
|
margin-right: 0.5rem;
|
|
}
|
|
@keyframes loading-dot {
|
|
0% {
|
|
box-shadow: 9999px 0 0 -1px;
|
|
}
|
|
50% {
|
|
box-shadow: 9999px 0 0 2px;
|
|
}
|
|
100% {
|
|
box-shadow: 9999px 0 0 -1px;
|
|
}
|
|
}
|
|
</style>
|
|
|