|
<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> |
|
|