Compare commits
32 Commits
wmill-scri
...
glm/improv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d60cf36f7 | ||
|
|
a196851998 | ||
|
|
08f82632c8 | ||
|
|
ff3e734a0d | ||
|
|
5617a1b19b | ||
|
|
72102b7b1c | ||
|
|
a56e37564b | ||
|
|
96af7c4349 | ||
|
|
a6592cf34e | ||
|
|
7b7e74b2ae | ||
|
|
f5a4d25fc4 | ||
|
|
91416fe96d | ||
|
|
8feb0bbc93 | ||
|
|
c48946b2b3 | ||
|
|
d0218631ae | ||
|
|
a6a91a413e | ||
|
|
2f137094d8 | ||
|
|
cc65c839f2 | ||
|
|
05a9cdd588 | ||
|
|
f3738a3850 | ||
|
|
60878f24d8 | ||
|
|
c3f1569238 | ||
|
|
f3d8348bb1 | ||
|
|
7f50d92d34 | ||
|
|
7779079fca | ||
|
|
9984efafcc | ||
|
|
e4fc89cb99 | ||
|
|
aa48107e27 | ||
|
|
05b55f53c9 | ||
|
|
50cb4df130 | ||
|
|
a89dc8f845 | ||
|
|
63abd70f2e |
@@ -6,13 +6,15 @@
|
|||||||
import ArgInput from './ArgInput.svelte'
|
import ArgInput from './ArgInput.svelte'
|
||||||
import FieldHeader from './FieldHeader.svelte'
|
import FieldHeader from './FieldHeader.svelte'
|
||||||
import DynamicInputHelpBox from './flows/content/DynamicInputHelpBox.svelte'
|
import DynamicInputHelpBox from './flows/content/DynamicInputHelpBox.svelte'
|
||||||
import type { PropPickerWrapperContext } from './flows/propPicker/PropPickerWrapper.svelte'
|
import type { PropPickerWrapperContext } from './prop_picker'
|
||||||
import { codeToStaticTemplate, getDefaultExpr } from './flows/utils'
|
import { codeToStaticTemplate, getDefaultExpr } from './flows/utils'
|
||||||
import SimpleEditor from './SimpleEditor.svelte'
|
import SimpleEditor from './SimpleEditor.svelte'
|
||||||
import { Button } from './common'
|
import { Button } from '$lib/components/common'
|
||||||
|
import AnimatedButton from '$lib/components/common/button/AnimatedButton.svelte'
|
||||||
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||||
|
import { fade } from 'svelte/transition'
|
||||||
|
import { tick } from 'svelte'
|
||||||
import type VariableEditor from './VariableEditor.svelte'
|
import type VariableEditor from './VariableEditor.svelte'
|
||||||
import type ItemPicker from './ItemPicker.svelte'
|
import type ItemPicker from './ItemPicker.svelte'
|
||||||
import type { InputTransform } from '$lib/gen'
|
import type { InputTransform } from '$lib/gen'
|
||||||
@@ -23,7 +25,8 @@
|
|||||||
import type { FlowCopilotContext } from './copilot/flow'
|
import type { FlowCopilotContext } from './copilot/flow'
|
||||||
import StepInputGen from './copilot/StepInputGen.svelte'
|
import StepInputGen from './copilot/StepInputGen.svelte'
|
||||||
import type { PickableProperties } from './flows/previousResults'
|
import type { PickableProperties } from './flows/previousResults'
|
||||||
|
import { buildPrefixRegex } from './flows/previousResults'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
export let schema: Schema | { properties?: Record<string, any>; required?: string[] }
|
export let schema: Schema | { properties?: Record<string, any>; required?: string[] }
|
||||||
export let arg: InputTransform | any
|
export let arg: InputTransform | any
|
||||||
export let argName: string
|
export let argName: string
|
||||||
@@ -57,6 +60,8 @@
|
|||||||
const { shouldUpdatePropertyType, exprsToSet } =
|
const { shouldUpdatePropertyType, exprsToSet } =
|
||||||
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
|
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
|
||||||
|
|
||||||
|
const { inputMatches } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||||
|
|
||||||
function setExpr() {
|
function setExpr() {
|
||||||
const newArg = $exprsToSet?.[argName]
|
const newArg = $exprsToSet?.[argName]
|
||||||
if (newArg) {
|
if (newArg) {
|
||||||
@@ -131,6 +136,54 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let codeInjectionDetected = false
|
||||||
|
|
||||||
|
const dynamicTemplateRegexPairs = buildPrefixRegex([
|
||||||
|
'flow_input',
|
||||||
|
'results',
|
||||||
|
'resource',
|
||||||
|
'variable'
|
||||||
|
])
|
||||||
|
|
||||||
|
function checkCodeInjection(rawValue: string) {
|
||||||
|
if (!arg || !rawValue || rawValue.length < 3 || !dynamicTemplateRegexPairs) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const matches = dynamicTemplateRegexPairs.filter(({ regex }) => regex.test(rawValue))
|
||||||
|
if (matches.length > 0) {
|
||||||
|
return matches.map((m) => ({ word: m.word, value: rawValue }))
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setJavaScriptExpr(rawValue: string) {
|
||||||
|
arg = {
|
||||||
|
type: 'javascript',
|
||||||
|
expr: rawValue
|
||||||
|
}
|
||||||
|
propertyType = 'javascript'
|
||||||
|
monaco?.setCode('')
|
||||||
|
monaco?.insertAtCursor(rawValue)
|
||||||
|
await tick()
|
||||||
|
monaco?.focus()
|
||||||
|
await tick()
|
||||||
|
monaco?.setCursorToEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyUp(e: KeyboardEvent) {
|
||||||
|
if (
|
||||||
|
e.key === 'Tab' &&
|
||||||
|
isStaticTemplate(inputCat) &&
|
||||||
|
propertyType == 'static' &&
|
||||||
|
!noDynamicToggle &&
|
||||||
|
codeInjectionDetected
|
||||||
|
) {
|
||||||
|
setJavaScriptExpr(arg.value)
|
||||||
|
} else {
|
||||||
|
stepInputGen?.onKeyUp?.(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isStaticTemplate(inputCat: InputCat) {
|
function isStaticTemplate(inputCat: InputCat) {
|
||||||
return inputCat === 'string' || inputCat === 'sql' || inputCat == 'yaml'
|
return inputCat === 'string' || inputCat === 'sql' || inputCat == 'yaml'
|
||||||
}
|
}
|
||||||
@@ -166,7 +219,24 @@
|
|||||||
|
|
||||||
const { focusProp, propPickerConfig } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
const { focusProp, propPickerConfig } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||||
|
|
||||||
$: isStaticTemplate(inputCat) && propertyType == 'static' && setPropertyType(arg?.value)
|
$: updateStaticInput(inputCat, propertyType, arg)
|
||||||
|
|
||||||
|
function updateStaticInput(
|
||||||
|
inputCat: InputCat,
|
||||||
|
propertyType: 'static' | 'javascript',
|
||||||
|
arg: InputTransform | any
|
||||||
|
) {
|
||||||
|
if (!isStaticTemplate(inputCat)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (propertyType == 'static') {
|
||||||
|
setPropertyType(arg?.value)
|
||||||
|
codeInjectionDetected = !!checkCodeInjection(arg?.value)
|
||||||
|
} else if (propertyType == 'javascript' && focused) {
|
||||||
|
setPropertyType(arg?.expr)
|
||||||
|
$inputMatches = checkCodeInjection(arg?.expr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setDefaultCode() {
|
function setDefaultCode() {
|
||||||
if (!arg?.value) {
|
if (!arg?.value) {
|
||||||
@@ -186,11 +256,22 @@
|
|||||||
let stepInputGen: StepInputGen | undefined = undefined
|
let stepInputGen: StepInputGen | undefined = undefined
|
||||||
|
|
||||||
loadResourceTypes()
|
loadResourceTypes()
|
||||||
|
|
||||||
|
$: connecting =
|
||||||
|
$propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if arg != undefined}
|
{#if arg != undefined}
|
||||||
<div class={$$props.class}>
|
<div
|
||||||
<div class="flex flex-row justify-between gap-1 pb-1">
|
class={twMerge(
|
||||||
|
'pl-2 pt-2 pb-2 ml-2 relative hover:bg-surface hover:shadow-md transition-all duration-200',
|
||||||
|
$propPickerConfig?.propName == argName
|
||||||
|
? 'bg-surface border-l-4 border-blue-500 shadow-md rounded-l-md z-2000'
|
||||||
|
: 'hover:rounded-md',
|
||||||
|
$$props.class
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="flex flex-row justify-between gap-1 pb-1 px-2">
|
||||||
<div class="flex flex-wrap grow">
|
<div class="flex flex-wrap grow">
|
||||||
<FieldHeader
|
<FieldHeader
|
||||||
label={argName}
|
label={argName}
|
||||||
@@ -322,147 +403,181 @@
|
|||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<AnimatedButton animate={connecting} baseRadius="6px" animationDuration="1s">
|
||||||
title="Connect to another node's output"
|
<Button
|
||||||
variant="border"
|
variant="border"
|
||||||
color="light"
|
color="light"
|
||||||
size="xs2"
|
size="xs2"
|
||||||
on:click={() => {
|
btnClasses={connecting ? 'text-blue-500' : 'text-primary'}
|
||||||
focusProp(argName, 'connect', (path) => {
|
on:click={() => {
|
||||||
connectProperty(path)
|
focusProp(argName, 'connect', (path) => {
|
||||||
dispatch('change', { argName })
|
connectProperty(path)
|
||||||
return true
|
dispatch('change', { argName })
|
||||||
})
|
return true
|
||||||
}}
|
})
|
||||||
id="flow-editor-plug"
|
}}
|
||||||
>
|
>
|
||||||
<Plug size={16} /> →
|
<Plug size={16} /> →
|
||||||
</Button>
|
</Button>
|
||||||
|
</AnimatedButton>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="max-w-xs" />
|
<div class="max-w-xs" />
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div
|
<div class="relative" on:keyup={handleKeyUp}>
|
||||||
class="relative {$propPickerConfig?.propName == argName
|
<!-- {#if $propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'}
|
||||||
? 'outline outline-offset-1 outline-1 outline-blue-500 rounded-md'
|
|
||||||
: ''}"
|
|
||||||
on:keyup={stepInputGen?.onKeyUp}
|
|
||||||
>
|
|
||||||
{#if $propPickerConfig?.propName == argName && $propPickerConfig?.insertionMode == 'connect'}
|
|
||||||
<span
|
<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'}
|
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 →
|
Connect input →
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if} -->
|
||||||
<!-- {inputCat}
|
<!-- {inputCat}
|
||||||
{propertyType} -->
|
{propertyType} -->
|
||||||
{#if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle}
|
<div class="relative flex flex-row items-top gap-2 justify-between">
|
||||||
{#if argName && schema?.properties?.[argName]?.description}
|
<div class="min-w-0 grow">
|
||||||
<div class="text-xs italic pb-1 text-secondary">
|
{#if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle}
|
||||||
<pre class="font-main">{schema.properties[argName].description}</pre>
|
{#if argName && schema?.properties?.[argName]?.description}
|
||||||
</div>
|
<div class="text-xs italic pb-1 text-secondary">
|
||||||
{/if}
|
<pre class="font-main">{schema.properties[argName].description}</pre>
|
||||||
<div class="mt-2 min-h-[28px]">
|
</div>
|
||||||
{#if arg}
|
{/if}
|
||||||
<TemplateEditor
|
<div class="mt-2 min-h-[28px]">
|
||||||
bind:this={monacoTemplate}
|
{#if arg}
|
||||||
{extraLib}
|
<TemplateEditor
|
||||||
|
bind:this={monacoTemplate}
|
||||||
|
{extraLib}
|
||||||
|
on:focus={onFocus}
|
||||||
|
on:blur={() => {
|
||||||
|
focused = false
|
||||||
|
}}
|
||||||
|
bind:code={arg.value}
|
||||||
|
fontSize={14}
|
||||||
|
on:change={() => {
|
||||||
|
dispatch('change', { argName })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if codeInjectionDetected}
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
color="light"
|
||||||
|
btnClasses="font-normal text-xs w-fit bg-green-100 text-green-800 hover:bg-green-100 dark:text-green-400 dark:bg-green-700 dark:hover:bg-green-700"
|
||||||
|
on:click={() => setJavaScriptExpr(arg.value)}
|
||||||
|
>
|
||||||
|
<span class="font-normal"
|
||||||
|
>JavaScript expression detected - press
|
||||||
|
<span class="font-bold">TAB</span> to exit static mode
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
{:else if (propertyType === undefined || propertyType == 'static') && schema?.properties?.[argName]}
|
||||||
|
<ArgInput
|
||||||
|
{resourceTypes}
|
||||||
|
noMargin
|
||||||
|
compact
|
||||||
|
bind:this={argInput}
|
||||||
on:focus={onFocus}
|
on:focus={onFocus}
|
||||||
on:blur={() => {
|
on:blur={() => {
|
||||||
focused = false
|
focused = false
|
||||||
}}
|
}}
|
||||||
bind:code={arg.value}
|
shouldDispatchChanges
|
||||||
fontSize={14}
|
|
||||||
on:change={() => {
|
on:change={() => {
|
||||||
dispatch('change', { argName })
|
dispatch('change', { argName })
|
||||||
}}
|
}}
|
||||||
|
label={argName}
|
||||||
|
bind:editor={monaco}
|
||||||
|
bind:description={schema.properties[argName].description}
|
||||||
|
bind:value={arg.value}
|
||||||
|
type={schema.properties[argName].type}
|
||||||
|
oneOf={schema.properties[argName].oneOf}
|
||||||
|
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
|
||||||
|
nullable={schema.properties[argName].nullable}
|
||||||
|
bind:title={schema.properties[argName].title}
|
||||||
|
bind:placeholder={schema.properties[argName].placeholder}
|
||||||
/>
|
/>
|
||||||
|
{:else if arg.expr != undefined}
|
||||||
|
<div class="border mt-2">
|
||||||
|
<SimpleEditor
|
||||||
|
bind:this={monaco}
|
||||||
|
bind:code={arg.expr}
|
||||||
|
on:change={() => {
|
||||||
|
dispatch('change', { argName })
|
||||||
|
}}
|
||||||
|
{extraLib}
|
||||||
|
lang="javascript"
|
||||||
|
shouldBindKey={false}
|
||||||
|
on:focus={() => {
|
||||||
|
focused = true
|
||||||
|
focusProp(argName, 'insert', (path) => {
|
||||||
|
monaco?.insertAtCursor(path)
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
on:change={() => {
|
||||||
|
dispatch('change', { argName })
|
||||||
|
}}
|
||||||
|
on:blur={() => {
|
||||||
|
focused = false
|
||||||
|
}}
|
||||||
|
autoHeight
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<DynamicInputHelpBox />
|
||||||
|
<div class="mb-2" />
|
||||||
|
{:else}
|
||||||
|
Not recognized input type {argName} ({arg.expr}, {propertyType})
|
||||||
|
<div class="flex mt-2">
|
||||||
|
<Button
|
||||||
|
variant="border"
|
||||||
|
size="xs"
|
||||||
|
on:click={() => {
|
||||||
|
arg.expr = ''
|
||||||
|
}}>Set expr to empty string</Button
|
||||||
|
></div
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if (propertyType === undefined || propertyType == 'static') && schema?.properties?.[argName]}
|
|
||||||
<ArgInput
|
{#if $propPickerConfig?.propName == argName && ($propPickerConfig?.insertionMode == 'insert' || $propPickerConfig?.insertionMode == 'append')}
|
||||||
{resourceTypes}
|
<div class="text-blue-500 mt-2" in:fade={{ duration: 200 }}>
|
||||||
noMargin
|
<svg
|
||||||
compact
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
bind:this={argInput}
|
width="20"
|
||||||
on:focus={onFocus}
|
height="20"
|
||||||
on:blur={() => {
|
viewBox="0 0 24 24"
|
||||||
focused = false
|
fill="currentColor"
|
||||||
}}
|
stroke="currentColor"
|
||||||
shouldDispatchChanges
|
stroke-width="2"
|
||||||
on:change={() => {
|
stroke-linecap="round"
|
||||||
dispatch('change', { argName })
|
stroke-linejoin="round"
|
||||||
}}
|
>
|
||||||
label={argName}
|
<polyline points="24 24 12 12 24 0" />
|
||||||
bind:editor={monaco}
|
</svg>
|
||||||
bind:description={schema.properties[argName].description}
|
</div>
|
||||||
bind:value={arg.value}
|
{:else}
|
||||||
type={schema.properties[argName].type}
|
<div class="w-0" />
|
||||||
oneOf={schema.properties[argName].oneOf}
|
{/if}
|
||||||
required={schema.required?.includes(argName)}
|
</div>
|
||||||
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
|
|
||||||
nullable={schema.properties[argName].nullable}
|
|
||||||
bind:title={schema.properties[argName].title}
|
|
||||||
bind:placeholder={schema.properties[argName].placeholder}
|
|
||||||
/>
|
|
||||||
{:else if arg.expr != undefined}
|
|
||||||
<div class="border mt-2">
|
|
||||||
<SimpleEditor
|
|
||||||
bind:this={monaco}
|
|
||||||
bind:code={arg.expr}
|
|
||||||
on:change={() => {
|
|
||||||
dispatch('change', { argName })
|
|
||||||
}}
|
|
||||||
{extraLib}
|
|
||||||
lang="javascript"
|
|
||||||
shouldBindKey={false}
|
|
||||||
on:focus={() => {
|
|
||||||
focused = true
|
|
||||||
focusProp(argName, 'insert', (path) => {
|
|
||||||
monaco?.insertAtCursor(path)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
on:change={() => {
|
|
||||||
dispatch('change', { argName })
|
|
||||||
}}
|
|
||||||
on:blur={() => {
|
|
||||||
focused = false
|
|
||||||
}}
|
|
||||||
autoHeight
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<DynamicInputHelpBox />
|
|
||||||
<div class="mb-2" />
|
|
||||||
{:else}
|
|
||||||
Not recognized input type {argName} ({arg.expr}, {propertyType})
|
|
||||||
<div class="flex mt-2">
|
|
||||||
<Button
|
|
||||||
variant="border"
|
|
||||||
size="xs"
|
|
||||||
on:click={() => {
|
|
||||||
arg.expr = ''
|
|
||||||
}}>Set expr to empty string</Button
|
|
||||||
></div
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
{#if keys.length > 0}
|
{#if keys.length > 0}
|
||||||
{#each keys as argName (argName)}
|
{#each keys as argName (argName)}
|
||||||
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
|
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
|
||||||
<div class="z-10 pt-4">
|
<div class="z-10 pt-2 relative">
|
||||||
<InputTransformForm
|
<InputTransformForm
|
||||||
{previousModuleId}
|
{previousModuleId}
|
||||||
bind:arg={args[argName]}
|
bind:arg={args[argName]}
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
slot="submission"
|
slot="submission"
|
||||||
class="flex flex-row-reverse w-full bg-surface border-t border-gray-200 rounded-bl-lg rounded-br-lg"
|
class="flex flex-row-reverse w-full border-t border-gray-200 rounded-bl-lg rounded-br-lg"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="border"
|
variant="border"
|
||||||
|
|||||||
@@ -405,6 +405,15 @@
|
|||||||
editor && editor.dispose()
|
editor && editor.dispose()
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export function setCursorToEnd(): void {
|
||||||
|
if (editor) {
|
||||||
|
const lastLine = editor.getModel()?.getLineCount() ?? 1
|
||||||
|
const lastColumn = editor.getModel()?.getLineMaxColumn(lastLine) ?? 1
|
||||||
|
editor.setPosition({ lineNumber: lastLine, column: lastColumn })
|
||||||
|
editor.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<EditorTheme />
|
<EditorTheme />
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
export let marginWidth = '2px'
|
||||||
|
export let animationDuration = '2s'
|
||||||
|
export let baseRadius = '4px'
|
||||||
|
export let animate = true
|
||||||
|
export let wrapperClasses = ''
|
||||||
|
export let ringColor = 'transparent'
|
||||||
|
|
||||||
|
let clientWidth = 0
|
||||||
|
let clientHeight = 0
|
||||||
|
|
||||||
|
$: circleRadius = Math.ceil(
|
||||||
|
Math.sqrt(clientWidth * clientWidth + clientHeight * clientHeight) / 2
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class={twMerge('gradient-button', wrapperClasses)}
|
||||||
|
style="--margin-width: {marginWidth}; --animation-duration: {animationDuration}; --base-radius: {baseRadius}; --circle-radius: {circleRadius}; --ring-color: {ringColor}"
|
||||||
|
class:animate
|
||||||
|
bind:clientWidth
|
||||||
|
bind:clientHeight
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.gradient-button {
|
||||||
|
position: relative;
|
||||||
|
padding: var(--margin-width, 2px);
|
||||||
|
font-size: inherit;
|
||||||
|
border: none;
|
||||||
|
border-radius: calc(var(--base-radius) + var(--margin-width, 2px));
|
||||||
|
color: currentColor;
|
||||||
|
background: inherit;
|
||||||
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Circular gradient */
|
||||||
|
.gradient-button::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: calc(var(--circle-radius, 300px) * 2px);
|
||||||
|
height: calc(var(--circle-radius, 300px) * 2px);
|
||||||
|
background: var(--ring-color, transparent);
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: -1;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-button.animate::before {
|
||||||
|
background: conic-gradient(from 0deg, #a4c7f2, #0c79fd, #104688, #a4c7f2);
|
||||||
|
animation: rotate var(--animation-duration, 2s) linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* inner background */
|
||||||
|
.gradient-button::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: var(--margin-width, 2px);
|
||||||
|
right: var(--margin-width, 2px);
|
||||||
|
bottom: var(--margin-width, 2px);
|
||||||
|
left: var(--margin-width, 2px);
|
||||||
|
background: inherit;
|
||||||
|
border-radius: var(--base-radius);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: translate(-50%, -50%) rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translate(-50%, -50%) rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-button.animate:hover::before {
|
||||||
|
animation-duration: 1s;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Popover on:close class="leading-none">
|
<Popover on:close class="leading-none">
|
||||||
<PopoverButton>
|
<PopoverButton let:open>
|
||||||
<div use:floatingRef>
|
<div use:floatingRef>
|
||||||
<slot name="button" />
|
<slot name="button" {open} />
|
||||||
</div>
|
</div>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
<ConditionalPortal condition={shouldUsePortal} {target}>
|
<ConditionalPortal condition={shouldUsePortal} {target}>
|
||||||
|
|||||||
@@ -4,11 +4,13 @@
|
|||||||
import FlowModuleSchemaMap from './map/FlowModuleSchemaMap.svelte'
|
import FlowModuleSchemaMap from './map/FlowModuleSchemaMap.svelte'
|
||||||
import WindmillIcon from '../icons/WindmillIcon.svelte'
|
import WindmillIcon from '../icons/WindmillIcon.svelte'
|
||||||
import { Skeleton } from '../common'
|
import { Skeleton } from '../common'
|
||||||
import { getContext } from 'svelte'
|
import { getContext, setContext } from 'svelte'
|
||||||
import type { FlowEditorContext } from './types'
|
import type { FlowEditorContext } from './types'
|
||||||
import type { FlowCopilotContext } from '../copilot/flow'
|
import type { FlowCopilotContext } from '../copilot/flow'
|
||||||
import { classNames } from '$lib/utils'
|
import { classNames } from '$lib/utils'
|
||||||
|
import { writable } from 'svelte/store'
|
||||||
|
import type { PropPickerWrapperContext, PropPickerConfig } from '$lib/components/prop_picker'
|
||||||
|
import type { PickableProperties } from '$lib/components/flows/previousResults'
|
||||||
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||||
|
|
||||||
export let loading: boolean
|
export let loading: boolean
|
||||||
@@ -24,6 +26,23 @@
|
|||||||
|
|
||||||
const { currentStepStore: copilotCurrentStepStore } =
|
const { currentStepStore: copilotCurrentStepStore } =
|
||||||
getContext<FlowCopilotContext>('FlowCopilotContext')
|
getContext<FlowCopilotContext>('FlowCopilotContext')
|
||||||
|
|
||||||
|
const propPickerConfig = writable<PropPickerConfig | undefined>(undefined)
|
||||||
|
setContext<PropPickerWrapperContext>('PropPickerWrapper', {
|
||||||
|
propPickerConfig,
|
||||||
|
inputMatches: writable(undefined),
|
||||||
|
focusProp: (propName, insertionMode, onSelect) => {
|
||||||
|
propPickerConfig.set({
|
||||||
|
propName,
|
||||||
|
insertionMode,
|
||||||
|
onSelect
|
||||||
|
})
|
||||||
|
},
|
||||||
|
clearFocus: () => {
|
||||||
|
propPickerConfig.set(undefined)
|
||||||
|
},
|
||||||
|
filteredPickableProperties: writable<PickableProperties | undefined>(undefined)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { Alert, Badge } from '$lib/components/common'
|
import { Alert, Badge } from '$lib/components/common'
|
||||||
import type { FlowModule, FlowModuleValue, InputTransform, PathScript, RawScript } from '$lib/gen'
|
import type { FlowModule, FlowModuleValue, InputTransform, PathScript, RawScript } from '$lib/gen'
|
||||||
import { getContext, setContext } from 'svelte'
|
import { getContext, setContext } from 'svelte'
|
||||||
import type { PropPickerWrapperContext } from '../propPicker/PropPickerWrapper.svelte'
|
import type { PropPickerWrapperContext } from '$lib/components/prop_picker'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import Toggle from '../../Toggle.svelte'
|
import Toggle from '../../Toggle.svelte'
|
||||||
import InputTransformSchemaForm from '$lib/components/InputTransformSchemaForm.svelte'
|
import InputTransformSchemaForm from '$lib/components/InputTransformSchemaForm.svelte'
|
||||||
@@ -79,7 +79,9 @@
|
|||||||
setContext<PropPickerWrapperContext>('PropPickerWrapper', {
|
setContext<PropPickerWrapperContext>('PropPickerWrapper', {
|
||||||
focusProp: () => {},
|
focusProp: () => {},
|
||||||
propPickerConfig: writable(undefined),
|
propPickerConfig: writable(undefined),
|
||||||
clearFocus: () => {}
|
inputMatches: writable(undefined),
|
||||||
|
clearFocus: () => {},
|
||||||
|
filteredPickableProperties: writable(undefined)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -378,10 +378,11 @@
|
|||||||
class={advancedSelected === 'runtime' ? 'h-[calc(100%-68px)]' : 'h-[calc(100%-34px)]'}
|
class={advancedSelected === 'runtime' ? 'h-[calc(100%-68px)]' : 'h-[calc(100%-34px)]'}
|
||||||
>
|
>
|
||||||
{#if selected === 'inputs' && (flowModule.value.type == 'rawscript' || flowModule.value.type == 'script' || flowModule.value.type == 'flow')}
|
{#if selected === 'inputs' && (flowModule.value.type == 'rawscript' || flowModule.value.type == 'script' || flowModule.value.type == 'flow')}
|
||||||
<div class="h-full overflow-auto px-2" id="flow-editor-step-input">
|
<div class="h-full overflow-auto px-2 bg-surface" id="flow-editor-step-input">
|
||||||
<PropPickerWrapper
|
<PropPickerWrapper
|
||||||
pickableProperties={stepPropPicker.pickableProperties}
|
pickableProperties={stepPropPicker.pickableProperties}
|
||||||
error={failureModule}
|
error={failureModule}
|
||||||
|
noPadding
|
||||||
>
|
>
|
||||||
<InputTransformSchemaForm
|
<InputTransformSchemaForm
|
||||||
bind:this={inputTransformSchemaForm}
|
bind:this={inputTransformSchemaForm}
|
||||||
|
|||||||
@@ -28,7 +28,8 @@
|
|||||||
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
||||||
import { getDependeeAndDependentComponents } from '../flowExplorer'
|
import { getDependeeAndDependentComponents } from '../flowExplorer'
|
||||||
import { replaceId } from '../flowStore'
|
import { replaceId } from '../flowStore'
|
||||||
|
import FlowPropPicker from '$lib/components/flows/propPicker/FlowPropPicker.svelte'
|
||||||
|
import type { PropPickerWrapperContext } from '$lib/components/prop_picker'
|
||||||
export let selected: boolean = false
|
export let selected: boolean = false
|
||||||
export let deletable: boolean = false
|
export let deletable: boolean = false
|
||||||
export let retry: boolean = false
|
export let retry: boolean = false
|
||||||
@@ -46,17 +47,24 @@
|
|||||||
export let concurrency: boolean = false
|
export let concurrency: boolean = false
|
||||||
export let retries: number | undefined = undefined
|
export let retries: number | undefined = undefined
|
||||||
export let warningMessage: string | undefined = undefined
|
export let warningMessage: string | undefined = undefined
|
||||||
|
let pickableIds: Record<string, any> | undefined = undefined
|
||||||
|
|
||||||
const { flowInputsStore } = getContext<{ flowInputsStore: Writable<FlowInput | undefined> }>(
|
const { flowInputsStore } = getContext<{ flowInputsStore: Writable<FlowInput | undefined> }>(
|
||||||
'FlowGraphContext'
|
'FlowGraphContext'
|
||||||
)
|
)
|
||||||
|
|
||||||
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
|
const flowEditorContext = getContext<FlowEditorContext>('FlowEditorContext')
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const { currentStepStore: copilotCurrentStepStore } =
|
const { currentStepStore: copilotCurrentStepStore } =
|
||||||
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
|
getContext<FlowCopilotContext | undefined>('FlowCopilotContext') || {}
|
||||||
|
|
||||||
|
const { propPickerConfig, filteredPickableProperties } =
|
||||||
|
getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||||
|
|
||||||
|
$: filteredPickableProperties && (pickableIds = $filteredPickableProperties?.priorIds)
|
||||||
|
|
||||||
let editId = false
|
let editId = false
|
||||||
|
|
||||||
let newId: string = id ?? ''
|
let newId: string = id ?? ''
|
||||||
@@ -266,6 +274,19 @@ hover:border-blue-700 hover:!visible {hover ? '' : '!hidden'}"
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if id && $propPickerConfig && pickableIds && Object.keys(pickableIds).includes(id)}
|
||||||
|
<div class="absolute -bottom-[18px] right-[50%] translate-x-[50%]">
|
||||||
|
<FlowPropPicker
|
||||||
|
json={{
|
||||||
|
[id]: pickableIds[id]
|
||||||
|
}}
|
||||||
|
prefix={'results'}
|
||||||
|
viewOnly={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if deletable}
|
{#if deletable}
|
||||||
<button
|
<button
|
||||||
class="absolute -top-[10px] -right-[10px] rounded-full h-[20px] w-[20px] trash center-center text-secondary
|
class="absolute -top-[10px] -right-[10px] rounded-full h-[20px] w-[20px] trash center-center text-secondary
|
||||||
@@ -280,7 +301,7 @@ hover:border-blue-700 hover:!visible {hover ? '' : '!hidden'}"
|
|||||||
|
|
||||||
{#if id !== 'preprocessor'}
|
{#if id !== 'preprocessor'}
|
||||||
<button
|
<button
|
||||||
class="absolute -top-[10px] right-[60px] rounded-full h-[20px] w-[20px] trash center-center text-secondary
|
class="absolute -top-[10px] right-[60px] rounded-full h-[20px] w-[20px] center-center text-secondary
|
||||||
outline-[1px] outline dark:outline-gray-500 outline-gray-300 bg-surface duration-150 hover:bg-blue-400 hover:text-white
|
outline-[1px] outline dark:outline-gray-500 outline-gray-300 bg-surface duration-150 hover:bg-blue-400 hover:text-white
|
||||||
{hover ? '' : '!hidden'}"
|
{hover ? '' : '!hidden'}"
|
||||||
on:click|preventDefault|stopPropagation={(event) => dispatch('move')}
|
on:click|preventDefault|stopPropagation={(event) => dispatch('move')}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
import { classNames } from '$lib/utils'
|
import { classNames } from '$lib/utils'
|
||||||
import { createEventDispatcher, getContext } from 'svelte'
|
import { createEventDispatcher, getContext } from 'svelte'
|
||||||
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
|
import type { FlowCopilotContext } from '$lib/components/copilot/flow'
|
||||||
|
import type { PropPickerConfig } from '$lib/components/prop_picker'
|
||||||
|
import FlowPropPicker from '$lib/components/flows/propPicker/FlowPropPicker.svelte'
|
||||||
|
|
||||||
export let label: string | undefined = undefined
|
export let label: string | undefined = undefined
|
||||||
export let bgColor: string = ''
|
export let bgColor: string = ''
|
||||||
@@ -14,6 +16,9 @@
|
|||||||
export let borderColor: string | undefined = undefined
|
export let borderColor: string | undefined = undefined
|
||||||
export let hideId: boolean = false
|
export let hideId: boolean = false
|
||||||
export let preLabel: string | undefined = undefined
|
export let preLabel: string | undefined = undefined
|
||||||
|
export let propPickerConfig: PropPickerConfig | undefined = undefined
|
||||||
|
export let inputJson = {}
|
||||||
|
export let prefix = ''
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
insert: {
|
insert: {
|
||||||
@@ -33,7 +38,7 @@
|
|||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div
|
<div
|
||||||
class={classNames(
|
class={classNames(
|
||||||
'w-full flex relative overflow-hidden rounded-sm',
|
'w-full flex relative rounded-sm',
|
||||||
selectable ? 'cursor-pointer' : '',
|
selectable ? 'cursor-pointer' : '',
|
||||||
selected ? 'outline outline-offset-1 outline-2 outline-gray-600' : '',
|
selected ? 'outline outline-offset-1 outline-2 outline-gray-600' : '',
|
||||||
label === 'Input' && $copilotCurrentStepStore === 'Input' ? 'z-[901]' : ''
|
label === 'Input' && $copilotCurrentStepStore === 'Input' ? 'z-[901]' : ''
|
||||||
@@ -76,4 +81,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{#if propPickerConfig && Object.keys(inputJson).length > 0}
|
||||||
|
<div class="absolute -bottom-[18px] right-[50%] translate-x-[50%]">
|
||||||
|
<FlowPropPicker json={inputJson} {prefix} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -260,3 +260,42 @@ declare const approvers: string
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildPrefixRegex(words: string[]): Array<{ regex: RegExp; word: string }> {
|
||||||
|
return words.map((word) => {
|
||||||
|
const prefixes: string[] = []
|
||||||
|
for (let i = 1; i <= word.length; i++) {
|
||||||
|
prefixes.push(word.slice(0, i) + '$')
|
||||||
|
}
|
||||||
|
prefixes.push(word + '\\.')
|
||||||
|
prefixes.push(word + '\\[')
|
||||||
|
|
||||||
|
return {
|
||||||
|
regex: new RegExp(`^(${prefixes.join('|')}).*`),
|
||||||
|
word
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterNestedObject(obj: any, nestedKeys: string[]) {
|
||||||
|
if (nestedKeys.length === 0) return obj
|
||||||
|
if (nestedKeys.length === 1) {
|
||||||
|
if (nestedKeys[0] === '') {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
const regexes = buildPrefixRegex(Object.keys(obj))
|
||||||
|
const matches = regexes.filter(({ regex }) => regex.test(nestedKeys[0]))
|
||||||
|
const filteredObj = {}
|
||||||
|
matches.forEach(({ word }) => {
|
||||||
|
if (obj.hasOwnProperty(word)) {
|
||||||
|
filteredObj[word] = obj[word]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return filteredObj
|
||||||
|
}
|
||||||
|
const [key, ...rest] = nestedKeys
|
||||||
|
if (obj && typeof obj === 'object' && key in obj) {
|
||||||
|
return filterNestedObject(obj[key], rest)
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import ObjectViewer from '$lib/components/propertyPicker/ObjectViewer.svelte'
|
||||||
|
import AnimatedButton from '$lib/components/common/button/AnimatedButton.svelte'
|
||||||
|
import { Popup } from '$lib/components/common'
|
||||||
|
import { Plug } from 'lucide-svelte'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
import type { PropPickerWrapperContext } from '$lib/components/prop_picker'
|
||||||
|
import { getContext } from 'svelte'
|
||||||
|
import Popover from '$lib/components/Popover.svelte'
|
||||||
|
|
||||||
|
export let json = {}
|
||||||
|
export let prefix = ''
|
||||||
|
export let viewOnly = false
|
||||||
|
|
||||||
|
const { propPickerConfig } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
on:click|preventDefault|stopPropagation={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
on:keydown|preventDefault|stopPropagation
|
||||||
|
data-prop-picker
|
||||||
|
>
|
||||||
|
<AnimatedButton
|
||||||
|
animate={$propPickerConfig?.insertionMode === 'connect' && !viewOnly}
|
||||||
|
wrapperClasses="h-[20px] w-[20px] "
|
||||||
|
baseRadius="9999px"
|
||||||
|
marginWidth="1px"
|
||||||
|
>
|
||||||
|
<Popup floatingConfig={{ strategy: 'fixed', placement: 'bottom-start' }}>
|
||||||
|
<svelte:fragment slot="button" let:open>
|
||||||
|
<Popover disablePopup={open}>
|
||||||
|
<svelte:fragment slot="text">node outputs</svelte:fragment>
|
||||||
|
<button
|
||||||
|
class={twMerge(
|
||||||
|
'rounded-full trash center-center h-[18px] w-[18px]',
|
||||||
|
viewOnly
|
||||||
|
? 'outline-[1px] outline dark:outline-gray-500 outline-gray-300 duration-150 bg-surface hover:bg-surface-hover text-secondary'
|
||||||
|
: $propPickerConfig?.insertionMode == 'connect'
|
||||||
|
? 'bg-surface text-blue-500'
|
||||||
|
: 'outline-[1px] outline dark:outline-gray-500 outline-gray-300 duration-150 bg-blue-500 hover:bg-blue-700 text-white'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Plug size={12} strokeWidth={2} />
|
||||||
|
</button>
|
||||||
|
</Popover>
|
||||||
|
</svelte:fragment>
|
||||||
|
<div data-prop-picker>
|
||||||
|
<ObjectViewer
|
||||||
|
{json}
|
||||||
|
topBrackets={false}
|
||||||
|
pureViewer={viewOnly}
|
||||||
|
{prefix}
|
||||||
|
on:select={(e) => {
|
||||||
|
$propPickerConfig?.onSelect(e.detail)
|
||||||
|
$propPickerConfig = undefined
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Popup>
|
||||||
|
</AnimatedButton>
|
||||||
|
</button>
|
||||||
@@ -1,31 +1,13 @@
|
|||||||
<script context="module" lang="ts">
|
|
||||||
type InsertionMode = 'append' | 'connect' | 'insert'
|
|
||||||
|
|
||||||
type SelectCallback = (path: string) => boolean
|
|
||||||
|
|
||||||
type PropPickerConfig = {
|
|
||||||
insertionMode: InsertionMode
|
|
||||||
propName: string
|
|
||||||
onSelect: SelectCallback
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PropPickerWrapperContext = {
|
|
||||||
propPickerConfig: Writable<PropPickerConfig | undefined>
|
|
||||||
focusProp: (propName: string, insertionMode: InsertionMode, onSelect: SelectCallback) => void
|
|
||||||
clearFocus: () => void
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import PropPicker from '$lib/components/propertyPicker/PropPicker.svelte'
|
import PropPicker from '$lib/components/propertyPicker/PropPicker.svelte'
|
||||||
import PropPickerResult from '$lib/components/propertyPicker/PropPickerResult.svelte'
|
import PropPickerResult from '$lib/components/propertyPicker/PropPickerResult.svelte'
|
||||||
import { clickOutside, sendUserToast } from '$lib/utils'
|
import { clickOutside } from '$lib/utils'
|
||||||
import { createEventDispatcher, setContext } from 'svelte'
|
import { createEventDispatcher, getContext } from 'svelte'
|
||||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||||
import { writable, type Writable } from 'svelte/store'
|
|
||||||
import type { PickableProperties } from '../previousResults'
|
import type { PickableProperties } from '../previousResults'
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
import AnimatedButton from '$lib/components/common/button/AnimatedButton.svelte'
|
||||||
|
import type { PropPickerWrapperContext } from '$lib/components/prop_picker'
|
||||||
export let pickableProperties: PickableProperties | undefined
|
export let pickableProperties: PickableProperties | undefined
|
||||||
export let result: any = undefined
|
export let result: any = undefined
|
||||||
export let extraResults: any = undefined
|
export let extraResults: any = undefined
|
||||||
@@ -35,74 +17,98 @@
|
|||||||
export let notSelectable = false
|
export let notSelectable = false
|
||||||
export let noPadding: boolean = false
|
export let noPadding: boolean = false
|
||||||
|
|
||||||
const propPickerConfig = writable<PropPickerConfig | undefined>(undefined)
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
setContext<PropPickerWrapperContext>('PropPickerWrapper', {
|
const { propPickerConfig } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||||
propPickerConfig,
|
|
||||||
focusProp: (propName, insertionMode, onSelect) => {
|
async function getPropPickerElements(): Promise<HTMLElement[]> {
|
||||||
propPickerConfig.set({
|
return Array.from(
|
||||||
propName,
|
document.querySelectorAll('[data-prop-picker], [data-prop-picker] *')
|
||||||
insertionMode,
|
) as HTMLElement[]
|
||||||
onSelect
|
}
|
||||||
})
|
|
||||||
},
|
|
||||||
clearFocus: () => {
|
|
||||||
propPickerConfig.set(undefined)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="h-full w-full"
|
class="h-full w-full"
|
||||||
use:clickOutside
|
data-prop-picker-root
|
||||||
on:click_outside={() => propPickerConfig.set(undefined)}
|
use:clickOutside={{ capture: true, exclude: getPropPickerElements }}
|
||||||
|
on:click_outside={() => {
|
||||||
|
propPickerConfig.set(undefined)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Splitpanes>
|
<Splitpanes class={$propPickerConfig ? 'splitpanes-remove-splitter' : ''}>
|
||||||
<Pane
|
<Pane
|
||||||
minSize={20}
|
minSize={20}
|
||||||
size={60}
|
size={60}
|
||||||
class={twMerge('relative !transition-none', noPadding ? '' : 'p-2')}
|
class={twMerge('relative !transition-none ', noPadding ? '' : 'p-2')}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</Pane>
|
</Pane>
|
||||||
<Pane
|
<Pane
|
||||||
minSize={20}
|
minSize={20}
|
||||||
size={40}
|
size={40}
|
||||||
class="pt-2 relative !transition-none {$propPickerConfig ? 'border-2 border-blue-500' : ''}"
|
class="!transition-none z-1000 {$propPickerConfig ? 'ml-[-1px]' : ''}"
|
||||||
>
|
>
|
||||||
{#if result}
|
<AnimatedButton
|
||||||
<PropPickerResult
|
animate={$propPickerConfig?.insertionMode == 'connect'}
|
||||||
{result}
|
baseRadius="4px"
|
||||||
{extraResults}
|
wrapperClasses="h-full w-full pt-2"
|
||||||
{flow_input}
|
marginWidth="4px"
|
||||||
on:select={({ detail }) => {
|
ringColor={$propPickerConfig?.insertionMode == 'insert' ||
|
||||||
if (!notSelectable && !$propPickerConfig) {
|
$propPickerConfig?.insertionMode == 'append'
|
||||||
sendUserToast('Set cursor within an input or click on the plug first', true)
|
? '#3b82f6'
|
||||||
}
|
: 'transparent'}
|
||||||
dispatch('select', detail)
|
animationDuration="1s"
|
||||||
if ($propPickerConfig?.onSelect(detail)) {
|
>
|
||||||
propPickerConfig.set(undefined)
|
{#if result}
|
||||||
}
|
<PropPickerResult
|
||||||
}}
|
{result}
|
||||||
/>
|
{extraResults}
|
||||||
{:else if pickableProperties}
|
{flow_input}
|
||||||
<PropPicker
|
allowCopy={!notSelectable && !$propPickerConfig}
|
||||||
{displayContext}
|
on:select={({ detail }) => {
|
||||||
{error}
|
dispatch('select', detail)
|
||||||
{pickableProperties}
|
if ($propPickerConfig?.onSelect(detail)) {
|
||||||
{notSelectable}
|
propPickerConfig.set(undefined)
|
||||||
on:select={({ detail }) => {
|
}
|
||||||
if (!notSelectable && !$propPickerConfig) {
|
}}
|
||||||
sendUserToast('Set cursor within an input or click on the plug first', true)
|
/>
|
||||||
}
|
{:else if pickableProperties}
|
||||||
dispatch('select', detail)
|
<PropPicker
|
||||||
if ($propPickerConfig?.onSelect(detail)) {
|
{displayContext}
|
||||||
propPickerConfig.set(undefined)
|
{error}
|
||||||
}
|
{pickableProperties}
|
||||||
}}
|
{notSelectable}
|
||||||
/>
|
allowCopy={!notSelectable && !$propPickerConfig}
|
||||||
{/if}
|
on:select={({ detail }) => {
|
||||||
|
dispatch('select', detail)
|
||||||
|
if ($propPickerConfig?.onSelect(detail)) {
|
||||||
|
propPickerConfig.set(undefined)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</AnimatedButton>
|
||||||
</Pane>
|
</Pane>
|
||||||
</Splitpanes>
|
</Splitpanes>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.splitpanes-remove-splitter > .splitpanes__pane) {
|
||||||
|
background-color: inherit !important;
|
||||||
|
}
|
||||||
|
:global(.splitpanes-remove-splitter > .splitpanes__splitter) {
|
||||||
|
background-color: transparent !important;
|
||||||
|
width: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.splitpanes__pane) {
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.splitpanes__pane:hover) {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
import { getStateColor } from '../../util'
|
import { getStateColor } from '../../util'
|
||||||
import type { GraphModuleState } from '../../model'
|
import type { GraphModuleState } from '../../model'
|
||||||
import type { GraphEventHandlers } from '../../graphBuilder'
|
import type { GraphEventHandlers } from '../../graphBuilder'
|
||||||
|
import { getContext } from 'svelte'
|
||||||
|
import type { PropPickerWrapperContext } from '$lib/components/prop_picker'
|
||||||
export let data: {
|
export let data: {
|
||||||
offset: number
|
offset: number
|
||||||
id: string
|
id: string
|
||||||
@@ -13,11 +14,21 @@
|
|||||||
flowModuleStates: Record<string, GraphModuleState> | undefined
|
flowModuleStates: Record<string, GraphModuleState> | undefined
|
||||||
eventHandlers: GraphEventHandlers
|
eventHandlers: GraphEventHandlers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { propPickerConfig, filteredPickableProperties } =
|
||||||
|
getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||||
|
|
||||||
|
$: filteredInput = filterIterFromInput($filteredPickableProperties?.flow_input)
|
||||||
|
|
||||||
|
function filterIterFromInput(inputJson: Record<string, any> | undefined): Record<string, any> {
|
||||||
|
if (!inputJson || typeof inputJson !== 'object' || !inputJson.iter) return {}
|
||||||
|
return { iter: inputJson.iter }
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NodeWrapper let:darkMode offset={data.offset}>
|
<NodeWrapper let:darkMode offset={data.offset}>
|
||||||
<VirtualItem
|
<VirtualItem
|
||||||
label={'Do one iteration'}
|
label={'Do one iterations'}
|
||||||
selectable={false}
|
selectable={false}
|
||||||
selected={false}
|
selected={false}
|
||||||
id={data.id}
|
id={data.id}
|
||||||
@@ -27,5 +38,8 @@
|
|||||||
on:select={(e) => {
|
on:select={(e) => {
|
||||||
data?.eventHandlers?.select(e.detail)
|
data?.eventHandlers?.select(e.detail)
|
||||||
}}
|
}}
|
||||||
|
propPickerConfig={$propPickerConfig}
|
||||||
|
inputJson={filteredInput}
|
||||||
|
prefix="flow_input"
|
||||||
/>
|
/>
|
||||||
</NodeWrapper>
|
</NodeWrapper>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import type { Writable } from 'svelte/store'
|
import type { Writable } from 'svelte/store'
|
||||||
import InsertModuleButton from '$lib/components/flows/map/InsertModuleButton.svelte'
|
import InsertModuleButton from '$lib/components/flows/map/InsertModuleButton.svelte'
|
||||||
|
import type { PropPickerWrapperContext } from '$lib/components/prop_picker'
|
||||||
|
|
||||||
export let data: {
|
export let data: {
|
||||||
hasPreprocessor: boolean
|
hasPreprocessor: boolean
|
||||||
@@ -23,6 +24,20 @@
|
|||||||
const { selectedId } = getContext<{
|
const { selectedId } = getContext<{
|
||||||
selectedId: Writable<string | undefined>
|
selectedId: Writable<string | undefined>
|
||||||
}>('FlowGraphContext')
|
}>('FlowGraphContext')
|
||||||
|
|
||||||
|
const { propPickerConfig, filteredPickableProperties } =
|
||||||
|
getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||||
|
|
||||||
|
function filterIterFromInput(inputJson: Record<string, any> | undefined): Record<string, any> {
|
||||||
|
if (!inputJson || typeof inputJson !== 'object') return {}
|
||||||
|
|
||||||
|
const newJson = { ...inputJson }
|
||||||
|
delete newJson.iter
|
||||||
|
|
||||||
|
return newJson
|
||||||
|
}
|
||||||
|
|
||||||
|
$: filteredInput = filterIterFromInput($filteredPickableProperties?.flow_input)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NodeWrapper let:darkMode>
|
<NodeWrapper let:darkMode>
|
||||||
@@ -64,5 +79,8 @@
|
|||||||
on:select={(e) => {
|
on:select={(e) => {
|
||||||
data.eventHandlers?.select(e.detail)
|
data.eventHandlers?.select(e.detail)
|
||||||
}}
|
}}
|
||||||
|
propPickerConfig={$propPickerConfig}
|
||||||
|
inputJson={filteredInput}
|
||||||
|
prefix="flow_input"
|
||||||
/>
|
/>
|
||||||
</NodeWrapper>
|
</NodeWrapper>
|
||||||
|
|||||||
20
frontend/src/lib/components/prop_picker.ts
Normal file
20
frontend/src/lib/components/prop_picker.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { Writable } from 'svelte/store'
|
||||||
|
import type { PickableProperties } from '$lib/components/flows/previousResults'
|
||||||
|
|
||||||
|
type InsertionMode = 'append' | 'connect' | 'insert'
|
||||||
|
|
||||||
|
type SelectCallback = (path: string) => boolean
|
||||||
|
|
||||||
|
export type PropPickerConfig = {
|
||||||
|
insertionMode: InsertionMode
|
||||||
|
propName: string
|
||||||
|
onSelect: SelectCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PropPickerWrapperContext = {
|
||||||
|
propPickerConfig: Writable<PropPickerConfig | undefined>
|
||||||
|
filteredPickableProperties: Writable<PickableProperties | undefined>
|
||||||
|
inputMatches: Writable<{ word: string; value: string }[] | undefined>
|
||||||
|
focusProp: (propName: string, insertionMode: InsertionMode, onSelect: SelectCallback) => void
|
||||||
|
clearFocus: () => void
|
||||||
|
}
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { copyToClipboard, pluralize, truncate } from '$lib/utils'
|
import { copyToClipboard, truncate } from '$lib/utils'
|
||||||
|
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import { Badge } from '../common'
|
|
||||||
import { computeKey } from './utils'
|
import { computeKey } from './utils'
|
||||||
import WarningMessage from './WarningMessage.svelte'
|
|
||||||
import { NEVER_TESTED_THIS_FAR } from '../flows/models'
|
import { NEVER_TESTED_THIS_FAR } from '../flows/models'
|
||||||
import Portal from '$lib/components/Portal.svelte'
|
import Portal from '$lib/components/Portal.svelte'
|
||||||
|
import { Button } from '$lib/components/common'
|
||||||
|
import Popover from '$lib/components/Popover.svelte'
|
||||||
import { Download, PanelRightOpen } from 'lucide-svelte'
|
import { Download, PanelRightOpen } from 'lucide-svelte'
|
||||||
import S3FilePicker from '../S3FilePicker.svelte'
|
import S3FilePicker from '../S3FilePicker.svelte'
|
||||||
import { workspaceStore } from '$lib/stores'
|
import { workspaceStore } from '$lib/stores'
|
||||||
@@ -19,9 +18,9 @@
|
|||||||
export let collapsed = (level != 0 && level % 3 == 0) || Array.isArray(json)
|
export let collapsed = (level != 0 && level % 3 == 0) || Array.isArray(json)
|
||||||
export let rawKey = false
|
export let rawKey = false
|
||||||
export let topBrackets = false
|
export let topBrackets = false
|
||||||
export let topLevelNode = false
|
|
||||||
export let allowCopy = true
|
export let allowCopy = true
|
||||||
export let collapseLevel: number | undefined = undefined
|
export let collapseLevel: number | undefined = undefined
|
||||||
|
export let prefix = ''
|
||||||
|
|
||||||
let s3FileViewer: S3FilePicker
|
let s3FileViewer: S3FilePicker
|
||||||
|
|
||||||
@@ -50,12 +49,21 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
function selectProp(key: string, value: any | undefined = undefined) {
|
function computeFullKey(key: string, rawKey: boolean) {
|
||||||
if (pureViewer && allowCopy) {
|
if (rawKey) {
|
||||||
const valueToCopy = value !== undefined ? value : computeKey(key, isArray, currentPath)
|
return `${prefix}('${key}')`
|
||||||
copyToClipboard(valueToCopy)
|
|
||||||
}
|
}
|
||||||
dispatch('select', rawKey ? key : computeKey(key, isArray, currentPath))
|
const keyToSelect = computeKey(key, isArray, currentPath)
|
||||||
|
const separator = !prefix || keyToSelect.startsWith('[') ? '' : '.'
|
||||||
|
return prefix + separator + keyToSelect
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectProp(key: string, value: any | undefined = undefined) {
|
||||||
|
const fullKey = computeFullKey(key, rawKey)
|
||||||
|
if (pureViewer && allowCopy) {
|
||||||
|
copyToClipboard(fullKey)
|
||||||
|
}
|
||||||
|
dispatch('select', fullKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: keyLimit = isArray ? 1 : 100
|
$: keyLimit = isArray ? 1 : 100
|
||||||
@@ -73,33 +81,40 @@
|
|||||||
{#if level != 0 && keys.length > 1}
|
{#if level != 0 && keys.length > 1}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<span class="cursor-pointer border hover:bg-surface-hover px-1 rounded" on:click={collapse}>
|
<Button
|
||||||
-
|
color="light"
|
||||||
</span>
|
size="xs2"
|
||||||
|
variant="border"
|
||||||
|
on:click={collapse}
|
||||||
|
wrapperClasses="inline-flex w-fit h-5"
|
||||||
|
btnClasses="font-semibold text-primary border-nord-300 rounded-[0.275rem]">-</Button
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
{#if level == 0 && topBrackets}<span class="h-0">{openBracket}</span>{/if}
|
{#if level == 0 && topBrackets}<span class="h-0">{openBracket}</span>{/if}
|
||||||
<ul class={`w-full pl-2 ${level === 0 ? 'border-none' : 'border-l border-dotted'}`}>
|
<ul class={`w-full pl-2 ${level === 0 ? 'border-none' : 'border-l border-dotted'}`}>
|
||||||
{#each keys.length > keyLimit ? keys.slice(0, keyLimit) : keys as key, index (key)}
|
{#each keys.length > keyLimit ? keys.slice(0, keyLimit) : keys as key, index (key)}
|
||||||
<li>
|
<li>
|
||||||
<button on:click={() => selectProp(key)} class="whitespace-nowrap">
|
<Popover>
|
||||||
{#if topLevelNode}
|
<svelte:fragment slot="text">{computeFullKey(key, rawKey)}</svelte:fragment>
|
||||||
<Badge baseClass="border border-blue-600" color="indigo">{key}</Badge>
|
<Button
|
||||||
{:else}
|
on:click={() => selectProp(key)}
|
||||||
<span
|
size="xs2"
|
||||||
class="key {pureViewer
|
color="dark"
|
||||||
? 'cursor-auto'
|
variant="contained"
|
||||||
: 'border '} font-semibold rounded px-1 hover:bg-surface-hover text-2xs text-secondary"
|
wrapperClasses="inline-flex p-0 whitespace-nowrap w-fit h-4"
|
||||||
>
|
btnClasses="font-normal rounded-[0.275rem]"
|
||||||
{!isArray ? key : index}</span
|
>
|
||||||
>
|
<span class={pureViewer ? 'cursor-auto' : ''}>
|
||||||
{/if}:
|
{!isArray ? key : index}
|
||||||
</button>
|
</span>
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
:
|
||||||
{#if getTypeAsString(json[key]) === 'object'}
|
{#if getTypeAsString(json[key]) === 'object'}
|
||||||
<svelte:self
|
<svelte:self
|
||||||
json={json[key]}
|
json={json[key]}
|
||||||
level={level + 1}
|
level={level + 1}
|
||||||
currentPath={computeKey(key, isArray, currentPath)}
|
currentPath={computeFullKey(key, isArray)}
|
||||||
{pureViewer}
|
{pureViewer}
|
||||||
{allowCopy}
|
{allowCopy}
|
||||||
on:select
|
on:select
|
||||||
@@ -107,28 +122,38 @@
|
|||||||
collapsed={collapseLevel !== undefined ? level + 1 >= collapseLevel : undefined}
|
collapsed={collapseLevel !== undefined ? level + 1 >= collapseLevel : undefined}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<Popover disablePopup={!json[key]}>
|
||||||
class="val text-left {pureViewer
|
<svelte:fragment slot="text">
|
||||||
? 'cursor-auto'
|
{JSON.stringify(json[key])}
|
||||||
: ''} rounded px-1 hover:bg-blue-100 dark:hover:bg-blue-100/10 {getTypeAsString(
|
</svelte:fragment>
|
||||||
json[key]
|
<button
|
||||||
)}"
|
class="val text-left {pureViewer
|
||||||
on:click={() => selectProp(key, json[key])}
|
? 'cursor-auto'
|
||||||
>
|
: ''} rounded px-1 {getTypeAsString(json[key])}"
|
||||||
{#if json[key] === NEVER_TESTED_THIS_FAR}
|
on:click={() => {
|
||||||
<WarningMessage />
|
if (json[key]) {
|
||||||
{:else if json[key] == undefined}
|
copyToClipboard(json[key])
|
||||||
<span class="text-2xs">undefined</span>
|
}
|
||||||
{:else if json[key] == null}
|
}}
|
||||||
<span class="text-2xs">null</span>
|
disabled={false}
|
||||||
{:else if typeof json[key] == 'string'}
|
>
|
||||||
<span title={json[key]} class="text-2xs">"{truncate(json[key], 200)}"</span>
|
{#if json[key] === NEVER_TESTED_THIS_FAR}
|
||||||
{:else}
|
<span class="text-2xs text-tertiary font-normal">
|
||||||
<span title={JSON.stringify(json[key])} class="text-2xs">
|
Test the flow to see a value
|
||||||
{truncate(JSON.stringify(json[key]), 200)}
|
</span>
|
||||||
</span>
|
{:else if json[key] == undefined}
|
||||||
{/if}
|
<span class="text-2xs">undefined</span>
|
||||||
</button>
|
{:else if json[key] == null}
|
||||||
|
<span class="text-2xs">null</span>
|
||||||
|
{:else if typeof json[key] == 'string'}
|
||||||
|
<span class="text-2xs">"{truncate(json[key], 200)}"</span>
|
||||||
|
{:else}
|
||||||
|
<span class="text-2xs">
|
||||||
|
{truncate(JSON.stringify(json[key]), 200)}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</Popover>
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -167,17 +192,18 @@
|
|||||||
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<span
|
|
||||||
class="border border-blue-600 rounded px-1 cursor-pointer hover:bg-gray-200"
|
|
||||||
class:hidden={!fullyCollapsed}
|
|
||||||
on:click={collapse}
|
|
||||||
>
|
|
||||||
{openBracket}{collapsedSymbol}{closeBracket}
|
|
||||||
</span>
|
|
||||||
{#if fullyCollapsed}
|
{#if fullyCollapsed}
|
||||||
<span class="text-tertiary text-xs">
|
<Button
|
||||||
{pluralize(Object.keys(json).length, Array.isArray(json) ? 'item' : 'key')}
|
color="light"
|
||||||
</span>
|
size="xs2"
|
||||||
|
variant="border"
|
||||||
|
on:click={collapse}
|
||||||
|
wrapperClasses="inline-flex w-fit h-5"
|
||||||
|
btnClasses="font-semibold border-nord-300 rounded-[0.275rem] p-1"
|
||||||
|
>
|
||||||
|
{openBracket}{collapsedSymbol}{closeBracket}
|
||||||
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if topBrackets}
|
{:else if topBrackets}
|
||||||
<span class="text-primary">{openBracket}{closeBracket}</span>
|
<span class="text-primary">{openBracket}{closeBracket}</span>
|
||||||
|
|||||||
@@ -3,41 +3,45 @@
|
|||||||
import { workspaceStore } from '$lib/stores'
|
import { workspaceStore } from '$lib/stores'
|
||||||
import { getContext } from 'svelte'
|
import { getContext } from 'svelte'
|
||||||
import { Badge, Button } from '../common'
|
import { Badge, Button } from '../common'
|
||||||
import type { PropPickerWrapperContext } from '../flows/propPicker/PropPickerWrapper.svelte'
|
import type { PropPickerWrapperContext } from '../prop_picker'
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
|
|
||||||
import ObjectViewer from './ObjectViewer.svelte'
|
import ObjectViewer from './ObjectViewer.svelte'
|
||||||
import { keepByKey } from './utils'
|
import { keepByKey } from './utils'
|
||||||
import type { PickableProperties } from '../flows/previousResults'
|
import type { PickableProperties } from '../flows/previousResults'
|
||||||
import ClearableInput from '../common/clearableInput/ClearableInput.svelte'
|
import ClearableInput from '../common/clearableInput/ClearableInput.svelte'
|
||||||
|
import { filterNestedObject } from '../flows/previousResults'
|
||||||
|
|
||||||
export let pickableProperties: PickableProperties
|
export let pickableProperties: PickableProperties
|
||||||
export let displayContext = true
|
export let displayContext = true
|
||||||
export let notSelectable: boolean
|
export let notSelectable: boolean
|
||||||
export let error: boolean = false
|
export let error: boolean = false
|
||||||
|
export let allowCopy = false
|
||||||
|
|
||||||
$: previousId = pickableProperties?.previousId
|
$: previousId = pickableProperties?.previousId
|
||||||
let variables: Record<string, string> = {}
|
let variables: Record<string, string> = {}
|
||||||
let resources: Record<string, any> = {}
|
let resources: Record<string, any> = {}
|
||||||
let displayVariable = false
|
let displayVariable = false
|
||||||
let displayResources = false
|
let displayResources = false
|
||||||
|
let allResultsCollapsed = true
|
||||||
const dispatch = createEventDispatcher()
|
let flowInputsFiltered: Record<string, any> = {}
|
||||||
|
let resultByIdFiltered: Record<string, any> = {}
|
||||||
|
let collapsableInitialState:
|
||||||
|
| {
|
||||||
|
allResultsCollapsed: boolean
|
||||||
|
displayVariable: boolean
|
||||||
|
displayResources: boolean
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
|
||||||
const EMPTY_STRING = ''
|
const EMPTY_STRING = ''
|
||||||
let search = ''
|
let search = ''
|
||||||
|
|
||||||
const { propPickerConfig } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
const { propPickerConfig, filteredPickableProperties, inputMatches } =
|
||||||
|
getContext<PropPickerWrapperContext>('PropPickerWrapper')
|
||||||
|
|
||||||
$: flowInputsFiltered =
|
$filteredPickableProperties = { ...pickableProperties }
|
||||||
search === EMPTY_STRING
|
|
||||||
? pickableProperties.flow_input
|
|
||||||
: keepByKey(pickableProperties.flow_input, search)
|
|
||||||
|
|
||||||
$: resultByIdFiltered =
|
$: filterPickableProperties(), updateCollapsable(), search, $inputMatches
|
||||||
search === EMPTY_STRING
|
|
||||||
? pickableProperties.priorIds
|
|
||||||
: keepByKey(pickableProperties.priorIds, search)
|
|
||||||
|
|
||||||
$: suggestedPropsFiltered = $propPickerConfig
|
$: suggestedPropsFiltered = $propPickerConfig
|
||||||
? keepByKey(pickableProperties.priorIds, $propPickerConfig.propName)
|
? keepByKey(pickableProperties.priorIds, $propPickerConfig.propName)
|
||||||
@@ -62,23 +66,90 @@
|
|||||||
).map((resource) => [resource.path, resource.description ?? ''])
|
).map((resource) => [resource.path, resource.description ?? ''])
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterPickableProperties() {
|
||||||
|
flowInputsFiltered = pickableProperties.flow_input
|
||||||
|
resultByIdFiltered = pickableProperties.priorIds
|
||||||
|
|
||||||
|
if ($inputMatches) {
|
||||||
|
if (!$inputMatches.some((match) => match.word === 'flow_input')) {
|
||||||
|
flowInputsFiltered = []
|
||||||
|
}
|
||||||
|
if (!$inputMatches.some((match) => match.word === 'results')) {
|
||||||
|
resultByIdFiltered = []
|
||||||
|
}
|
||||||
|
if ($inputMatches.length == 1) {
|
||||||
|
if ($inputMatches[0].word === 'flow_input') {
|
||||||
|
let [, ...nestedKeys] = $inputMatches[0].value.split('.')
|
||||||
|
flowInputsFiltered = filterNestedObject(flowInputsFiltered, nestedKeys)
|
||||||
|
} else if ($inputMatches[0].word === 'results') {
|
||||||
|
let [, ...nestedKeys] = $inputMatches[0].value.split('.')
|
||||||
|
resultByIdFiltered = filterNestedObject(resultByIdFiltered, nestedKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flowInputsFiltered && search !== EMPTY_STRING) {
|
||||||
|
flowInputsFiltered = keepByKey(flowInputsFiltered, search)
|
||||||
|
}
|
||||||
|
if (resultByIdFiltered && search !== EMPTY_STRING) {
|
||||||
|
resultByIdFiltered = keepByKey(resultByIdFiltered, search)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filteredPickableProperties) {
|
||||||
|
resultByIdFiltered && ($filteredPickableProperties.priorIds = resultByIdFiltered)
|
||||||
|
flowInputsFiltered && ($filteredPickableProperties.flow_input = flowInputsFiltered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateCollapsable() {
|
||||||
|
if (!$inputMatches || $inputMatches.length !== 1) {
|
||||||
|
resetCollapsable()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!collapsableInitialState) {
|
||||||
|
collapsableInitialState = { allResultsCollapsed, displayVariable, displayResources }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($inputMatches[0].word === 'variable') {
|
||||||
|
await loadVariables()
|
||||||
|
displayVariable = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ($inputMatches[0].word === 'resource') {
|
||||||
|
await loadResources()
|
||||||
|
displayResources = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ($inputMatches[0].word === 'results') {
|
||||||
|
allResultsCollapsed = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetCollapsable() {
|
||||||
|
if (!collapsableInitialState) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
;({ allResultsCollapsed, displayVariable, displayResources } = collapsableInitialState)
|
||||||
|
collapsableInitialState = undefined
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col h-full">
|
<div class="flex flex-col h-full !bg-surface rounded overflow-hidden">
|
||||||
<div class="px-2">
|
<div class="px-2">
|
||||||
{#if !notSelectable}
|
{#if !notSelectable}
|
||||||
<div class="flex flex-row space-x-1">
|
{#if $propPickerConfig}
|
||||||
{#if $propPickerConfig}
|
<!-- <Badge large color="blue">
|
||||||
<Badge large color="blue">
|
{`Selected: ${$propPickerConfig?.propName}`}
|
||||||
{`Selected: ${$propPickerConfig?.propName}`}
|
</Badge> -->
|
||||||
</Badge>
|
<Badge large color="blue">
|
||||||
<Badge large color="blue">
|
{`Mode: ${$propPickerConfig?.insertionMode}`}
|
||||||
{`Mode: ${$propPickerConfig?.insertionMode}`}
|
</Badge>
|
||||||
</Badge>
|
{:else}
|
||||||
{:else}
|
<Badge large color="blue">← Edit or connect an input</Badge>
|
||||||
<Badge large color="blue">← Edit or connect an input</Badge>
|
{/if}
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
<ClearableInput bind:value={search} placeholder="Search prop..." wrapperClass="py-2" />
|
<ClearableInput bind:value={search} placeholder="Search prop..." wrapperClass="py-2" />
|
||||||
</div>
|
</div>
|
||||||
@@ -86,28 +157,26 @@
|
|||||||
class="overflow-y-auto px-2 pt-2 grow"
|
class="overflow-y-auto px-2 pt-2 grow"
|
||||||
class:bg-surface-secondary={!$propPickerConfig && !notSelectable}
|
class:bg-surface-secondary={!$propPickerConfig && !notSelectable}
|
||||||
>
|
>
|
||||||
<div class="flex justify-between items-center space-x-1">
|
{#if flowInputsFiltered && Object.keys(flowInputsFiltered).length > 0}
|
||||||
<span class="font-bold text-sm">Flow Input</span>
|
<div class="flex justify-between items-center space-x-1">
|
||||||
<div class="flex space-x-2 items-center" />
|
<span class="font-normal text-sm text-secondary">Flow Input</span>
|
||||||
</div>
|
<div class="flex space-x-2 items-center" />
|
||||||
<div class="overflow-y-auto mb-2">
|
</div>
|
||||||
<ObjectViewer
|
|
||||||
allowCopy={false}
|
|
||||||
pureViewer={!$propPickerConfig}
|
|
||||||
json={flowInputsFiltered}
|
|
||||||
on:select={(e) => {
|
|
||||||
dispatch(
|
|
||||||
'select',
|
|
||||||
e.detail?.startsWith('[') ? `flow_input${e.detail}` : `flow_input.${e.detail}`
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{#if error}
|
|
||||||
<span class="font-bold text-sm">Error</span>
|
|
||||||
<div class="overflow-y-auto mb-2">
|
<div class="overflow-y-auto mb-2">
|
||||||
<ObjectViewer
|
<ObjectViewer
|
||||||
allowCopy={false}
|
{allowCopy}
|
||||||
|
pureViewer={!$propPickerConfig}
|
||||||
|
json={flowInputsFiltered}
|
||||||
|
prefix="flow_input"
|
||||||
|
on:select
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if error}
|
||||||
|
<span class="font-normal text-sm text-secondary">Error</span>
|
||||||
|
<div class="overflow-y-auto mb-2">
|
||||||
|
<ObjectViewer
|
||||||
|
{allowCopy}
|
||||||
pureViewer={!$propPickerConfig}
|
pureViewer={!$propPickerConfig}
|
||||||
json={{
|
json={{
|
||||||
error: {
|
error: {
|
||||||
@@ -122,160 +191,186 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if Object.keys(pickableProperties.priorIds).length > 0}
|
{#if Object.keys(pickableProperties.priorIds).length > 0}
|
||||||
{#if suggestedPropsFiltered && Object.keys(suggestedPropsFiltered).length > 0}
|
{#if suggestedPropsFiltered && Object.keys(suggestedPropsFiltered).length > 0}
|
||||||
<span class="font-bold text-sm">Suggested Results</span>
|
<span class="font-normal text-sm text-secondary">Suggested Results</span>
|
||||||
<div class="overflow-y-auto mb-2">
|
<div class="overflow-y-auto mb-2">
|
||||||
<ObjectViewer
|
<ObjectViewer
|
||||||
allowCopy={false}
|
{allowCopy}
|
||||||
topLevelNode
|
|
||||||
pureViewer={!$propPickerConfig}
|
pureViewer={!$propPickerConfig}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
json={suggestedPropsFiltered}
|
json={suggestedPropsFiltered}
|
||||||
on:select={(e) => {
|
prefix="results"
|
||||||
dispatch('select', `results.${e.detail}`)
|
on:select
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="font-bold text-sm">All Results</span>
|
<span class="font-normal text-sm text-secondary">All Results</span>
|
||||||
<div class="overflow-y-auto mb-2">
|
<div class="overflow-y-auto mb-2">
|
||||||
<ObjectViewer
|
<ObjectViewer
|
||||||
allowCopy={false}
|
{allowCopy}
|
||||||
topLevelNode
|
|
||||||
pureViewer={!$propPickerConfig}
|
pureViewer={!$propPickerConfig}
|
||||||
collapsed={true}
|
collapsed={true}
|
||||||
json={resultByIdFiltered}
|
json={resultByIdFiltered}
|
||||||
on:select={(e) => {
|
prefix="results"
|
||||||
dispatch('select', `results.${e.detail}`)
|
on:select
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
{#if previousId}
|
{@const json = Object.fromEntries(
|
||||||
<span class="font-bold text-sm">Previous Result</span>
|
Object.entries(resultByIdFiltered).filter(([k, v]) => k == previousId)
|
||||||
|
)}
|
||||||
|
{#if previousId && Object.keys(json).length > 0}
|
||||||
|
<span class="font-normal text-sm text-secondary">Previous Result</span>
|
||||||
<div class="overflow-y-auto mb-2">
|
<div class="overflow-y-auto mb-2">
|
||||||
<ObjectViewer
|
<ObjectViewer
|
||||||
allowCopy={false}
|
{allowCopy}
|
||||||
topLevelNode
|
|
||||||
pureViewer={!$propPickerConfig}
|
pureViewer={!$propPickerConfig}
|
||||||
json={Object.fromEntries(
|
{json}
|
||||||
Object.entries(resultByIdFiltered).filter(([k, v]) => k == previousId)
|
prefix="results"
|
||||||
)}
|
on:select
|
||||||
on:select={(e) => {
|
|
||||||
dispatch('select', `results.${e.detail}`)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if pickableProperties.hasResume}
|
{#if pickableProperties.hasResume}
|
||||||
<span class="font-bold text-sm">Resume payloads</span>
|
<span class="font-normal text-sm text-secondary">Resume payloads</span>
|
||||||
<div class="overflow-y-auto mb-2">
|
<div class="overflow-y-auto mb-2">
|
||||||
<ObjectViewer
|
<ObjectViewer
|
||||||
allowCopy={false}
|
{allowCopy}
|
||||||
topLevelNode
|
|
||||||
pureViewer={!$propPickerConfig}
|
pureViewer={!$propPickerConfig}
|
||||||
json={{
|
json={{
|
||||||
resume: 'The resume payload',
|
resume: 'The resume payload',
|
||||||
resumes: 'All resume payloads from all approvers',
|
resumes: 'All resume payloads from all approvers',
|
||||||
approvers: 'The list of approvers'
|
approvers: 'The list of approvers'
|
||||||
}}
|
}}
|
||||||
on:select={(e) => {
|
on:select
|
||||||
dispatch('select', `${e.detail}`)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if Object.keys(pickableProperties.priorIds).length > 0}
|
{#if Object.keys(pickableProperties.priorIds).length > 0}
|
||||||
{#if suggestedPropsFiltered && Object.keys(suggestedPropsFiltered).length > 0}
|
{#if !$inputMatches && suggestedPropsFiltered && Object.keys(suggestedPropsFiltered).length > 0}
|
||||||
<span class="font-bold text-sm">Suggested Results</span>
|
<span class="font-normal text-sm text-secondary">Suggested Results</span>
|
||||||
<div class="overflow-y-auto mb-2">
|
<div class="overflow-y-auto mb-2">
|
||||||
<ObjectViewer
|
<ObjectViewer
|
||||||
allowCopy={false}
|
{allowCopy}
|
||||||
topLevelNode
|
|
||||||
pureViewer={!$propPickerConfig}
|
pureViewer={!$propPickerConfig}
|
||||||
collapsed={false}
|
collapsed={false}
|
||||||
json={suggestedPropsFiltered}
|
json={suggestedPropsFiltered}
|
||||||
on:select={(e) => {
|
prefix="results"
|
||||||
dispatch('select', `results.${e.detail}`)
|
on:select
|
||||||
}}
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if Object.keys(resultByIdFiltered).length > 0}
|
||||||
|
<div class="overflow-y-auto mb-2">
|
||||||
|
<span class="font-normal text-sm text-tertiary">All Results :</span>
|
||||||
|
{#if !allResultsCollapsed}
|
||||||
|
<Button
|
||||||
|
color="light"
|
||||||
|
size="xs2"
|
||||||
|
variant="border"
|
||||||
|
on:click={() => {
|
||||||
|
allResultsCollapsed = true
|
||||||
|
}}
|
||||||
|
wrapperClasses="inline-flex w-fit h-4"
|
||||||
|
btnClasses="font-normal text-primary border-nord-300 rounded-[0.275rem]">-</Button
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<ObjectViewer
|
||||||
|
{allowCopy}
|
||||||
|
pureViewer={!$propPickerConfig}
|
||||||
|
bind:collapsed={allResultsCollapsed}
|
||||||
|
json={resultByIdFiltered}
|
||||||
|
prefix="results"
|
||||||
|
on:select
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="font-bold text-sm">All Results</span>
|
|
||||||
<div class="overflow-y-auto mb-2">
|
|
||||||
<ObjectViewer
|
|
||||||
allowCopy={false}
|
|
||||||
topLevelNode
|
|
||||||
pureViewer={!$propPickerConfig}
|
|
||||||
collapsed={true}
|
|
||||||
json={resultByIdFiltered}
|
|
||||||
on:select={(e) => {
|
|
||||||
dispatch('select', `results.${e.detail}`)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if displayContext}
|
{#if displayContext}
|
||||||
<span class="font-bold text-sm">Variables </span>
|
{#if !$inputMatches || $inputMatches.some((match) => match.word === 'variable')}
|
||||||
<div class="overflow-y-auto mb-2">
|
<div class="overflow-y-auto mb-2">
|
||||||
{#if displayVariable}
|
<span class="font-normal text-sm text-secondary">Variables :</span>
|
||||||
<div class="flex">
|
|
||||||
|
{#if displayVariable}
|
||||||
<Button
|
<Button
|
||||||
color="light"
|
color="light"
|
||||||
size="xs"
|
size="xs2"
|
||||||
variant="border"
|
variant="border"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
displayVariable = false
|
displayVariable = false
|
||||||
}}>-</Button
|
}}
|
||||||
|
wrapperClasses="inline-flex w-fit h-4"
|
||||||
|
btnClasses="font-normal text-primary border-nord-300 rounded-[0.275rem]">-</Button
|
||||||
>
|
>
|
||||||
</div>
|
|
||||||
<ObjectViewer
|
<ObjectViewer
|
||||||
allowCopy={false}
|
{allowCopy}
|
||||||
pureViewer={!$propPickerConfig}
|
pureViewer={!$propPickerConfig}
|
||||||
rawKey={true}
|
rawKey={true}
|
||||||
json={variables}
|
json={variables}
|
||||||
on:select={(e) => dispatch('select', `variable('${e.detail}')`)}
|
prefix="variable"
|
||||||
/>
|
on:select
|
||||||
{:else}
|
/>
|
||||||
<button
|
{:else}
|
||||||
class="border border-blue-600 key font-normal rounded hover:bg-blue-100 px-1"
|
<Button
|
||||||
on:click={async () => {
|
color="light"
|
||||||
await loadVariables()
|
size="xs2"
|
||||||
displayVariable = true
|
variant="border"
|
||||||
}}>{'{...}'}</button
|
on:click={async () => {
|
||||||
>
|
await loadVariables()
|
||||||
{/if}
|
displayVariable = true
|
||||||
</div>
|
}}
|
||||||
<span class="font-bold text-sm">Resources</span>
|
wrapperClasses="inline-flex w-fit h-5"
|
||||||
<div class="overflow-y-auto mb-2">
|
btnClasses="font-semibold border-nord-300 rounded-[0.275rem] p-1"
|
||||||
{#if displayResources}
|
>
|
||||||
<Button
|
{'{...}'}
|
||||||
color="light"
|
</Button>
|
||||||
variant="border"
|
{/if}
|
||||||
size="xs"
|
</div>
|
||||||
on:click={() => {
|
{/if}
|
||||||
displayResources = false
|
{#if !$inputMatches || $inputMatches.some((match) => match.word === 'resource')}
|
||||||
}}>-</Button
|
<div class="overflow-y-auto mb-2">
|
||||||
>
|
<span class="font-normal text-sm text-secondary">Resources :</span>
|
||||||
<ObjectViewer
|
|
||||||
allowCopy={false}
|
{#if displayResources}
|
||||||
pureViewer={!$propPickerConfig}
|
<Button
|
||||||
rawKey={true}
|
color="light"
|
||||||
json={resources}
|
size="xs2"
|
||||||
on:select={(e) => dispatch('select', `resource('${e.detail}')`)}
|
variant="border"
|
||||||
/>
|
on:click={() => {
|
||||||
{:else}
|
displayResources = false
|
||||||
<button
|
}}
|
||||||
class="border border-blue-600 px-1 key font-normal rounded hover:bg-blue-100"
|
wrapperClasses="inline-flex w-fit h-5"
|
||||||
on:click={async () => {
|
btnClasses="font-semibold text-primary border-nord-300 rounded-[0.275rem]">-</Button
|
||||||
await loadResources()
|
>
|
||||||
displayResources = true
|
<ObjectViewer
|
||||||
}}>{'{...}'}</button
|
{allowCopy}
|
||||||
>
|
pureViewer={!$propPickerConfig}
|
||||||
{/if}
|
rawKey={true}
|
||||||
</div>
|
json={resources}
|
||||||
|
prefix="resource"
|
||||||
|
on:select
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<Button
|
||||||
|
color="light"
|
||||||
|
size="xs2"
|
||||||
|
variant="border"
|
||||||
|
on:click={async () => {
|
||||||
|
await loadResources()
|
||||||
|
displayResources = true
|
||||||
|
}}
|
||||||
|
wrapperClasses="inline-flex w-fit h-5"
|
||||||
|
btnClasses="font-semibold border-nord-300 rounded-[0.275rem] p-1"
|
||||||
|
>
|
||||||
|
{'{...}'}
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,31 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte'
|
|
||||||
import ObjectViewer from './ObjectViewer.svelte'
|
import ObjectViewer from './ObjectViewer.svelte'
|
||||||
|
|
||||||
|
export let allowCopy = false
|
||||||
export let result: any
|
export let result: any
|
||||||
export let extraResults: any = undefined
|
export let extraResults: any = undefined
|
||||||
export let flow_input: any = undefined
|
export let flow_input: any = undefined
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full px-2">
|
<div class="w-full px-2">
|
||||||
<span class="font-bold text-sm">Result</span>
|
<span class="font-normal text-sm text-secondary">Result</span>
|
||||||
<div class="overflow-y-auto mb-2 w-full">
|
<div class="overflow-y-auto mb-2 w-full">
|
||||||
<ObjectViewer
|
<ObjectViewer {allowCopy} json={{ result, ...(extraResults ? extraResults : {}) }} on:select />
|
||||||
allowCopy={false}
|
|
||||||
json={{ result, ...(extraResults ? extraResults : {}) }}
|
|
||||||
on:select
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{#if flow_input}
|
{#if flow_input}
|
||||||
<span class="font-bold text-sm">Flow Input</span>
|
<span class="font-normal text-sm text-secondary">Flow Input</span>
|
||||||
<div class="overflow-y-auto w-full">
|
<div class="overflow-y-auto w-full">
|
||||||
<ObjectViewer
|
<ObjectViewer {allowCopy} json={flow_input} prefix="flow_input" on:select />
|
||||||
allowCopy={false}
|
|
||||||
json={flow_input}
|
|
||||||
on:select={(e) => dispatch('select', `flow_input.${e.detail}`)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -171,13 +171,36 @@ export function validatePassword(password: string): boolean {
|
|||||||
|
|
||||||
const portalDivs = ['app-editor-select']
|
const portalDivs = ['app-editor-select']
|
||||||
|
|
||||||
export function clickOutside(node: Node, capture?: boolean): { destroy(): void } {
|
interface ClickOutsideOptions {
|
||||||
const handleClick = (event: MouseEvent) => {
|
capture?: boolean
|
||||||
|
exclude?: (() => Promise<HTMLElement[]>) | HTMLElement[] | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clickOutside(
|
||||||
|
node: Node,
|
||||||
|
options?: ClickOutsideOptions | boolean
|
||||||
|
): { destroy(): void; update(newOptions: ClickOutsideOptions | boolean): void } {
|
||||||
|
const handleClick = async (event: MouseEvent) => {
|
||||||
const target = event.target as HTMLElement
|
const target = event.target as HTMLElement
|
||||||
|
const opts = typeof options === 'boolean' ? { capture: options } : options
|
||||||
|
|
||||||
if (node && !node.contains(target) && !event.defaultPrevented) {
|
let excludedElements: HTMLElement[] = []
|
||||||
|
if (opts?.exclude) {
|
||||||
|
if (Array.isArray(opts.exclude)) {
|
||||||
|
excludedElements = opts.exclude
|
||||||
|
} else {
|
||||||
|
excludedElements = await opts.exclude()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isExcluded = excludedElements.some((excludedEl) => {
|
||||||
|
const contains = excludedEl?.contains?.(target)
|
||||||
|
const isTarget = target === excludedEl
|
||||||
|
return contains || isTarget
|
||||||
|
})
|
||||||
|
|
||||||
|
if (node && !node.contains(target) && !event.defaultPrevented && !isExcluded) {
|
||||||
const portalDivsSelector = portalDivs.map((id) => `#${id}`).join(', ')
|
const portalDivsSelector = portalDivs.map((id) => `#${id}`).join(', ')
|
||||||
|
|
||||||
const parent = target.closest(portalDivsSelector)
|
const parent = target.closest(portalDivsSelector)
|
||||||
|
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
@@ -186,11 +209,15 @@ export function clickOutside(node: Node, capture?: boolean): { destroy(): void }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('click', handleClick, capture ?? true)
|
const capture = typeof options === 'boolean' ? options : options?.capture ?? true
|
||||||
|
document.addEventListener('click', handleClick, capture)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
update(newOptions: ClickOutsideOptions | boolean) {
|
||||||
|
options = newOptions
|
||||||
|
},
|
||||||
destroy() {
|
destroy() {
|
||||||
document.removeEventListener('click', handleClick, capture ?? true)
|
document.removeEventListener('click', handleClick, capture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -724,7 +751,7 @@ export async function tryEvery({
|
|||||||
try {
|
try {
|
||||||
await tryCode()
|
await tryCode()
|
||||||
break
|
break
|
||||||
} catch (err) { }
|
} catch (err) {}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
if (i >= times) {
|
if (i >= times) {
|
||||||
@@ -948,5 +975,4 @@ export function getSchemaFromProperties(properties: { [name: string]: SchemaProp
|
|||||||
export function validateFileExtension(ext: string) {
|
export function validateFileExtension(ext: string) {
|
||||||
const validExtensionRegex = /^[a-zA-Z0-9]+([._][a-zA-Z0-9]+)*$/
|
const validExtensionRegex = /^[a-zA-Z0-9]+([._][a-zA-Z0-9]+)*$/
|
||||||
return validExtensionRegex.test(ext)
|
return validExtensionRegex.test(ext)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user