feat: Add flow input and current step in the prop picker (#236)

* Add flow input and current step in the prop picker

* Fix step + correctly bind pickableProperties

* Correctly make pickable properties + use popper to fix display issues

* styling

* Remove debugger

* Simplify how search works by removing one store

* preview

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
Faton Ramadani
2022-07-26 11:51:45 +02:00
committed by GitHub
parent 022f39be01
commit 17dfd558d5
12 changed files with 138 additions and 149 deletions
@@ -54,7 +54,7 @@
}
flowStore.subscribe((flow: Flow) => {
setQueryWithoutLoad($page.url, 'state', btoa(JSON.stringify(flowToMode(flow, mode))))
//setQueryWithoutLoad($page.url, 'state', btoa(JSON.stringify(flowToMode(flow, mode))))
})
onMount(() => {
@@ -33,6 +33,8 @@
let jobs = []
let jobId: string
$: dispatch('change', jobs)
export async function runPreview(args) {
viewPreview = true
intervalId && clearInterval(intervalId)
@@ -77,7 +79,6 @@
//only CompletedJob has success property
clearInterval(intervalId)
}
dispatch('change', job)
} catch (err) {
sendUserToast(err, true)
}
+18 -13
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { FlowModuleValue, type FlowModule } from '$lib/gen'
import { addPreviewResult, previewResults } from '$lib/stores'
import { buildExtraLib, objectToTsType, schemaToObject, schemaToTsType } from '$lib/utils'
import { previewResults } from '$lib/stores'
import { buildExtraLib, objectToTsType, schemaToTsType } from '$lib/utils'
import { faRobot } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
import Editor from './Editor.svelte'
@@ -17,6 +17,7 @@
schemasStore,
type FlowMode
} from './flows/flowStore'
import { getPickableProperties } from './flows/utils'
import SchemaForm from './SchemaForm.svelte'
import Tooltip from './Tooltip.svelte'
@@ -28,10 +29,11 @@
let editor: Editor
let websocketAlive = { pyright: false, black: false, deno: false }
let pickableProperties: Object | undefined = undefined
$: schema = $schemasStore[i]
$: shouldPick = mod.value.path === '' && mod.value.language === undefined
$: previousSchema = i === 0 ? schemaToObject($flowStore?.schema) : $previewResults[i]
$: pickableProperties = getPickableProperties($flowStore?.schema, args, $previewResults, mode, i)
$: extraLib = buildExtraLib(
i === 0 ? schemaToTsType($flowStore?.schema) : objectToTsType($previewResults[i])
)
@@ -107,7 +109,7 @@
{schema}
{extraLib}
{i}
{previousSchema}
bind:pickableProperties
bind:args={mod.input_transform}
/>
{/if}
@@ -123,19 +125,22 @@
{mode}
schemas={$schemasStore}
on:change={(e) => {
addPreviewResult(e.detail.result, i + 1)
const results = e.detail.map((x) => x.result)
previewResults.set(results)
}}
/>
</div>
<div>
<button class="w-full h-full" on:click={() => (open = -1)}>(-)</button>
</div>
{:else}
<div>
<button class="w-full h-full" on:click={() => (open = i)}>(+)</button>
</div>
{/if}
{/if}
{#if open == i}
<div>
<button class="w-full h-full" on:click={() => (open = -1)}>(-)</button>
</div>
{:else}
<div>
<button class="w-full h-full" on:click={() => (open = i)}>(+)</button>
</div>
{/if}
</div>
</li>
{#if i == 0 && mode == 'pull'}
@@ -144,7 +149,7 @@
Starting from here, the flow for loop over items from step 1's result above &nbsp;<Tooltip
>This flow being in 'Pull' mode, the rest of the flow will for loop over the list of items
returned by the trigger script right above. Retrieve the item value using
`previous_result._value`</Tooltip
`flow_input._value`</Tooltip
>
</div>
</li>
@@ -18,7 +18,7 @@
export let isValid: boolean = true
export let i: number | undefined = undefined
export let previousSchema: Object | undefined = undefined
export let pickableProperties: Object | undefined = undefined
let inputCheck: { [id: string]: boolean } = {}
$: isValid = allTrue(inputCheck) ?? false
@@ -103,10 +103,10 @@
{#if propertiesTypes[argName] === undefined || propertiesTypes[argName] === InputTransform.type.STATIC}
<OverlayPropertyPicker
{previousSchema}
bind:pickableProperties
disabled={!hasOverlay(inputCats[argName])}
on:select={(event) => {
const toAppend = `\$\{previous_result.${event.detail}}`
const toAppend = `\$\{${event.detail}}`
args[argName].value = `${args[argName].value ?? ''}${toAppend}`
setPropertyType(argName, args[argName].value, false)
}}
+35 -16
View File
@@ -1,14 +1,9 @@
import type { Schema } from '$lib/common'
import {
FlowModuleValue,
InputTransform,
ScriptService,
type Flow,
type FlowModule
} from '$lib/gen'
import { FlowModuleValue, InputTransform, type Flow, type FlowModule } from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { loadSchema } from '$lib/scripts'
import { emptySchema, getScriptByPath } from '$lib/utils'
import { emptySchema, getScriptByPath, schemaToObject } from '$lib/utils'
import type { FlowMode } from './flowStore'
export function flowToMode(flow: Flow | any, mode: FlowMode): Flow {
@@ -54,15 +49,12 @@ export function getTypeAsString(arg: any): string {
if (arg === null) {
return 'null'
}
if (arg === undefined) {
return 'undefined'
}
return typeof arg
}
export function formatValue(arg: any) {
if (getTypeAsString(arg) === 'string') {
return `"${arg}"`
}
return arg
}
export async function getFirstStepSchema(flow: Flow): Promise<Schema> {
const [firstModule] = flow.value.modules
@@ -79,8 +71,6 @@ export async function getFirstStepSchema(flow: Flow): Promise<Schema> {
return emptySchema()
}
export async function createInlineScriptModuleFromPath(path: string): Promise<FlowModuleValue> {
const { content, language } = await getScriptByPath(path)
@@ -174,3 +164,32 @@ export function getDefaultExpr(i: number, key: string = 'myfield') {
previous_result.${key}`
}
export function getPickableProperties(
schema: Schema,
args: Record<string, any>,
previewResults: Record<number, Object>,
mode: FlowMode,
i: number
) {
const flowInputAsObject = schemaToObject(schema, args)
const flowInput =
mode === 'pull'
? Object.assign(
{
_value: 'The current value of the iteration.',
_index: 'The current index of the iteration.'
},
flowInputAsObject
)
: flowInputAsObject
console.log(previewResults)
const pickableProperties = {
flow_input: flowInput,
previous_result: i === 0 ? flowInput : previewResults[i - 1],
step: i >= 1 ? Object.values(previewResults).slice(0, i) : []
}
return pickableProperties
}
@@ -1,6 +1,7 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { formatValue, getTypeAsString } from '../flows/utils'
import { getTypeAsString } from '../flows/utils'
import WarningMessage from './WarningMessage.svelte'
export let json: Object
export let level = 0
@@ -30,6 +31,9 @@
function computeKey(key: string) {
if (isArray) {
if (currentPath === 'step') {
return `${currentPath}(${key})?`
}
return `${currentPath}[${key}]`
} else {
if (currentPath) {
@@ -47,12 +51,14 @@
{#if keys.length > 0}
<span class:hidden={collapsed}>
<span class="cursor-pointer hover:bg-slate-200" on:click={collapse}>{openBracket}</span>
<ul>
<!-- <span class="cursor-pointer hover:bg-slate-200" on:click={collapse}>{openBracket}</span> -->
<ul class="w-full">
{#each keys as key, index}
<li class={getTypeAsString(json[key]) !== 'object' ? 'hover:bg-sky-100 py-2' : 'py-2'}>
<li class={getTypeAsString(json[key]) !== 'object' ? 'hover:bg-sky-100 pt-1' : 'pt-1'}>
{#if !isArray}
<span class="key">{key}:</span>
<span class="key mr-1">{key}:</span>
{:else}
<span class="key mr-1">{index}:</span>
{/if}
{#if getTypeAsString(json[key]) === 'object'}
@@ -64,23 +70,19 @@
on:select
/>
{:else}
<span class="val {getTypeAsString(json[key])}">
{formatValue(json[key])}
{#if index < keys.length - 1}
<span class="text-black">,</span>
<button class="val {getTypeAsString(json[key])}" on:click={() => selectProp(key)}>
{#if json[key] === undefined}
<WarningMessage />
{:else}
<span> {JSON.stringify(json[key])}</span>
<button class="ml-2 default-button-secondary py-0"> Select </button>
{/if}
<button class="default-button-secondary" on:click={() => selectProp(key)}>
Select
</button>
</span>
</button>
{/if}
</li>
{/each}
</ul>
<span class="cursor-pointer hover:bg-slate-200" on:click={collapse}>{closeBracket}</span>
{#if !isLast}
<span class="text-black">,</span>
{/if}
<!-- <span class="cursor-pointer hover:bg-slate-200" on:click={collapse}>{closeBracket}</span> -->
</span>
<span class="cursor-pointer hover:bg-slate-200" class:hidden={!collapsed} on:click={collapse}>
{openBracket}{collapsedSymbol}{closeBracket}
@@ -88,6 +90,8 @@
{#if !isLast && collapsed}
<span class="text-black">,</span>
{/if}
{:else}
<span class="text-black">{openBracket}{closeBracket}</span>
{/if}
<style>
@@ -104,6 +108,12 @@
.val {
@apply text-black;
}
.val.undefined {
@apply text-red-500;
}
.val.null {
@apply text-red-500;
}
.val.string {
@apply text-lime-600;
}
@@ -1,19 +1,21 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import Overlay from 'svelte-overlay'
import { createPopperActions } from 'svelte-popperjs'
import PropPicker from './PropPicker.svelte'
import WarningMessage from './WarningMessage.svelte'
export let previousSchema: Object | undefined
const [popperRef, popperContent] = createPopperActions({
placement: 'bottom',
strategy: 'fixed'
})
export let pickableProperties: Object | undefined
export let disabled = false
let isOpen = false
const dispatch = createEventDispatcher()
function handleWindowKeyDown(event: { key: string }) {
if (event.key === 'Escape') {
isOpen = false
}
function toggle() {
isOpen = !isOpen
}
function onMouseLeave(mouseEvent: MouseEvent) {
@@ -24,54 +26,44 @@
const up = offsetY <= 0
const content = document.getElementsByClassName('content')
const overlayBottom = content.item(0)?.classList.contains('bottom-right')
const overlayBottom = content.item(0)?.getAttribute('data-popper-placement') === 'bottom'
if ((down && overlayBottom) || (up && !overlayBottom)) {
return
}
isOpen = !isOpen
toggle()
}
</script>
{#if !disabled}
<Overlay
onWindowKeyDown={handleWindowKeyDown}
closeOnClickOutside
closeOnScroll
bind:isOpen
class="w-full"
style="z-index:unset"
>
<div
slot="parent"
let:toggle
on:mousemove={() => !isOpen && toggle()}
on:mouseleave={onMouseLeave}
>
<div class="w-full">
<div use:popperRef on:mousemove={() => !isOpen && toggle()} on:mouseleave={onMouseLeave}>
<slot />
</div>
<div slot="content" class="content" let:toggle on:mouseleave={toggle} let:close>
{#if Boolean(previousSchema)}
{#if isOpen}
<div class="content" use:popperContent on:mouseleave={toggle}>
<PropPicker
props={previousSchema}
bind:pickableProperties
on:select={(event) => {
isOpen = false
dispatch('select', event.detail)
}}
/>
{:else}
<WarningMessage {close} />
{/if}
</div>
</Overlay>
</div>
{/if}
</div>
{:else}
<slot />
{/if}
<style>
.content {
@apply w-full;
@apply drop-shadow-xl;
@apply w-full;
@apply max-w-4xl;
@apply px-6;
@apply z-50;
}
</style>
@@ -1,28 +1,26 @@
<script lang="ts">
import { derived, writable } from 'svelte/store'
import ObjectViewer from './ObjectViewer.svelte'
import { keepByKey } from './utils'
export let props: Object = {}
export let pickableProperties: Object = {}
const EMPTY_STRING = ''
const search = writable(EMPTY_STRING)
let search = ''
$: propsFiltered = derived(search, ($search: string) => {
if ($search === EMPTY_STRING) {
return props
}
return keepByKey(props, $search)
})
$: propsFiltered =
search === EMPTY_STRING ? pickableProperties : keepByKey(pickableProperties, search)
</script>
<div class="relative">
<div class="p-3 bg-white rounded-lg border border-gray-200 shadow-md">
<!-- {JSON.stringify(propsFiltered)} -->
<div
class="relative p-3 bg-white rounded-lg border border-gray-200 shadow-md max-h-max overflow-y-scroll w-full"
>
<div class="overflow-y-auto max-h-96">
<input
bind:value={$search}
bind:value={search}
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg block w-full p-2 mb-2"
placeholder="Search prop..."
/>
<ObjectViewer json={$propsFiltered} on:select />
<ObjectViewer json={propsFiltered} on:select />
</div>
</div>
@@ -1,44 +1,16 @@
<script lang="ts">
export let close: () => void
</script>
<div class="relative">
<div class="p-3 bg-white rounded-lg border border-gray-200 shadow-md">
<div id="alert-4" class="flex p-4 bg-yellow-100 rounded-lg dark:bg-yellow-200" role="alert">
<svg
class="flex-shrink-0 w-5 h-5 text-yellow-700 dark:text-yellow-800"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
><path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
clip-rule="evenodd"
/></svg
>
<div class="ml-3 text-sm font-medium text-yellow-700 dark:text-yellow-800">
The property picker is not available unless a preview is run on the previous step.
</div>
<button
type="button"
class="ml-auto -mx-1.5 -my-1.5 bg-yellow-100 text-yellow-500 rounded-lg focus:ring-2 focus:ring-yellow-400 p-1.5 hover:bg-yellow-200 inline-flex h-8 w-8 dark:bg-yellow-200 dark:text-yellow-600 dark:hover:bg-yellow-300"
data-dismiss-target="#alert-4"
aria-label="Close"
on:click={close}
>
<span class="sr-only">Close</span>
<svg
class="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
><path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
/></svg
>
</button>
</div>
<div class="flex p-4 bg-yellow-100 rounded-lg dark:bg-yellow-200" role="alert">
<svg
class="flex-shrink-0 w-5 h-5 text-yellow-700 dark:text-yellow-800"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
><path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
clip-rule="evenodd"
/></svg
>
<div class="ml-3 text-sm font-medium text-yellow-700 dark:text-yellow-800">
Previous results are not available unless a preview is run on the previous step.
</div>
</div>
+1 -1
View File
@@ -58,7 +58,7 @@ export async function main() {
// return newState - state
// You may refer to each row/value returned by the trigger script using
// previous_result._value
// flow_input._value
return [1,2,3]
}
`
-7
View File
@@ -58,10 +58,3 @@ export function clearPreviewResults() {
previewResults.set({})
}
export function addPreviewResult(res: Object, index: number) {
previewResults.update((rec) => {
rec[index] = res
return rec
})
}
+2 -3
View File
@@ -339,7 +339,7 @@ export function schemaToTsType(schema: Schema): string {
return `{ ${types} }`
}
export function schemaToObject(schema: Schema): Object {
export function schemaToObject(schema: Schema, args: Record<string, any>): Object {
const object = {}
if (!schema) {
@@ -348,8 +348,7 @@ export function schemaToObject(schema: Schema): Object {
const propKeys = Object.keys(schema.properties)
propKeys.forEach((key: string) => {
const prop = schema.properties[key]
object[key] = prop.type
object[key] = args[key] ?? null
})
return object