fix(frontend): Update flow viewer styling (#1441)

* fix(frontend): Update flow viewer styling

* update
This commit is contained in:
Ádám Kovács
2023-04-20 16:35:45 +02:00
committed by GitHub
parent fe75aa18f2
commit 46a29b5d27
7 changed files with 200 additions and 126 deletions
@@ -5,7 +5,7 @@
import IconedPath from './IconedPath.svelte'
import { scriptPathToHref } from '../utils'
import type { FlowModule, FlowValue } from '$lib/gen'
import { Drawer, DrawerContent } from './common'
import { Badge, Button, Drawer, DrawerContent } from './common'
import { Highlight } from 'svelte-highlight'
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
import typescript from 'svelte-highlight/languages/typescript'
@@ -46,7 +46,7 @@
</div>
{#if stepDetail.value.path.startsWith('hub/')}
<div class="mt-6">
<h3>Code</h3>
<h3 class="mb-2">Code</h3>
<iframe
class="w-full h-full text-sm"
title="embedded script from hub"
@@ -69,11 +69,11 @@
{/if}
</DrawerContent>
</Drawer>
<div class="grid grid-cols-3 w-full">
<div class="grid grid-cols-3 w-full h-full">
<div
class="{noSide
? 'col-span-3'
: 'sm:col-span-2 col-span-3'} w-full border border-gray-400 max-h-screen"
: 'sm:col-span-2 col-span-3'} w-full border border-gray-400 max-h-full"
class:overflow-auto={overflowAuto}
>
<FlowGraph
@@ -86,63 +86,101 @@
</div>
{#if !noSide}
<div
class="w-full border-r border-b border-t border-gray-400 min-h-[150px] p-2 overflow-auto hidden sm:block"
class="relative w-full h-full min-h-[150px] max-h-[90vh] border-r border-b border-t border-gray-400
p-2 pt-0 overflow-auto hidden sm:flex flex-col gap-4"
>
{#if stepDetail == undefined}
<SchemaViewer schema={flow?.schema} />
<span class="font-black text-lg w-full my-4 mt-14">
<span>Click on a step to see its details</span>
</span>
<div>
<p class="font-medium text-gray-600 text-center pt-4 pb-8">
Click on a step to see its details
</p>
<h3 class="mb-2 font-semibold">Flow Inputs</h3>
<SchemaViewer schema={flow?.schema} />
</div>
{:else if stepDetail == 'Input'}
<SchemaViewer schema={flow?.schema} />
{:else if stepDetail == 'Result'}
End of the flow
<p class="font-medium text-gray-600 text-center pt-4 pb-8"> End of the flow </p>
{:else if typeof stepDetail != 'string' && stepDetail.value}
<div class="font-black text-lg w-full mb-6"
>Step {stepDetail.id ?? ''}<span class="ml-2 font-normal">{stepDetail.summary || ''}</span
></div
>
<div class="">
<div class="sticky top-0 bg-white w-full flex items-center py-2">
{#if stepDetail.id}
<Badge color="indigo">
{stepDetail.id}
</Badge>
{/if}
<span class="ml-2 font-medium text-lg">
{#if stepDetail.summary}
{stepDetail.summary}
{:else if stepDetail.value.type == 'identity'}
Identity
{:else if stepDetail.value.type == 'forloopflow'}
For loop
{:else if stepDetail.value.type == 'branchall'}
Run all branches
{:else if stepDetail.value.type == 'branchone'}
Run one branch
{:else if stepDetail.value.type == 'flow'}
Inner flow
{:else}
Anonymous step
{/if}
</span>
</div>
{#if stepDetail.value.type == 'script'}
<div class="pb-2">
<a
rel="noreferrer"
target="_blank"
href={scriptPathToHref(stepDetail?.value?.path ?? '')}
class=""
>
<IconedPath path={stepDetail?.value?.path ?? ''} />
</a>
</div>
{/if}
</div>
{#if stepDetail.value.type == 'identity'}
<div> An identity step return as output its input </div>
<p class="font-medium text-gray-600 text-center pt-4 pb-8">
An identity step returns its inputs as outputs
</p>
{:else if stepDetail.value.type == 'rawscript'}
<div class="text-2xs mb-4">
<h3 class="mb-2">Step Inputs</h3>
<div class="text-xs">
<h3 class="mb-2 font-semibold">Step Inputs</h3>
<InputTransformsViewer inputTransforms={stepDetail?.value?.input_transforms ?? {}} />
</div>
<h3 class="mb-2"
>Code &nbsp; <button class="text-gray-600 text-xs" on:click={codeViewer.openDrawer}
>Expand</button
>
</h3>
<span class="!text-xs">
<HighlightCode language={stepDetail.value.language} code={stepDetail.value.content} />
</span>
{:else if stepDetail.value.type == 'script'}
<div class="mb-4">
<a
rel="noreferrer"
target="_blank"
href={scriptPathToHref(stepDetail?.value?.path ?? '')}
class=""
>
<IconedPath path={stepDetail?.value?.path ?? ''} />
</a>
<div>
<div class="mb-2 flex justify-between items-center">
<h3 class="font-semibold">Code</h3>
<Button size="xs2" color="light" variant="contained" on:click={codeViewer.openDrawer}>
Expand
</Button>
</div>
<div class="w-full overflow-auto">
<HighlightCode language={stepDetail.value.language} code={stepDetail.value.content} />
</div>
</div>
<div class="text-2xs mb-4">
<h3 class="mb-2">Step Inputs</h3>
{:else if stepDetail.value.type == 'script'}
<div class="text-2xs">
<h3 class="mb-2 font-semibold">Step Inputs</h3>
<InputTransformsViewer inputTransforms={stepDetail?.value?.input_transforms ?? {}} />
</div>
{#if stepDetail.value.path.startsWith('hub/')}
<div class="mt-6">
<h3
>Code&nbsp; <button class="text-gray-600 text-xs" on:click={codeViewer.openDrawer}
>Expand</button
<div class="flex flex-col grow">
<div class="mb-2 flex justify-between items-center">
<h3 class="font-semibold">Code</h3>
<Button
size="xs2"
color="light"
variant="contained"
on:click={codeViewer.openDrawer}
>
</h3>
Expand
</Button>
</div>
<iframe
class="w-full h-full text-sm"
class="w-full grow text-sm"
title="embedded script from hub"
frameborder="0"
src="https://hub.windmill.dev/embed/script/{stepDetail.value?.path?.substring(4)}"
@@ -150,19 +188,24 @@
</div>
{/if}
{:else if stepDetail.value.type == 'forloopflow'}
<p
>For loop with iterator: {#if stepDetail.value.iterator.type == 'static'}<ObjectViewer
json={stepDetail.value.iterator.value}
/>{:else}
<div>
<p class="font-medium text-gray-600 pb-2"> Iterator expression: </p>
{#if stepDetail.value.iterator.type == 'static'}
<ObjectViewer json={stepDetail.value.iterator.value} />
{:else}
<span class="text-xs">
<Highlight language={typescript} code={cleanExpr(stepDetail.value.iterator.expr)} />
</span>
{/if}</p
>
{/if}
</div>
{:else if stepDetail.value.type == 'branchall'}
<p>Run all branches</p>
<p class="font-medium text-gray-600 text-center pt-4 pb-8">
All branches will run, regardless of the inputs
</p>
{:else if stepDetail.value.type == 'branchone'}
<p>Run one branch based on a predicate</p>
<p class="font-medium text-gray-600 text-center pt-4 pb-8">
Only one branch will run based on a predicate
</p>
{:else if stepDetail.value.type == 'flow'}
<FlowPathViewer noSide path={stepDetail.value.path} />
{/if}
+16 -3
View File
@@ -8,6 +8,15 @@
import FieldHeader from './FieldHeader.svelte'
import { copyToClipboard } from '../utils'
import FlowGraphViewer from './FlowGraphViewer.svelte'
import { setContext } from 'svelte'
import {
SVELVET_CONTEXT_KEY,
type SvelvetSettingsContext
} from './graph/svelvet/container/models'
setContext<SvelvetSettingsContext>(SVELVET_CONTEXT_KEY, {
fullHeight: true
})
export let flow: {
summary: string
@@ -40,7 +49,7 @@
<Tabs bind:selected={tab}>
<Tab value="ui">Graph</Tab>
<Tab value="json">Json</Tab>
<Tab value="json">JSON</Tab>
<Tab value="schema">Input Schema</Tab>
<svelte:fragment slot="content">
@@ -79,7 +88,7 @@
</div>
</TabContent>
<TabContent value="json">
<div class="relative">
<div class="relative pt-2">
<Button
on:click={() => copyToClipboard(JSON.stringify(flowFiltered, null, 4))}
color="dark"
@@ -90,7 +99,11 @@
>
Copy content
</Button>
<Highlight language={json} code={JSON.stringify(flowFiltered, null, 4)} />
<Highlight
language={json}
code={JSON.stringify(flowFiltered, null, 4)}
class="overflow-auto"
/>
</div>
</TabContent>
<TabContent value="schema">
@@ -1,23 +1,27 @@
<script lang="ts">
import type { InputTransform } from '$lib/gen'
import { Highlight } from 'svelte-highlight'
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
import typescript from 'svelte-highlight/languages/typescript'
import { cleanExpr } from './flows/utils'
export let inputTransforms: Record<string, InputTransform>
$: entries = Object.entries(inputTransforms)
</script>
<ul class="mb-1">
{#each Object.entries(inputTransforms) as [key, val]}
<li>
<span class="font-black text-gray-700">{key}</span>: {#if val.type == 'static'}<ObjectViewer
json={val.value}
/>{:else}
<span class="text-xs">
<Highlight language={typescript} code={cleanExpr(val.expr)} />
</span>
{/if}
</li>
{/each}
</ul>
{#if entries.length}
<ul class="mb-1">
{#each entries as [key, val]}
<li class="flex pb-2 last:pb-0">
<span class="font-black text-gray-700 text-xs">{key}:</span>
{#if val.type == 'static'}
<ObjectViewer json={val.value} />
{:else}
<span class="text-xs text-black whitespace-pre-wrap ml-2">
{cleanExpr(val.expr)}
</span>
{/if}
</li>
{/each}
</ul>
{:else}
<div class="text-gray-600 text-sm"> No inputs </div>
{/if}
+54 -52
View File
@@ -18,58 +18,60 @@
<Tab value="arguments">Arguments</Tab>
<Tab value="advanced">Advanced</Tab>
<svelte:fragment slot="content">
<TabContent value="arguments">
{#if schema && schema.properties && Object.keys(schema.properties).length > 0 && schema.required}
<div class="flex flex-row">
<TableCustom>
<tr slot="header-row" class="underline">
<th>name</th>
<th>type</th>
<th>description</th>
<th>default</th>
<th>format</th>
<th>required</th>
</tr>
<tbody slot="body">
{#each Object.entries(schema.properties) as [name, property] (name)}
<tr>
<td class="font-semibold pl-1">{name}</td>
<td
><Badge color="blue"
>{#if !property.type} any {:else} {property.type} {/if}</Badge
></td
>
<td>{property.description ?? ''}</td>
<td
>{property.default == '<function call>'
? '<function call>'
: property.default
? JSON.stringify(property.default)
: ''}</td
>
<td
>{property.format ?? ''}
{property.contentEncoding
? `(encoding: ${property.contentEncoding})`
: ''}</td
>
<td
>{#if schema.required.includes(name)}
<span class="text-red-600 font-bold text-lg">*</span>
{/if}</td
>
</tr>
{/each}
</tbody>
</TableCustom>
</div>
{:else}
<div class="text-gray-700 text-xs italic m-1">No arguments</div>
{/if}
</TabContent>
<TabContent value="advanced">
<Highlight language={json} code={JSON.stringify(schema, null, 4)} />
</TabContent>
<div class="overflow-auto pt-2">
<TabContent value="arguments">
{#if schema && schema.properties && Object.keys(schema.properties).length > 0 && schema.required}
<div class="flex flex-row">
<TableCustom>
<tr slot="header-row" class="underline">
<th>name</th>
<th>type</th>
<th>description</th>
<th>default</th>
<th>format</th>
<th>required</th>
</tr>
<tbody slot="body">
{#each Object.entries(schema.properties) as [name, property] (name)}
<tr>
<td class="font-semibold pl-1">{name}</td>
<td
><Badge color="blue"
>{#if !property.type} any {:else} {property.type} {/if}</Badge
></td
>
<td>{property.description ?? ''}</td>
<td
>{property.default == '<function call>'
? '<function call>'
: property.default
? JSON.stringify(property.default)
: ''}</td
>
<td
>{property.format ?? ''}
{property.contentEncoding
? `(encoding: ${property.contentEncoding})`
: ''}</td
>
<td
>{#if schema.required.includes(name)}
<span class="text-red-600 font-bold text-lg">*</span>
{/if}</td
>
</tr>
{/each}
</tbody>
</TableCustom>
</div>
{:else}
<div class="text-gray-700 text-xs italic m-1">No arguments</div>
{/if}
</TabContent>
<TabContent value="advanced">
<Highlight language={json} code={JSON.stringify(schema, null, 4)} />
</TabContent>
</div>
</svelte:fragment>
</Tabs>
</div>
@@ -0,0 +1,6 @@
export const SVELVET_CONTEXT_KEY = 'svelvet_settings_context' as const
export type SvelvetSettingsContext = {
/** Sets the height of the canvas to `100%` instead of an arbitrary value in pixels */
fullHeight: boolean
}
@@ -6,9 +6,12 @@
createStoreEmpty,
populateSvelvetStoreFromUserInput
} from '../../store/controllers/storeApi'
import { afterUpdate, onMount } from 'svelte'
import { afterUpdate, onMount, getContext } from 'svelte'
import GraphView from './GraphView.svelte'
import { sanitizeCanvasOptions, sanitizeUserNodesAndEdges } from '../controllers/middleware'
import { SVELVET_CONTEXT_KEY, type SvelvetSettingsContext } from '../models'
const settings = getContext<SvelvetSettingsContext | undefined>(SVELVET_CONTEXT_KEY)
export let nodes: UserNodeType[]
export let edges: UserEdgeType[]
@@ -29,6 +32,7 @@
export let scroll: boolean = false
export let download: boolean = false
const fullHeight = settings?.fullHeight ?? false
// generates a unique string for each svelvet component's unique store instance
// creates a store that uses the unique sting as the key to create and look up the corresponding store
// this way we can have multiple Svelvet Components on the same page and prevent overlap of information
@@ -109,7 +113,9 @@
<!-- Now that a store has been created from the initial nodes and initial edges we drill props from the store down to the D3 GraphView along with the unique key -->
<div
class="Svelvet"
style={`width: ${width}px; height: ${height}px; background-color: ${bgColor};`}
style={`width: ${width}px; height: ${
fullHeight ? '100%' : height + 'px'
}; background-color: ${bgColor};`}
>
{#if error != ''}
<div class="error text-red-600 center-center p-4">{error}</div>
@@ -65,7 +65,7 @@
}`}
>
{#each keys.length > keyLimit ? keys.slice(0, keyLimit) : keys as key, index (key)}
<li class="pt-1">
<li class="pb-1 last-of-type:pb-0">
<button on:click={() => selectProp(key, key)} class="whitespace-nowrap">
{#if topLevelNode}
<Badge baseClass="border border-blue-600" color="indigo">{key}</Badge>
@@ -136,7 +136,7 @@
{:else if topBrackets}
<span class="text-black">{openBracket}{closeBracket}</span>
{:else}
<span class="text-gray-600 text-xs ml-2">No items</span>
<span class="text-gray-500 text-xs ml-2">No items</span>
{/if}
<style lang="postcss">