Compare commits

...

10 Commits

Author SHA1 Message Date
Guilhem
01030ec3f8 Nit 2025-03-25 17:50:37 +00:00
Guilhem
b7f22b693e Use RunButton in app editor 2025-03-25 17:13:55 +00:00
Guilhem
e1d67d82ce Add props to RunButton 2025-03-25 17:11:15 +00:00
Guilhem
e60466397c Use RunButton for app debug run tab 2025-03-25 16:42:43 +00:00
Guilhem
00cbdcef36 Use run button in step test 2025-03-25 15:40:58 +00:00
Guilhem
318c55dccd Use run button in script editor 2025-03-25 15:40:43 +00:00
Guilhem
d7c96160f9 Add hidden mode for capture button 2025-03-25 15:40:10 +00:00
Guilhem
eb20023ca8 Upgrade Button to allow custom dropdown icon 2025-03-25 15:39:05 +00:00
Guilhem
088534c02d Add common run button 2025-03-25 15:38:23 +00:00
Guilhem
5d3459aaf1 keep job sync on cancel 2025-03-25 13:19:02 +00:00
11 changed files with 204 additions and 165 deletions

View File

@@ -4,10 +4,10 @@
import { workspaceStore } from '$lib/stores'
import { getScriptByPath } from '$lib/scripts'
import { CornerDownLeft, Loader2 } from 'lucide-svelte'
import { Loader2 } from 'lucide-svelte'
import { getContext } from 'svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import Button from './common/button/Button.svelte'
import { RunButton } from '$lib/components/common'
import DisplayResult from './DisplayResult.svelte'
import type { FlowEditorContext } from './flows/types'
import LogViewer from './LogViewer.svelte'
@@ -36,6 +36,7 @@
let testJobLoader: TestJobLoader
let testIsLoading = false
let testJob: Job | undefined = undefined
let cancelLoading = false
let jobProgressReset: () => void
@@ -106,6 +107,7 @@
<TestJobLoader
toastError={noEditor}
on:done={() => jobDone()}
on:cancel-loading={({ detail }) => (cancelLoading = detail)}
bind:scriptProgress
bind:this={testJobLoader}
bind:isLoading={testIsLoading}
@@ -120,24 +122,12 @@
{/if}
<div class="w-full justify-center flex">
{#if testIsLoading}
<Button size="sm" on:click={testJobLoader?.cancelJob} btnClasses="w-full" color="red">
<Loader2 size={16} class="animate-spin mr-1" />
Cancel
</Button>
{:else}
<Button
color="dark"
btnClasses="truncate"
size="sm"
on:click={() => runTest(stepArgs)}
shortCut={{
Icon: CornerDownLeft
}}
>
Run
</Button>
{/if}
<RunButton
on:run={() => runTest(stepArgs)}
on:cancel={() => testJobLoader?.cancelJob()}
{testIsLoading}
bind:cancelLoading
/>
</div>
<ModulePreviewForm {pickableProperties} {mod} {schema} bind:args={stepArgs} />

View File

@@ -14,15 +14,15 @@
import TestJobLoader from './TestJobLoader.svelte'
import JobProgressBar from '$lib/components/jobs/JobProgressBar.svelte'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import { Button } from './common'
import { Button, RunButton } from './common'
import SplitPanesWrapper from './splitPanes/SplitPanesWrapper.svelte'
import WindmillIcon from './icons/WindmillIcon.svelte'
//import WindmillIcon from './icons/WindmillIcon.svelte'
import * as Y from 'yjs'
import { scriptLangToEditorLang } from '$lib/scripts'
import { WebsocketProvider } from 'y-websocket'
import Modal from './common/modal/Modal.svelte'
import DiffEditor from './DiffEditor.svelte'
import { Clipboard, CornerDownLeft, Github, Play } from 'lucide-svelte'
import { Clipboard, Github } from 'lucide-svelte'
import { setLicense } from '$lib/enterpriseUtils'
import type { ScriptEditorWhitelabelCustomUi } from './custom_ui'
import Tabs from './common/tabs/Tabs.svelte'
@@ -31,6 +31,7 @@
import CaptureTable from '$lib/components/triggers/CaptureTable.svelte'
import CaptureButton from './triggers/CaptureButton.svelte'
import { setContext } from 'svelte'
import CaptureIcon from './triggers/CaptureIcon.svelte'
// Exported
export let schema: Schema | any = emptySchema()
@@ -92,6 +93,8 @@
let yContent: Y.Text | undefined = undefined
let peers: { name: string }[] = []
let showCollabPopup = false
let captureItems: Item[] = []
let cancelLoading = false
const url = new URL(window.location.toString())
let initialCollab = /true|1/i.test(url.searchParams.get('collab') ?? '0')
@@ -272,12 +275,17 @@
<TestJobLoader
on:done={loadPastTests}
on:cancel-loading={() => (cancelLoading = true)}
bind:scriptProgress
bind:this={testJobLoader}
bind:isLoading={testIsLoading}
bind:job={testJob}
/>
{#if customUi?.previewPanel?.disableTriggerButton !== true}
<CaptureButton on:openTriggers hidden bind:items={captureItems} />
{/if}
<svelte:window on:keydown={onKeyDown} />
<Modal title="Invite others" bind:open={showCollabPopup}>
@@ -406,63 +414,17 @@
{/if}
<div class="flex justify-center pt-1">
{#if testIsLoading}
<Button on:click={testJobLoader?.cancelJob} btnClasses="w-full" color="red" size="xs">
<WindmillIcon
white={true}
class="mr-2 text-white"
height="16px"
width="20px"
spin="fast"
/>
Cancel
</Button>
{:else if customUi?.previewPanel?.disableTriggerButton !== true}
<div class="flex flex-row divide-x divide-gray-800 dark:divide-gray-300 items-stretch">
<Button
color="dark"
on:click={() => {
runTest()
}}
btnClasses="w-full rounded-r-none"
size="xs"
startIcon={{
icon: Play,
classes: 'animate-none'
}}
shortCut={{ Icon: CornerDownLeft, hide: testIsLoading }}
>
{#if testIsLoading}
Running
{:else}
Test
{/if}
</Button>
<CaptureButton on:openTriggers />
</div>
{:else}
<div class="flex flex-row divide-x divide-gray-800 dark:divide-gray-300 items-stretch">
<Button
color="dark"
on:click={() => {
runTest()
}}
btnClasses="w-full"
size="xs"
startIcon={{
icon: Play,
classes: 'animate-none'
}}
shortCut={{ Icon: CornerDownLeft, hide: testIsLoading }}
>
{#if testIsLoading}
Running
{:else}
Test
{/if}
</Button>
</div>
{/if}
<RunButton
on:cancel={testJobLoader?.cancelJob}
on:run={() => runTest()}
{testIsLoading}
bind:dropdownItems={captureItems}
bind:cancelLoading
>
<svelte:fragment slot="dropdownIcon">
<CaptureIcon variant="redDot" />
</svelte:fragment>
</RunButton>
</div>
<Splitpanes horizontal class="!max-h-[calc(100%-43px)]">
<Pane size={33}>

View File

@@ -150,8 +150,9 @@
export async function cancelJob() {
const id = currentId
if (id) {
dispatch('cancel', id)
currentId = undefined
// currentId = undefined why do we need this? This leads to not watching the job anymore hence not getting the update of the cancelled job
// dispatch('cancel', id) this event is not used anywhere
dispatch('cancel-loading', true)
try {
await JobService.cancelQueuedJob({
workspace: $workspaceStore ?? '',
@@ -159,6 +160,7 @@
requestBody: {}
})
} catch (err) {
dispatch('cancel-loading', false)
console.error(err)
}
}
@@ -283,7 +285,7 @@
async function syncer(id: string): Promise<void> {
if (currentId != id) {
dispatch('cancel', id)
// dispatch('cancel', id) This event is not used anywhere
return
}
syncIteration++

View File

@@ -94,6 +94,7 @@
import { base } from '$lib/base'
import ClipboardPanel from '$lib/components/details/ClipboardPanel.svelte'
import LazyModePanel from './contextPanel/LazyModePanel.svelte'
import { RunButton } from '$lib/components/common'
async function hash(message) {
try {
@@ -956,11 +957,21 @@
}, 500)
}
$: customPath !== undefined && validateCustomPath(customPath)
let cancelLoading = false
$: if (cancelLoading && !testIsLoading) {
cancelLoading = false
}
</script>
<svelte:window on:keydown={onKeyDown} />
<TestJobLoader bind:this={testJobLoader} bind:isLoading={testIsLoading} bind:job />
<TestJobLoader
bind:this={testJobLoader}
bind:isLoading={testIsLoading}
bind:job
on:cancel-loading={({ detail }) => (cancelLoading = detail)}
/>
{#if $$slots.unsavedConfirmationModal}
<slot
@@ -1412,16 +1423,12 @@
{:else}
<div class="flex flex-col h-full w-full mb-4">
{#if job?.['running']}
<div class="flex flex-row-reverse w-full">
<Button
color="red"
variant="border"
on:click={() => testJobLoader?.cancelJob()}
>
<Loader2 size={14} class="animate-spin mr-2" />
Cancel
</Button>
<div class="flex flex-row-reverse w-full p-2 justify-center">
<RunButton
on:cancel={() => testJobLoader?.cancelJob()}
{testIsLoading}
{cancelLoading}
/>
</div>
{/if}
{#if job?.args}

View File

@@ -294,7 +294,13 @@
>
Format
</Button>
<RunButton bind:runLoading {id} inlineScript={!transformer ? inlineScript : undefined} />
<RunButton
bind:runLoading
{id}
inlineScript={!transformer ? inlineScript : undefined}
size="xs2"
btnClasses="w-24 h-[28px]"
/>
</div>
</div>

View File

@@ -6,13 +6,14 @@
CancelablePromise,
InlineScript
} from '../../types'
import { Button } from '$lib/components/common'
import { CornerDownLeft, Loader2 } from 'lucide-svelte'
import { RunButton } from '$lib/components/common'
export let id: string
export let inlineScript: InlineScript | undefined = undefined
export let runLoading = false
export let hideShortcut = false
export let size: 'xs' | 'xs2' = 'xs'
export let btnClasses = ''
const { runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
const { runnableJobEditorPanel } = getContext<AppEditorContext>('AppEditorContext')
@@ -20,38 +21,23 @@
</script>
{#if $runnableComponents[id] != undefined}
{#if !runLoading}
<Button
loading={runLoading}
size="xs"
color="dark"
btnClasses="!px-2 !py-1"
on:click={async () => {
runLoading = true
$runnableJobEditorPanel.focused = true
try {
cancelable = $runnableComponents[id]?.cb?.map((f) => f(inlineScript, true))
await Promise.all(cancelable)
} catch {}
runLoading = false
}}
shortCut={{ Icon: CornerDownLeft, hide: hideShortcut }}
>
Run
</Button>
{:else}
<Button
size="xs"
color="red"
variant="border"
btnClasses="!px-2 !py-1 !ml-[3px]"
on:click={async () => {
cancelable?.forEach((f) => f.cancel())
runLoading = false
}}
>
<Loader2 size={12} class="animate-spin mr-2" />
Cancel
</Button>
{/if}
<RunButton
{btnClasses}
testIsLoading={runLoading}
{hideShortcut}
on:cancel={async () => {
cancelable?.forEach((f) => f.cancel())
runLoading = false
}}
on:run={async () => {
runLoading = true
$runnableJobEditorPanel.focused = true
try {
cancelable = $runnableComponents[id]?.cb?.map((f) => f(inlineScript, true))
await Promise.all(cancelable)
} catch {}
runLoading = false
}}
{size}
/>
{/if}

View File

@@ -0,0 +1,56 @@
<script lang="ts">
import { Button } from '$lib/components/common'
import WindmillIcon from '$lib/components/icons/WindmillIcon.svelte'
import { createEventDispatcher } from 'svelte'
import { CornerDownLeft, Play } from 'lucide-svelte'
import type { Item } from '$lib/utils'
import { twMerge } from 'tailwind-merge'
const dispatch = createEventDispatcher()
export let testIsLoading = false
export let cancelLoading = false
export let dropdownItems: Item[] = []
export let btnClasses = ''
export let hideShortcut = false
export let size: 'xs' | 'xs2' = 'xs'
$: if (cancelLoading && !testIsLoading) {
cancelLoading = false
}
function handleClick() {
if (testIsLoading) {
dispatch('cancel')
} else {
dispatch('run')
}
}
</script>
<Button
color={testIsLoading ? 'red' : 'dark'}
btnClasses={twMerge('w-28', btnClasses)}
{size}
on:click={handleClick}
shortCut={testIsLoading || hideShortcut
? undefined
: {
Icon: CornerDownLeft
}}
{dropdownItems}
dropdownDisabled={testIsLoading}
>
<div class="flex flex-row items-center gap-2 align-left">
{#if testIsLoading}
<WindmillIcon white={true} class="text-white" height="16px" width="16px" spin="fast" />
{cancelLoading ? 'Canceling...' : 'Cancel'}
{:else}
<Play size={16} />
Run
{/if}
</div>
<svelte:fragment slot="dropdownIcon">
<slot name="dropdownIcon" />
</svelte:fragment>
</Button>

View File

@@ -34,6 +34,7 @@
export let shortCut:
| { key?: string; hide?: boolean; Icon?: any; withoutModifier?: boolean }
| undefined = undefined
export let dropdownDisabled: boolean = false
type MenuItem = {
label: string
@@ -42,10 +43,25 @@
icon?: any
disabled?: boolean
}
export let dropdownItems: MenuItem[] | (() => MenuItem[]) | undefined = undefined
export let dropdownItems: MenuItem[] | (() => MenuItem[]) | Item[] | undefined = undefined
function computeDropdowns(menuItems: MenuItem[] | (() => MenuItem[])): Item[] {
function computeDropdowns(menuItems: MenuItem[] | (() => MenuItem[]) | Item[]): Item[] {
const items = typeof menuItems === 'function' ? menuItems() : menuItems
function isItemArray(items: any[]): items is Item[] {
return items.every(
(item) =>
'displayName' in item &&
typeof item.displayName === 'string' &&
(!('action' in item) || typeof item.action === 'function' || item.action === undefined) &&
(!('disabled' in item) || typeof item.disabled === 'boolean')
)
}
if (items.length > 0 && isItemArray(items)) {
return items
}
return items.map((item) => ({
displayName: item.label,
action: item.onClick ? (e) => item.onClick?.(e) : undefined,
@@ -232,7 +248,11 @@
{/if}
{#if dropdownItems && dropdownItems.length > 0}
<Dropdown items={computeDropdowns(dropdownItems)} class="h-auto w-fit">
<Dropdown
items={computeDropdowns(dropdownItems)}
class="h-auto w-fit"
disabled={dropdownDisabled}
>
<svelte:fragment slot="buttonReplacement">
<div
class={twMerge(
@@ -242,7 +262,11 @@
'rounded-r-md !rounded-l-none'
)}
>
<ChevronDown class="w-5 h-5" />
{#if $$slots.dropdownIcon}
<slot name="dropdownIcon" />
{:else}
<ChevronDown class="w-5 h-5" />
{/if}
</div>
</svelte:fragment>
</Dropdown>

View File

@@ -16,6 +16,7 @@ export { default as TabContent } from './tabs/TabContent.svelte'
export { default as Tabs } from './tabs/Tabs.svelte'
export { default as Breadcrumb } from './breadcrumb/Breadcrumb.svelte'
export { default as FileInput } from './fileInput/FileInput.svelte'
export { default as RunButton } from './RunButton.svelte'
export * from './alert/model'
export * from './badge/model'

View File

@@ -13,6 +13,8 @@
import MqttIcon from '../icons/MqttIcon.svelte'
export let small = false
export let hidden = false
export let items: Item[] = []
const dispatch = createEventDispatcher()
@@ -75,32 +77,34 @@
]
</script>
<DropdownV2 {items} placement="bottom-start" fixedHeight={false}>
<svelte:fragment slot="buttonReplacement">
{#if small}
<Button
color="light"
size="xs"
variant="border"
wrapperClasses="h-full"
nonCaptureEvent
title="Test trigger"
>
<div class="flex flex-row items-center gap-1">
{#if !hidden}
<DropdownV2 {items} placement="bottom-start" fixedHeight={false}>
<svelte:fragment slot="buttonReplacement">
{#if small}
<Button
color="light"
size="xs"
variant="border"
wrapperClasses="h-full"
nonCaptureEvent
title="Test trigger"
>
<div class="flex flex-row items-center gap-1">
<CaptureIcon variant="redDot" />
<Plus size={10} class="text-red" />
</div>
</Button>
{:else}
<Button
color="dark"
btnClasses="!rounded-l-none"
wrapperClasses="h-full"
nonCaptureEvent
title="Test trigger"
>
<CaptureIcon variant="redDot" />
<Plus size={10} class="text-red" />
</div>
</Button>
{:else}
<Button
color="dark"
btnClasses="!rounded-l-none"
wrapperClasses="h-full"
nonCaptureEvent
title="Test trigger"
>
<CaptureIcon variant="redDot" />
</Button>
{/if}
</svelte:fragment>
</DropdownV2>
</Button>
{/if}
</svelte:fragment>
</DropdownV2>
{/if}

View File

@@ -527,7 +527,7 @@
{#if !forceCancel}
<Button
color="red"
size="md"
size="sm"
startIcon={{ icon: TimerOff }}
on:click|once={() => {
if (job?.id) {
@@ -543,7 +543,7 @@
{:else}
<Button
color="red"
size="md"
size="sm"
startIcon={{ icon: TimerOff }}
on:click|once={() => {
if (job?.id) {
@@ -580,6 +580,7 @@
restartFlow(job?.id, selectedJobStep, 0)
}}
startIcon={{ icon: RefreshCw }}
size="sm"
>
Re-start from
<Badge baseClass="ml-1" color="indigo">