Files
windmill/frontend/src/lib/components/InputTransformForm.svelte
T
2023-11-18 12:36:08 +01:00

340 lines
9.6 KiB
Svelte

<script lang="ts">
import type { Schema } from '$lib/common'
import type { InputCat } from '$lib/utils'
import { getContext } from 'svelte'
import ArgInput from './ArgInput.svelte'
import FieldHeader from './FieldHeader.svelte'
import DynamicInputHelpBox from './flows/content/DynamicInputHelpBox.svelte'
import type { PropPickerWrapperContext } from './flows/propPicker/PropPickerWrapper.svelte'
import { codeToStaticTemplate, getDefaultExpr } from './flows/utils'
import SimpleEditor from './SimpleEditor.svelte'
import { Button } from './common'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import type VariableEditor from './VariableEditor.svelte'
import type ItemPicker from './ItemPicker.svelte'
import type { InputTransform } from '$lib/gen'
import TemplateEditor from './TemplateEditor.svelte'
import { setInputCat as computeInputCat, isCodeInjection } from '$lib/utils'
import { FunctionSquare, Plug } from 'lucide-svelte'
import { getResourceTypes } from './resourceTypesStore'
export let schema: Schema
export let arg: InputTransform | any
export let argName: string
export let extraLib: string = 'missing extraLib'
export let inputCheck: boolean = true
export let previousModuleId: string | undefined
export let pickForField: string | undefined = undefined
export let variableEditor: VariableEditor | undefined = undefined
export let itemPicker: ItemPicker | undefined = undefined
export let noDynamicToggle = false
export let argExtra: Record<string, any> = {}
let monaco: SimpleEditor | undefined = undefined
let monacoTemplate: TemplateEditor | undefined = undefined
let argInput: ArgInput | undefined = undefined
$: inputCat = computeInputCat(
schema.properties[argName].type,
schema.properties[argName].format,
schema.properties[argName].items?.type,
schema.properties[argName].enum,
schema.properties[argName].contentEncoding
)
let propertyType = getPropertyType(arg)
function updatePropertyType() {
propertyType = arg.type
}
$: arg?.type && arg.type !== propertyType && updatePropertyType()
function getPropertyType(arg: InputTransform | any): 'static' | 'javascript' {
let type: 'static' | 'javascript' = arg?.type ?? 'static'
if (
type == 'javascript' &&
isStaticTemplate(inputCat) &&
(arg?.expr?.length === 0 || arg?.expr?.[0] === '`')
) {
const newValue = codeToStaticTemplate(arg.expr)
if (newValue) {
type = 'static'
arg.value = newValue
}
}
return type
}
function setPropertyType(rawValue: string) {
if (!arg) {
return
}
if (isCodeInjection(rawValue)) {
arg.expr = getDefaultExpr(
argName,
previousModuleId,
`\`${rawValue.toString().replaceAll('`', '\\`')}\``
)
arg.type = 'javascript'
propertyType = 'static'
} else {
if (arg.type === 'javascript' && propertyType === 'static') {
arg.type = 'static'
}
if (arg.type) {
propertyType = arg.type
}
}
}
function isStaticTemplate(inputCat: InputCat) {
return inputCat === 'string' || inputCat === 'sql' || inputCat == 'yaml'
}
function connectProperty(rawValue: string) {
arg.expr = getDefaultExpr(undefined, previousModuleId, rawValue)
arg.type = 'javascript'
propertyType = 'javascript'
monaco?.setCode(arg.expr)
}
function onFocus() {
if (isStaticTemplate(inputCat)) {
focusProp(argName, 'append', (path) => {
const toAppend = `\$\{${path}}`
arg.value = `${arg.value ?? ''}${toAppend}`
monacoTemplate?.setCode(arg.value)
setPropertyType(arg.value)
argInput?.focus()
return false
})
} else {
focusProp(argName, 'insert', (path) => {
arg.expr = path
arg.type = 'javascript'
propertyType = 'javascript'
monaco?.setCode(arg.expr)
return true
})
}
}
const { focusProp, propPickerConfig } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
$: isStaticTemplate(inputCat) && propertyType == 'static' && setPropertyType(arg?.value)
function setDefaultCode() {
if (!arg?.value) {
monacoTemplate?.setCode(schema.properties[argName].default)
}
}
$: schema.properties[argName].default && setDefaultCode()
let resourceTypes: string[] | undefined = undefined
async function loadResourceTypes() {
resourceTypes = await getResourceTypes()
}
loadResourceTypes()
</script>
{#if arg != undefined}
<div class={$$props.class}>
<div class="flex flex-row justify-between gap-1 pb-1">
<div class="flex flex-wrap grow">
<FieldHeader
label={argName}
format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
required={schema.required.includes(argName)}
type={schema.properties[argName].type}
/>
{#if isStaticTemplate(inputCat)}
<div>
<span
class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 !py-0.5 rounded ml-2 {propertyType ==
'static' && arg.type === 'javascript'
? 'visible'
: 'invisible'}"
>
{'${...}'}
</span>
</div>
{/if}
</div>
{#if !noDynamicToggle}
<div class="flex flex-row gap-x-6 gap-y-1 flex-wrap z-10">
<div>
<ToggleButtonGroup
bind:selected={propertyType}
on:selected={(e) => {
const staticTemplate = isStaticTemplate(inputCat)
if (e.detail === 'javascript') {
if (arg.expr == undefined) {
arg.expr = getDefaultExpr(
argName,
previousModuleId,
staticTemplate
? `\`${arg?.value?.toString().replaceAll('`', '\\`') ?? ''}\``
: arg.value
? JSON.stringify(arg?.value, null, 4)
: ''
)
}
if (arg) {
arg.value = undefined
arg.type = 'javascript'
}
propertyType = 'javascript'
} else {
if (staticTemplate) {
if (arg) {
arg.value = codeToStaticTemplate(arg.expr)
arg.expr = undefined
}
setPropertyType(arg?.value)
} else {
if (arg) {
arg.type = 'static'
arg.value = undefined
arg.expr = undefined
}
}
propertyType = 'static'
}
}}
>
{#if isStaticTemplate(inputCat)}
<ToggleButton
tooltip={`Write text or surround javascript with \`\$\{\` and \`\}\`. Use \`results\` to connect to another node\'s output.`}
light
value="static"
size="xs2"
label={'${}'}
/>
{:else}
<ToggleButton small label="Static" value="static" />
{/if}
<ToggleButton
small
light
tooltip="Javascript expression ('flow_input' or 'results')."
value="javascript"
icon={FunctionSquare}
/>
</ToggleButtonGroup>
</div>
<div>
<Button
title="Connect to another node's output"
variant="border"
color="light"
size="xs2"
on:click={() => {
focusProp(argName, 'connect', (path) => {
connectProperty(path)
return true
})
}}
id="flow-editor-plug"
>
<Plug size={16} /> &rightarrow;
</Button>
</div>
</div>
{/if}
</div>
<div class="max-w-xs" />
<div
class="relative {$propPickerConfig?.propName == argName
? 'outline outline-offset-0 outline-2 outline-blue-500 rounded-md'
: ''}"
>
{#if $propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'}
<span
class={'text-white z-50 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute top-0 right-0 bg-blue-500'}
>
Connect input &rightarrow;
</span>
{/if}
<!-- {inputCat}
{propertyType} -->
{#if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle}
<div class="mt-2 min-h-[28px]">
{#if arg}
<TemplateEditor
bind:this={monacoTemplate}
{extraLib}
on:focus={onFocus}
bind:code={arg.value}
fontSize={14}
/>
{/if}
</div>
{:else if propertyType === undefined || propertyType == 'static'}
<ArgInput
{resourceTypes}
noMargin
compact
bind:this={argInput}
on:focus={onFocus}
label={argName}
bind:editor={monaco}
bind:description={schema.properties[argName].description}
bind:value={arg.value}
type={schema.properties[argName].type}
required={schema.required.includes(argName)}
bind:pattern={schema.properties[argName].pattern}
bind:valid={inputCheck}
defaultValue={schema.properties[argName].default}
bind:enum_={schema.properties[argName].enum}
bind:format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
bind:itemsType={schema.properties[argName].items}
properties={schema.properties[argName].properties}
nestedRequired={schema.properties[argName].required}
displayHeader={false}
extra={argExtra}
{variableEditor}
{itemPicker}
bind:pickForField
showSchemaExplorer
/>
{:else if arg.expr != undefined}
<div class="border rounded mt-2 border-gray-300">
<SimpleEditor
bind:this={monaco}
bind:code={arg.expr}
{extraLib}
lang="javascript"
shouldBindKey={false}
on:focus={() => {
focusProp(argName, 'insert', (path) => {
monaco?.insertAtCursor(path)
return false
})
}}
autoHeight
/>
</div>
<DynamicInputHelpBox />
<div class="mb-2" />
{:else}
Not recognized input type {argName}
{/if}
</div>
</div>
{/if}