mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 08:02:26 +00:00
fix(frontend): Update flow viewer styling (#1441)
* fix(frontend): Update flow viewer styling * update
This commit is contained in:
@@ -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 <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 <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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user