mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 08:02:40 +00:00
feat(front): Rework how summaries are edited in the flow editor (#632)
* feat(front): Rework how summaries are edited in the flow editor * feat(front): add IconOnly mode, to better handle responsivness * feat(front): Fix FlowModule summary input width * feat(front): simplify summary placeholder * feat(front): Fix minimap resizing issues + fix minimap delete
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
export let lang: 'python3' | 'deno' | 'go'
|
||||
export let editor: Editor
|
||||
export let websocketAlive: { pyright: boolean; black: boolean; deno: boolean }
|
||||
export let iconOnly: boolean = false
|
||||
|
||||
let variablePicker: ItemPicker
|
||||
let resourcePicker: ItemPicker
|
||||
@@ -192,6 +193,7 @@
|
||||
size="xs"
|
||||
spacingSize="md"
|
||||
startIcon={{ icon: faFile }}
|
||||
{iconOnly}
|
||||
>
|
||||
Insert variable
|
||||
</Button>
|
||||
@@ -203,8 +205,9 @@
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={resourcePicker.openModal}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faCube }}
|
||||
>
|
||||
<Icon data={faCube} class="mr-2" />
|
||||
Insert resource
|
||||
</Button>
|
||||
</div>
|
||||
@@ -216,8 +219,9 @@
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={scriptPicker.openModal}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faCode }}
|
||||
>
|
||||
<Icon data={faCode} class="mr-2" />
|
||||
Search script
|
||||
</Button>
|
||||
</div>
|
||||
@@ -229,8 +233,9 @@
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.clearContent}
|
||||
{iconOnly}
|
||||
startIcon={{ icon: faRotateLeft }}
|
||||
>
|
||||
<Icon data={faRotateLeft} class="mr-2" />
|
||||
Reset content
|
||||
</Button>
|
||||
</div>
|
||||
@@ -241,16 +246,14 @@
|
||||
spacingSize="md"
|
||||
color="light"
|
||||
on:click={editor.reloadWebsocket}
|
||||
startIcon={{ icon: faRotate }}
|
||||
>
|
||||
<Icon data={faRotate} class="h-4 w-4 mr-2" />
|
||||
|
||||
Reload assistants
|
||||
<span class="ml-1">
|
||||
{#if lang == 'deno'}
|
||||
(<span class={websocketAlive.deno ? 'text-green-600' : 'text-red-700'}>Deno</span>)
|
||||
{:else if lang == 'python3'}
|
||||
(<span class={websocketAlive.pyright ? 'text-green-600' : 'text-red-700'}>Pyright</span>
|
||||
|
||||
<span class={websocketAlive.black ? 'text-green-600' : 'text-red-700'}>Black</span>)
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import IconedResourceType from './IconedResourceType.svelte'
|
||||
|
||||
export let path: string
|
||||
</script>
|
||||
|
||||
{#if path.startsWith('hub/')}
|
||||
<span class="inline-flex flex-row gap-x-2 items-center">
|
||||
<div class="flex space-x-2 items-center">
|
||||
{#if path.startsWith('hub/')}
|
||||
<IconedResourceType name={path.split('/')[2]} silent={true} />
|
||||
<span>{path}</span>
|
||||
</span>
|
||||
{:else}
|
||||
{path}
|
||||
{/if}
|
||||
{:else}
|
||||
<Badge color="blue">Workspace</Badge>
|
||||
<span>{path}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
classes: 'animate-spin'
|
||||
}}
|
||||
>
|
||||
'Cancel'
|
||||
Cancel
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -101,14 +101,15 @@
|
||||
<LogViewer content={testJob?.logs} isLoading={testIsLoading} />
|
||||
</top>
|
||||
<down slot="down">
|
||||
<pre
|
||||
class="overflow-x-auto break-all relative h-full p-2 text-sm">{#if testJob && 'result' in testJob && testJob.result}<DisplayResult
|
||||
result={testJob.result}
|
||||
/>
|
||||
{:else if testIsLoading}Waiting for Result...
|
||||
{:else}Test to see result here
|
||||
<pre class="overflow-x-auto break-all relative h-full p-2 text-sm">
|
||||
{#if testJob && 'result' in testJob && testJob.result}
|
||||
<DisplayResult result={testJob.result} />
|
||||
{:else if testIsLoading}
|
||||
Waiting for Result...
|
||||
{:else}
|
||||
Test to see result here
|
||||
{/if}
|
||||
</pre>
|
||||
</pre>
|
||||
</down>
|
||||
</VSplitPane>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
export let disabled: boolean = false
|
||||
export let href: string | undefined = undefined
|
||||
export let target: Button.Target = '_self'
|
||||
export let iconOnly: boolean = false
|
||||
|
||||
export let startIcon: { icon: any; classes?: string } | undefined = undefined
|
||||
export let endIcon: { icon: any; classes?: string } | undefined = undefined
|
||||
@@ -70,7 +71,7 @@
|
||||
spacingClasses[spacingSize],
|
||||
'focus:ring-4 font-medium',
|
||||
'rounded-md',
|
||||
'flex justify-center items-center text-center',
|
||||
'flex justify-center items-center text-center whitespace-nowrap',
|
||||
btnClasses,
|
||||
disabled ? 'pointer-events-none cursor-default filter grayscale' : ''
|
||||
),
|
||||
@@ -83,15 +84,17 @@
|
||||
{#if startIcon}
|
||||
<Icon
|
||||
data={startIcon.icon}
|
||||
class={classNames('mr-2', startIcon.classes)}
|
||||
class={classNames(iconOnly ? undefined : 'mr-2', startIcon.classes)}
|
||||
scale={iconScale[size]}
|
||||
/>
|
||||
{/if}
|
||||
<slot />
|
||||
{#if !iconOnly}
|
||||
<slot />
|
||||
{/if}
|
||||
{#if endIcon}
|
||||
<Icon
|
||||
data={endIcon.icon}
|
||||
class={classNames('ml-2', endIcon.classes)}
|
||||
class={classNames(iconOnly ? undefined : 'ml-2', endIcon.classes)}
|
||||
scale={iconScale[size]}
|
||||
/>
|
||||
{/if}
|
||||
@@ -101,15 +104,17 @@
|
||||
{#if startIcon}
|
||||
<Icon
|
||||
data={startIcon.icon}
|
||||
class={classNames('mr-2', startIcon.classes)}
|
||||
class={classNames(iconOnly ? undefined : 'mr-2', startIcon.classes)}
|
||||
scale={iconScale[size]}
|
||||
/>
|
||||
{/if}
|
||||
<slot />
|
||||
{#if !iconOnly}
|
||||
<slot />
|
||||
{/if}
|
||||
{#if endIcon}
|
||||
<Icon
|
||||
data={endIcon.icon}
|
||||
class={classNames('ml-2', endIcon.classes)}
|
||||
class={classNames(iconOnly ? undefined : 'ml-2', endIcon.classes)}
|
||||
scale={iconScale[size]}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<FlowEditorHeader />
|
||||
|
||||
<div class="h-full overflow-hidden">
|
||||
<HSplitPane leftPaneSize="25%" rightPaneSize="75%" minLeftPaneSize="20%" minRightPaneSize="20%">
|
||||
<HSplitPane leftPaneSize="25%" rightPaneSize="75%" minLeftPaneSize="20%" minRightPaneSize="40%">
|
||||
<left slot="left" class="h-full ">
|
||||
<div class="h-full overflow-auto p-4 bg-gray-50">
|
||||
{#if $flowStore.value.modules && $flowStateStore.modules}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
export let flowModule: FlowModule | undefined = undefined
|
||||
</script>
|
||||
|
||||
<FlowCardHeader {title} {flowModule}>
|
||||
<FlowCardHeader {title} bind:flowModule>
|
||||
<slot name="header" />
|
||||
</FlowCardHeader>
|
||||
<slot />
|
||||
|
||||
@@ -1,27 +1,40 @@
|
||||
<script lang="ts">
|
||||
import type { BadgeColor } from '$lib/components/common'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import IconedPath from '$lib/components/IconedPath.svelte'
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { RawScript, type FlowModule } from '$lib/gen'
|
||||
import { isEmptyFlowModule } from '../flowStateUtils'
|
||||
|
||||
export let flowModule: FlowModule | undefined = undefined
|
||||
export let title: string | undefined = undefined
|
||||
|
||||
$: flowModuleTitle =
|
||||
flowModule?.summary ||
|
||||
(flowModule?.value.type === 'rawscript'
|
||||
? `Inline ${flowModule?.value.language}`
|
||||
: 'Select a script')
|
||||
$: shouldPick = flowModule && isEmptyFlowModule(flowModule)
|
||||
|
||||
const languageColors: Record<RawScript.language, BadgeColor> = {
|
||||
[RawScript.language.GO]: 'dark-indigo',
|
||||
[RawScript.language.DENO]: 'dark-blue',
|
||||
[RawScript.language.PYTHON3]: 'dark-green'
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between flex-wrap py-2 px-4 border-b bg-gray-50 shadow-sm h-12"
|
||||
class="flex items-center justify-between py-2 px-4 border-b bg-gray-50 shadow-sm space-x-2 h-12 flex-nowrap"
|
||||
>
|
||||
{#if flowModule}
|
||||
<span class="text-xs font-bold text-gray-900 flex flex-col shrink">
|
||||
{#if 'path' in flowModule.value && flowModule.value.path}
|
||||
<IconedPath path={flowModule.value.path} />
|
||||
{:else if 'language' in flowModule.value && flowModule.value.language}
|
||||
{flowModuleTitle}
|
||||
{/if}
|
||||
<span class="text-sm w-full">
|
||||
<div class="flex items-center space-x-2">
|
||||
{#if shouldPick}
|
||||
<span class="font-bold text-xs">Select a script</span>
|
||||
{:else if flowModule?.value.type === 'rawscript'}
|
||||
<Badge color={languageColors[flowModule?.value.language] ?? 'gray'} capitalize>
|
||||
{flowModule?.value.language}
|
||||
</Badge>
|
||||
<input bind:value={flowModule.summary} placeholder={'Summary'} />
|
||||
{:else if flowModule?.value.type === 'script' && 'path' in flowModule.value && flowModule.value.path}
|
||||
<IconedPath path={flowModule.value.path} />
|
||||
<input bind:value={flowModule.summary} placeholder="Summary" class="ml-2" />
|
||||
{/if}
|
||||
</div>
|
||||
</span>
|
||||
{/if}
|
||||
{#if title}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
<script context="module" lang="ts">
|
||||
export type FlowModuleWidthContext = {
|
||||
width: Writable<number>
|
||||
threshold: number
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { VSplitPane } from 'svelte-split-pane'
|
||||
|
||||
@@ -26,10 +33,11 @@
|
||||
import { flowStateStore, type FlowModuleState } from '../flowState'
|
||||
import { scriptLangToEditorLang } from '$lib/utils'
|
||||
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { getContext, setContext } from 'svelte'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import FlowModuleAdvancedSettings from './FlowModuleAdvancedSettings.svelte'
|
||||
import { loadSchemaFromModule } from '../utils'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import FlowModuleScript from './FlowModuleScript.svelte'
|
||||
|
||||
const { selectedId, select } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
@@ -83,27 +91,33 @@
|
||||
async function applyCreateLoop() {
|
||||
await apply(createLoop, null)
|
||||
}
|
||||
|
||||
export const FLOW_MODULE_WIDTH_THRESHOLD = 768
|
||||
const width = writable<number>(0)
|
||||
|
||||
setContext<FlowModuleWidthContext>('FlowModuleWidth', {
|
||||
width,
|
||||
threshold: FLOW_MODULE_WIDTH_THRESHOLD
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
<div class="flex flex-col h-full ">
|
||||
<FlowCard {flowModule}>
|
||||
<div class="flex flex-col h-full" bind:clientWidth={$width}>
|
||||
<FlowCard bind:flowModule>
|
||||
<svelte:fragment slot="header">
|
||||
<div class="flex-shrink-0">
|
||||
<FlowModuleHeader
|
||||
bind:module={flowModule}
|
||||
on:delete
|
||||
on:fork={() => apply(fork, flowModule)}
|
||||
on:createScriptFromInlineScript={() => {
|
||||
apply(createScriptFromInlineScript, {
|
||||
flowModule: flowModule,
|
||||
suffix: $selectedId,
|
||||
schema: flowModuleState.schema
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<FlowModuleHeader
|
||||
bind:module={flowModule}
|
||||
on:delete
|
||||
on:fork={() => apply(fork, flowModule)}
|
||||
on:createScriptFromInlineScript={() => {
|
||||
apply(createScriptFromInlineScript, {
|
||||
flowModule: flowModule,
|
||||
suffix: $selectedId,
|
||||
schema: flowModuleState.schema
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
{#if shouldPick}
|
||||
<FlowInputs
|
||||
@@ -126,7 +140,12 @@
|
||||
{:else}
|
||||
{#if flowModule.value.type === 'rawscript'}
|
||||
<div class="flex-shrink-0 border-b p-1">
|
||||
<EditorBar {editor} lang={flowModule.value['language'] ?? 'deno'} {websocketAlive} />
|
||||
<EditorBar
|
||||
{editor}
|
||||
lang={flowModule.value['language'] ?? 'deno'}
|
||||
{websocketAlive}
|
||||
iconOnly={$width < FLOW_MODULE_WIDTH_THRESHOLD}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { isEmptyFlowModule } from '$lib/components/flows/flowStateUtils'
|
||||
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
|
||||
import { faCodeBranch, faSave, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Button } from 'flowbite-svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import type { FlowModuleWidthContext } from './FlowModule.svelte'
|
||||
import RemoveStepConfirmationModal from './RemoveStepConfirmationModal.svelte'
|
||||
|
||||
export let module: FlowModule
|
||||
@@ -17,25 +18,42 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const { selectedId, select } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
const { width, threshold } = getContext<FlowModuleWidthContext>('FlowModuleWidth')
|
||||
$: iconOnly = $width < threshold
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row space-x-2" on:click|stopPropagation={() => undefined}>
|
||||
<div class="flex flex-row space-x-2">
|
||||
{#if module.value.type === 'script' && !shouldPick}
|
||||
<Button size="xs" color="alternative" on:click={() => dispatch('fork')}>
|
||||
<Icon data={faCodeBranch} class="mr-2" />
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
on:click={() => dispatch('fork')}
|
||||
startIcon={{ icon: faCodeBranch }}
|
||||
{iconOnly}
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
{#if module.value.type === 'rawscript' && !shouldPick}
|
||||
<Button size="xs" color="alternative" on:click={() => dispatch('createScriptFromInlineScript')}>
|
||||
<Icon data={faSave} class="mr-2" />
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={() => dispatch('createScriptFromInlineScript')}
|
||||
{iconOnly}
|
||||
>
|
||||
Save to workspace
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
size="xs"
|
||||
color="alternative"
|
||||
color="light"
|
||||
variant="border"
|
||||
startIcon={{ icon: faTrashAlt }}
|
||||
{iconOnly}
|
||||
on:click={(event) => {
|
||||
if (event.shiftKey || shouldPick) {
|
||||
dispatch('delete')
|
||||
@@ -45,7 +63,6 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon data={faTrashAlt} class="mr-2" />
|
||||
{$selectedId.includes('failure') ? 'Delete error handler' : 'Remove step'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="flex justify-between items-center border-y p-2 px-4">
|
||||
<div id="flow_title" class="flex justify-between items-center">
|
||||
<button on:click={() => select('settings')}>
|
||||
<span class="font-mono text-sm "> {$flowStore.path}</span>
|
||||
<span class="font-mono text-sm"> {$flowStore.path}</span>
|
||||
<Icon
|
||||
data={faPen}
|
||||
scale={0.8}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
export let color: 'blue' | 'orange' = 'blue'
|
||||
export let isFirst: boolean = false
|
||||
@@ -35,20 +35,21 @@
|
||||
</div>
|
||||
<div
|
||||
class={classNames(
|
||||
'border w-full rounded-sm p-2 bg-white text-sm cursor-pointer flex justify-between items-center space-x-2',
|
||||
'border w-full rounded-sm p-2 bg-white text-sm cursor-pointer flex justify-between items-center space-x-2 overflow-hidden',
|
||||
margin,
|
||||
selected ? 'outline outline-offset-1 outline-2 outline-gray-600' : ''
|
||||
)}
|
||||
>
|
||||
<slot name="content" />
|
||||
{#if deletable}
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
on:click={(event) => dispatch('delete', { event })}
|
||||
class="text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-sm text-xs px-2 py-1"
|
||||
>
|
||||
<Icon data={faTrashAlt} scale={0.8} />
|
||||
</button>
|
||||
startIcon={{ icon: faTrashAlt }}
|
||||
iconOnly={true}
|
||||
color="light"
|
||||
variant="border"
|
||||
size="xs"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -130,15 +130,14 @@
|
||||
<div slot="icon">
|
||||
<span>{index + 1}</span>
|
||||
</div>
|
||||
<div slot="content" class="w-full">
|
||||
<input
|
||||
bind:value={mod.summary}
|
||||
placeholder={mod.summary ||
|
||||
<div slot="content" class="w-full truncate block">
|
||||
<span
|
||||
>{mod.summary ||
|
||||
mod.value.path ||
|
||||
(mod.value.type === 'rawscript'
|
||||
? `Inline ${mod.value.language}`
|
||||
: 'Select a script')}
|
||||
/>
|
||||
: 'Select a script')}</span
|
||||
>
|
||||
</div>
|
||||
</FlowModuleSchemaItem>
|
||||
</li>
|
||||
@@ -160,6 +159,9 @@
|
||||
|
||||
<RemoveStepConfirmationModal
|
||||
bind:open={confirmationModalOpen}
|
||||
on:canceled={() => {
|
||||
indexToRemove = undefined
|
||||
}}
|
||||
on:confirmed={() => {
|
||||
if (indexToRemove !== undefined) {
|
||||
removeAtIndex(indexToRemove)
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<HSplitPane leftPaneSize="50%" rightPaneSize="50%" minLeftPaneSize="20%" minRightPaneSize="20%">
|
||||
<left slot="left" class="relative">
|
||||
<div class=" overflow-auto h-full p-4">
|
||||
<div class="overflow-auto h-full p-4">
|
||||
<slot />
|
||||
</div>
|
||||
</left>
|
||||
|
||||
Reference in New Issue
Block a user