feat(frontend): Open/Close UI (#445)

* feat(frontend): Properly open/close and highlight a flowbox

* feat(frontend): Fix removing first step
This commit is contained in:
Faton Ramadani
2022-08-17 17:24:57 +02:00
committed by GitHub
parent 068147251c
commit 7e4aac9971
7 changed files with 177 additions and 162 deletions

View File

@@ -15,7 +15,8 @@
fork,
loadFlowModuleSchema,
pickScript,
createScriptFromInlineScript
createScriptFromInlineScript,
isEmptyFlowModule
} from './flows/flowStateUtils'
import { jobsToResults } from './flows/utils'
import SchemaForm from './SchemaForm.svelte'
@@ -67,7 +68,7 @@
index: "iteration's index"
}
if (hasElements(stepBeforeLoop.previewResults)) {
if (hasElements(stepBeforeLoop?.previewResults)) {
const lastResults = getLast(stepBeforeLoop.previewResults)
if (hasElements(lastResults)) {
@@ -89,7 +90,7 @@
}
}
$: shouldPick = 'path' in mod.value && mod.value.path === '' && !('language' in mod.value)
$: shouldPick = isEmptyFlowModule(mod)
$: pickableProperties = getPickableProperties($flowStore, $flowStateStore)
$: extraLib = buildExtraLib(
schemaToTsType($flowStore?.schema),
@@ -140,12 +141,11 @@
$: opened = $stepOpened === String(indexes.join('-'))
</script>
<FlowBox>
<FlowBox bind:opened>
<svelte:fragment slot="header">
<FlowModuleHeader
{mod}
bind:mod
bind:indexes
{shouldPick}
on:delete
on:fork={() => apply(fork, mod)}
on:createScriptFromInlineScript={() => {
@@ -160,83 +160,75 @@
<div slot="content">
{#if opened}
{#if shouldPick}
<FlowInputs
shouldDisableTriggerScripts={indexes[0] > 0}
shouldDisableLoopCreation={indexes.length > 1 || indexes[0] == 0}
on:pick={(e) => apply(pickScript, e.detail.path)}
on:new={(e) =>
apply(createInlineScriptModule, {
language: e.detail.language,
isTrigger: e.detail.isTrigger
})}
on:loop={() => applyCreateLoop()}
/>
{/if}
{#if mod.value.type === 'rawscript'}
<div class="mb-2 overflow-hidden">
<EditorBar {editor} {websocketAlive} lang={mod.value.language ?? 'deno'} />
</div>
<div on:mouseleave={() => reload(mod)}>
<Editor
bind:websocketAlive
bind:this={editor}
class="{bigEditor ? 'h-2/3' : 'h-80'} border p-2 rounded"
bind:code={mod.value.content}
deno={mod.value.language === RawScript.language.DENO}
automaticLayout={true}
on:blur={() => reload(mod)}
formatAction={() => reload(mod)}
<div class="p-6 border-t border-gray-300">
{#if shouldPick}
<FlowInputs
shouldDisableTriggerScripts={i != 0}
shouldDisableLoopCreation={indexes.length > 1 || i == 0}
on:pick={(e) => apply(pickScript, e.detail.path)}
on:new={(e) =>
apply(createInlineScriptModule, {
language: e.detail.language,
isTrigger: e.detail.isTrigger
})}
on:loop={() => applyCreateLoop()}
/>
<button
class="w-full text-center"
on:click={() => {
bigEditor = !bigEditor
}}
>
<Icon data={bigEditor ? faChevronUp : faChevronDown} scale={1.0} />
</button>
</div>
<div class="mt-2 mb-8">
<p class="text-gray-500 italic">
Move the focus outside of the text editor to recompute the input schema or press
Ctrl/Cmd+S
</p>
</div>
{/if}
{#if !shouldPick}
<p class="text-lg font-bold text-gray-900 mb-2">Step inputs</p>
<SchemaForm
{schema}
{extraLib}
inputTransform={true}
importPath={String(indexes.join('-'))}
bind:pickableProperties
bind:args={mod.input_transform}
/>
{/if}
{#if !shouldPick}
<div class="border-b border-gray-200" />
<div class="p-3">
<FlowPreview
bind:args
flow={$flowStore}
{i}
{/if}
{#if mod.value.type === 'rawscript'}
<div class="mb-2 overflow-hidden">
<EditorBar {editor} {websocketAlive} lang={mod.value.language ?? 'deno'} />
</div>
<div on:mouseleave={() => reload(mod)}>
<Editor
bind:websocketAlive
bind:this={editor}
class="{bigEditor ? 'h-2/3' : 'h-80'} border p-2 rounded"
bind:code={mod.value.content}
deno={mod.value.language === RawScript.language.DENO}
automaticLayout={true}
on:blur={() => reload(mod)}
formatAction={() => reload(mod)}
/>
<button
class="w-full text-center"
on:click={() => {
bigEditor = !bigEditor
}}
>
<Icon data={bigEditor ? faChevronUp : faChevronDown} scale={1.0} />
</button>
</div>
<div class="mt-2 mb-8">
<p class="text-gray-500 italic">
Move the focus outside of the text editor to recompute the input schema or press
Ctrl/Cmd+S
</p>
</div>
{/if}
{#if !shouldPick}
<p class="text-lg font-bold text-gray-900 mb-2">Step inputs</p>
<SchemaForm
{schema}
on:change={(e) => onPreview(e.detail)}
{extraLib}
inputTransform={true}
importPath={String(indexes.join('-'))}
bind:pickableProperties
bind:args={mod.input_transform}
/>
</div>
{/if}
{:else}
<div class="flex flex-col justify-center w-full">
<button
on:click={() => {
stepOpened.update(() => String(indexes.join('-')))
}}
>
<Icon data={faArrowDown} />
</button>
{/if}
{#if !shouldPick}
<div class="border-b border-gray-200" />
<div class="p-3">
<FlowPreview
bind:args
flow={$flowStore}
{i}
{schema}
on:change={(e) => onPreview(e.detail)}
/>
</div>
{/if}
</div>
{/if}
</div>

View File

@@ -4,13 +4,11 @@
let slots = $$props.$$slots
</script>
<div class="bg-white border border-gray-300 rounded-md shadow-md ">
<div class="bg-white border border-gray-300 rounded-md">
<FlowBoxHeader {title}>
<slot name="header" />
</FlowBoxHeader>
{#if slots.content}
<div class="p-6 ">
<slot name="content" />
</div>
<slot name="content" />
{/if}
</div>

View File

@@ -2,7 +2,7 @@
export let title: string | undefined = undefined
</script>
<div class="flex items-center justify-between flex-wra px-6 py-2 border-b border-gray-300">
<div class="flex items-center justify-between flex-wra px-6 py-2 ">
{#if title}
<h3 class="text-sm font-bold text-gray-900">{title}</h3>
{/if}

View File

@@ -10,11 +10,13 @@
<CopyFirstStepSchema />
</div>
<div slot="content">
<SchemaEditor
on:change={() => {
$flowStore = $flowStore
}}
schema={$flowStore.schema}
/>
<div class="p-6 border-t border-gray-300">
<SchemaEditor
on:change={() => {
$flowStore = $flowStore
}}
schema={$flowStore.schema}
/>
</div>
</div>
</FlowBox>

View File

@@ -1,7 +1,14 @@
<script lang="ts">
import type { FlowModule } from '$lib/gen'
import { getScriptByPath } from '$lib/utils'
import { faCode, faCodeBranch, faSave, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import {
faArrowDown,
faClose,
faCode,
faCodeBranch,
faSave,
faTrashAlt
} from '@fortawesome/free-solid-svg-icons'
import { Button } from 'flowbite-svelte'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
@@ -9,13 +16,14 @@
import python from 'svelte-highlight/languages/python'
import typescript from 'svelte-highlight/languages/typescript'
import Modal from '../Modal.svelte'
import Tooltip from '../Tooltip.svelte'
import { scrollIntoView } from './utils'
import { isEmptyFlowModule } from './flowStateUtils'
import { stepOpened } from './stepOpenedStore'
export let indexes: number[]
export let shouldPick = false
export let mod: FlowModule
$: shouldPick = isEmptyFlowModule(mod)
let modalViewer: Modal
let modalViewerContent = ''
let modalViewerLanguage: 'deno' | 'python3' = 'deno'
@@ -31,15 +39,11 @@
}
}
function scrollTo({ target }) {
const element = document.querySelector(target.getAttribute('href'))
scrollIntoView(element)
}
const dispatch = createEventDispatcher()
$: opened = $stepOpened === String(indexes.join('-'))
</script>
<a href="#module-{indexes.join('-')}" class="grow text-inherit" on:click|preventDefault={scrollTo}>
<div on:click={() => stepOpened.update(() => String(indexes.join('-')))}>
<h3 class="text-sm font-bold text-gray-900">
{#if 'path' in mod.value && mod.value.path}
{mod.value.path}
@@ -49,7 +53,7 @@
Select a script
{/if}
</h3>
</a>
</div>
<div class="flex flex-row space-x-2">
{#if mod.value.type === 'script' && !shouldPick}
@@ -79,6 +83,19 @@
<Icon data={faTrashAlt} class="mr-2" />
Remove step
</Button>
{#if opened}
<Button size="xs" color="dark" on:click={() => stepOpened.update(() => undefined)}>
<Icon data={faClose} />
</Button>
{:else}
<Button
size="xs"
color="light"
on:click={() => stepOpened.update(() => String(indexes.join('-')))}
>
<Icon data={faArrowDown} />
</Button>
{/if}
</div>
<Modal bind:this={modalViewer}>

View File

@@ -103,60 +103,63 @@
</div>
<div slot="content">
<Path
bind:error={pathError}
bind:path={$flowStore.path}
{initialPath}
namePlaceholder="my_flow"
kind="flow"
>
<div slot="ownerToolkit">
Flow permissions depend on their path. Select the group <span class="font-mono">all</span>
to share your flow, and <span class="font-mono">user</span> to keep it private.
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
</div>
</Path>
<div class="p-6 border-t border-gray-300">
<Path
bind:error={pathError}
bind:path={$flowStore.path}
{initialPath}
namePlaceholder="my_flow"
kind="flow"
>
<div slot="ownerToolkit">
Flow permissions depend on their path. Select the group <span class="font-mono">all</span>
to share your flow, and <span class="font-mono">user</span> to keep it private.
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
</div>
</Path>
<label class="block mt-4">
<span class="text-gray-700">Summary <Required required={false} /></span>
<textarea
bind:value={$flowStore.summary}
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
placeholder="A very short summary of the flow displayed when the flow is listed"
rows="1"
/>
</label>
<label class="block mt-4">
<span class="text-gray-700">Summary <Required required={false} /></span>
<textarea
bind:value={$flowStore.summary}
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
placeholder="A very short summary of the flow displayed when the flow is listed"
rows="1"
/>
</label>
<CollapseLink text="set primary schedule" open={true}>
<Tooltip>
The primary schedule of a flow is simply a schedule that has the same name as a flow. It can
be set and enabled directly within the flow editor. "Watching for new changes" flows are
meant to be watching regularly for new items in an external systems. The primary schedule
purpose is there to set the periodicity at which you want this watcher to operate.
</Tooltip>
<Toggle
bind:checked={scheduleEnabled}
options={{
left: 'disabled',
right: 'enabled'
}}
/>
<div class="p-2 my-2 rounded" class:bg-gray-300={!scheduleEnabled}>
{#if !scheduleEnabled}
<span class="font-black">No next scheduled run when disabled</span>
{/if}
<CronInput bind:schedule={scheduleCron} />
</div>
<div class="flex flex-row-reverse">
<Button
color="alternative"
size="sm"
on:click={() => (scheduleArgs = JSON.parse(JSON.stringify(previewArgs)))}
>
Copy from preview arguments
</Button>
</div>
<SchemaForm schema={$flowStore.schema} bind:args={scheduleArgs} />
</CollapseLink>
<CollapseLink text="set primary schedule" open={true}>
<Tooltip>
The primary schedule of a flow is simply a schedule that has the same name as a flow. It
can be set and enabled directly within the flow editor. "Watching for new changes" flows
are meant to be watching regularly for new items in an external systems. The primary
schedule purpose is there to set the periodicity at which you want this watcher to
operate.
</Tooltip>
<Toggle
bind:checked={scheduleEnabled}
options={{
left: 'disabled',
right: 'enabled'
}}
/>
<div class="p-2 my-2 rounded" class:bg-gray-300={!scheduleEnabled}>
{#if !scheduleEnabled}
<span class="font-black">No next scheduled run when disabled</span>
{/if}
<CronInput bind:schedule={scheduleCron} />
</div>
<div class="flex flex-row-reverse">
<Button
color="alternative"
size="sm"
on:click={() => (scheduleArgs = JSON.parse(JSON.stringify(previewArgs)))}
>
Copy from preview arguments
</Button>
</div>
<SchemaForm schema={$flowStore.schema} bind:args={scheduleArgs} />
</CollapseLink>
</div>
</div>
</FlowBox>

View File

@@ -47,10 +47,13 @@ export function getTypeAsString(arg: any): string {
export function scrollIntoView(element: any) {
if (!element) return
element.scrollIntoView({
behavior: 'smooth',
block: 'start',
inline: 'nearest'
var headerOffset = 45
var elementPosition = element.getBoundingClientRect().top
var offsetPosition = elementPosition + window.pageYOffset - headerOffset
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
})
}
@@ -121,8 +124,9 @@ export function getDefaultExpr(
previousExpr?: string
) {
const expr = previousExpr ?? `previous_result.${key}`
return `import { previous_result, flow_input, step, variable, resource, params } from 'windmill${importPath ? `@${importPath}` : ''
}'
return `import { previous_result, flow_input, step, variable, resource, params } from 'windmill${
importPath ? `@${importPath}` : ''
}'
${expr}`
}
@@ -187,7 +191,6 @@ export async function runFlowPreview(args: Record<string, any>, flow: Flow) {
})
}
export function codeToStaticTemplate(code?: string): string | undefined {
if (!code) return undefined