Files
windmill/frontend/src/lib/components/raw_apps/RawAppInlineScriptEditor.svelte
T
Ruben Fiszel 355a92a3df appSvelte5
2025-06-13 17:34:23 +02:00

220 lines
6.0 KiB
Svelte

<script lang="ts">
import Button from '$lib/components/common/button/Button.svelte'
import type { Preview } from '$lib/gen'
import { createEventDispatcher, onMount } from 'svelte'
import { Maximize2, Trash2 } from 'lucide-svelte'
import { inferArgs } from '$lib/infer'
import type { Schema } from '$lib/common'
import Editor from '$lib/components/Editor.svelte'
import { emptySchema } from '$lib/utils'
import { scriptLangToEditorLang } from '$lib/scripts'
import ScriptGen from '$lib/components/copilot/ScriptGen.svelte'
import DiffEditor from '$lib/components/DiffEditor.svelte'
import EditorSettings from '$lib/components/EditorSettings.svelte'
import InlineScriptEditorDrawer from '../apps/editor/inlineScriptsPanel/InlineScriptEditorDrawer.svelte'
import type { InlineScript } from '../apps/types'
import type { AppInput } from '../apps/inputType'
import CacheTtlPopup from '../apps/editor/inlineScriptsPanel/CacheTtlPopup.svelte'
import RunButton from '$lib/components/RunButton.svelte'
import { computeFields } from '../apps/editor/inlineScriptsPanel/utils'
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
export let inlineScript: InlineScript | undefined
export let name: string | undefined = undefined
export let id: string
export let fields: Record<string, AppInput> = {}
export let path: string
export let isLoading: boolean = false
export let onRun: () => Promise<void>
export let onCancel: () => Promise<void>
export let editor: Editor | undefined = undefined
let diffEditor: DiffEditor
let validCode = true
async function inferInlineScriptSchema(
language: Preview['language'],
content: string,
schema: Schema
): Promise<Schema> {
try {
await inferArgs(language, content, schema)
validCode = true
} catch (e) {
console.error("Couldn't infer args", e)
validCode = false
}
return schema
}
onMount(async () => {
if (inlineScript && !inlineScript.schema) {
if (inlineScript.language != 'frontend') {
inlineScript.schema = await inferInlineScriptSchema(
inlineScript?.language,
inlineScript?.content,
emptySchema()
)
}
}
syncFields()
})
async function syncFields() {
if (inlineScript) {
const newSchema = inlineScript.schema ?? emptySchema()
fields = computeFields(newSchema, true, fields)
}
}
const dispatch = createEventDispatcher()
let drawerIsOpen: boolean | undefined = undefined
</script>
{#if inlineScript}
{#if inlineScript.language != 'frontend'}
<InlineScriptEditorDrawer
{id}
appPath={path}
bind:isOpen={drawerIsOpen}
{editor}
bind:this={inlineScriptEditorDrawer}
bind:inlineScript
on:createScriptFromInlineScript={() => {
dispatch('createScriptFromInlineScript')
drawerIsOpen = false
}}
/>
{/if}
<div class="h-full flex flex-col gap-1">
<div class="flex justify-between w-full gap-2 px-2 pt-1 flex-row items-center">
{#if name !== undefined}
<div class="flex flex-row gap-2 w-full items-center">
<input
on:keydown|stopPropagation
bind:value={name}
placeholder="Inline script name"
class="!text-xs !rounded-sm !shadow-none"
on:keyup={() => {
// // $app = $app
// if (stateId) {
// $stateId++
// }
}}
/>
<div
title={validCode ? 'Main function parsable' : 'Main function not parsable'}
class="rounded-full !w-2 !h-2 {validCode ? 'bg-green-300' : 'bg-red-300'}"
></div>
</div>
{/if}
<div class="flex w-full flex-row gap-1 items-center justify-end">
{#if inlineScript}
<CacheTtlPopup bind:cache_ttl={inlineScript.cache_ttl} />
{/if}
<ScriptGen
lang={inlineScript?.language}
{editor}
{diffEditor}
inlineScript
args={Object.entries(fields ?? {}).reduce((acc, [key, obj]) => {
acc[key] = obj.type === 'static' ? obj.value : undefined
return acc
}, {})}
/>
<EditorSettings />
<Button
title="Delete"
size="xs2"
color="light"
variant="contained"
aria-label="Delete"
on:click={() => dispatch('delete')}
endIcon={{ icon: Trash2 }}
iconOnly
/>
{#if inlineScript.language != 'frontend'}
<Button
size="xs2"
color="light"
title="Full Editor"
variant="contained"
on:click={() => {
inlineScriptEditorDrawer?.openDrawer()
}}
endIcon={{ icon: Maximize2 }}
iconOnly
/>
{/if}
<Button
variant="border"
size="xs2"
color="light"
on:click={async () => {
editor?.format()
}}
shortCut={{
key: 'S'
}}
>
Format
</Button>
<RunButton {isLoading} {onRun} {onCancel} />
</div>
</div>
<!-- {inlineScript.content} -->
<div class="border-y h-full w-full">
{#if !drawerIsOpen && inlineScript.language != 'frontend'}
<Editor
path={path + '/' + id}
bind:this={editor}
small
class="flex flex-1 grow h-full"
scriptLang={inlineScript.language}
bind:code={inlineScript.content}
fixedOverflowWidgets={true}
cmdEnterAction={() => onRun()}
on:change={async (e) => {
if (inlineScript && inlineScript.language != 'frontend') {
if (inlineScript.lock != undefined) {
inlineScript.lock = undefined
}
const oldSchema = JSON.stringify(inlineScript.schema)
if (inlineScript.schema == undefined) {
inlineScript.schema = emptySchema()
}
await inferInlineScriptSchema(inlineScript?.language, e.detail, inlineScript.schema)
if (JSON.stringify(inlineScript.schema) != oldSchema) {
inlineScript = inlineScript
syncFields()
}
}
// // $app = $app
}}
args={Object.entries(fields ?? {}).reduce((acc, [key, obj]) => {
acc[key] = obj.type === 'static' ? obj.value : undefined
return acc
}, {})}
/>
<DiffEditor
open={false}
bind:this={diffEditor}
class="h-full"
automaticLayout
fixedOverflowWidgets
defaultLang={scriptLangToEditorLang(inlineScript?.language)}
/>
{/if}
</div>
</div>
{/if}