Compare commits

...

3 Commits

Author SHA1 Message Date
Ruben Fiszel
57b02e6103 update 2025-06-10 11:29:47 +02:00
Ruben Fiszel
a7ed1c83c5 merge updates 2025-06-10 01:45:48 +02:00
Ruben Fiszel
360a371d0e all 2025-05-28 23:58:57 +02:00
20 changed files with 670 additions and 381 deletions

View File

@@ -7,7 +7,6 @@
"": { "": {
"name": "windmill-components", "name": "windmill-components",
"version": "1.496.3", "version": "1.496.3",
"hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-js": "^4.0.0", "@aws-crypto/sha256-js": "^4.0.0",
@@ -151,7 +150,7 @@
"fsevents": "^2.3.3" "fsevents": "^2.3.3"
}, },
"peerDependencies": { "peerDependencies": {
"svelte": "^4.0.0" "svelte": "^5.0.0"
} }
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {

View File

@@ -5,7 +5,6 @@
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"postinstall": "node scripts/untar_ui_builder.js && node scripts/patch_files.js",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --threshold warning", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --threshold warning",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
@@ -161,7 +160,7 @@
"zod-to-json-schema": "^3.24.5" "zod-to-json-schema": "^3.24.5"
}, },
"peerDependencies": { "peerDependencies": {
"svelte": "^4.0.0" "svelte": "^5.0.0"
}, },
"exports": { "exports": {
"./package.json": "./package.json", "./package.json": "./package.json",
@@ -381,11 +380,16 @@
"./tailwindUtils": { "./tailwindUtils": {
"types": "./package/components/apps/editor/componentsPanel/tailwindUtils.d.ts", "types": "./package/components/apps/editor/componentsPanel/tailwindUtils.d.ts",
"default": "./package/components/apps/editor/componentsPanel/tailwindUtils.js" "default": "./package/components/apps/editor/componentsPanel/tailwindUtils.js"
},
"./components/custom_ui": {
"types": "./package/components/custom_ui.d.ts",
"default": "./package/components/custom_ui.js"
} }
}, },
"files": [ "files": [
"dist", "dist",
"package" "package",
"scripts"
], ],
"license": "AGPL-3.0", "license": "AGPL-3.0",
"svelte": "./dist/index.js", "svelte": "./dist/index.js",
@@ -519,6 +523,9 @@
], ],
"tailwindUtils": [ "tailwindUtils": [
"./package/components/apps/editor/componentsPanel/tailwindUtils.d.ts" "./package/components/apps/editor/componentsPanel/tailwindUtils.d.ts"
],
"components/custom_ui": [
"./package/components/custom_ui.d.ts"
] ]
} }
}, },

View File

@@ -1,3 +1,14 @@
// Check if this script is being run from the package root
const isTopLevel = !process.env.npm_config_global && process.env.INIT_CWD === process.cwd()
if (isTopLevel) {
console.log('Running postinstall: direct install')
// Your postinstall logic here
} else {
console.log('Skipping postinstall: installed as dependency')
process.exit(0)
}
import path from 'path' import path from 'path'
import fs from 'fs' import fs from 'fs'
@@ -18,17 +29,6 @@ const response = await fetch(tarUrl)
const buffer = await response.arrayBuffer() const buffer = await response.arrayBuffer()
await fs.promises.writeFile(outputTarPath, Buffer.from(buffer)) await fs.promises.writeFile(outputTarPath, Buffer.from(buffer))
// Check if this script is being run from the package root
const isRootInstall = process.cwd() + '/scripts' === __dirname
if (isRootInstall) {
console.log('Running postinstall: direct install')
// Your postinstall logic here
} else {
console.log('Skipping postinstall: installed as dependency')
process.exit(0)
}
// Create extract directory if it doesn't exist // Create extract directory if it doesn't exist
try { try {
await fs.promises.mkdir(extractTo, { recursive: true }) await fs.promises.mkdir(extractTo, { recursive: true })

View File

@@ -1,8 +1,10 @@
<script context="module" lang="ts"> <script module lang="ts">
export const EDITOR_BAR_WIDTH_THRESHOLD = 1044 export const EDITOR_BAR_WIDTH_THRESHOLD = 1044
</script> </script>
<script lang="ts"> <script lang="ts">
import { run } from 'svelte/legacy'
import { ResourceService, VariableService, type Script } from '$lib/gen' import { ResourceService, VariableService, type Script } from '$lib/gen'
import { workspaceStore } from '$lib/stores' import { workspaceStore } from '$lib/stores'
@@ -47,110 +49,145 @@
import type { EditorBarUi } from './custom_ui' import type { EditorBarUi } from './custom_ui'
import EditorSettings from './EditorSettings.svelte' import EditorSettings from './EditorSettings.svelte'
export let lang: SupportedLanguage | 'bunnative' | undefined interface Props {
export let editor: Editor | undefined lang: SupportedLanguage | 'bunnative' | undefined
export let websocketAlive: { editor: Editor | undefined
pyright: boolean websocketAlive: {
ruff: boolean pyright: boolean
deno: boolean ruff: boolean
go: boolean deno: boolean
shellcheck: boolean go: boolean
shellcheck: boolean
}
iconOnly?: boolean
validCode?: boolean
kind?: 'script' | 'trigger' | 'approval'
template?: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative'
collabMode?: boolean
collabLive?: boolean
collabUsers?: { name: string }[]
scriptPath?: string | undefined
diffEditor?: DiffEditor | undefined
args: Record<string, any>
noHistory?: boolean
saveToWorkspace?: boolean
customUi?: EditorBarUi
lastDeployedCode?: string | undefined
diffMode?: boolean
showHistoryDrawer?: boolean
right?: import('svelte').Snippet
} }
export let iconOnly: boolean = false
export let validCode: boolean = true
export let kind: 'script' | 'trigger' | 'approval' = 'script'
export let template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative' =
'script'
export let collabMode = false
export let collabLive = false
export let collabUsers: { name: string }[] = []
export let scriptPath: string | undefined = undefined
export let diffEditor: DiffEditor | undefined = undefined
export let args: Record<string, any>
export let noHistory = false
export let saveToWorkspace = false
export let customUi: EditorBarUi = {}
export let lastDeployedCode: string | undefined = undefined
export let diffMode: boolean = false
export let showHistoryDrawer: boolean = false
let contextualVariablePicker: ItemPicker let {
let variablePicker: ItemPicker lang,
let resourcePicker: ItemPicker editor,
let resourceTypePicker: ItemPicker websocketAlive,
let variableEditor: VariableEditor iconOnly = false,
let resourceEditor: ResourceEditorDrawer validCode = true,
let showContextVarPicker = false kind = 'script',
let showVarPicker = false template = 'script',
let showResourcePicker = false collabMode = false,
let showResourceTypePicker = false collabLive = false,
collabUsers = [],
scriptPath = undefined,
diffEditor = undefined,
args,
noHistory = false,
saveToWorkspace = false,
customUi = {},
lastDeployedCode = undefined,
diffMode = false,
showHistoryDrawer = $bindable(false),
right
}: Props = $props()
$: showContextVarPicker = [ let contextualVariablePicker: ItemPicker | undefined = $state()
'python3', let variablePicker: ItemPicker | undefined = $state()
'bash', let resourcePicker: ItemPicker | undefined = $state()
'powershell', let resourceTypePicker: ItemPicker | undefined = $state()
'go', let variableEditor: VariableEditor | undefined = $state()
'deno', let resourceEditor: ResourceEditorDrawer | undefined = $state()
'bun', let showContextVarPicker = $state(false)
'bunnative', let showVarPicker = $state(false)
'nativets', let showResourcePicker = $state(false)
'php', let showResourceTypePicker = $state(false)
'rust',
'csharp',
'nu',
'java'
// for related places search: ADD_NEW_LANG
].includes(lang ?? '')
$: showVarPicker = [
'python3',
'bash',
'powershell',
'go',
'deno',
'bun',
'bunnative',
'nativets',
'php',
'rust',
'csharp',
'nu',
'java'
// for related places search: ADD_NEW_LANG
].includes(lang ?? '')
$: showResourcePicker = [
'python3',
'bash',
'powershell',
'go',
'deno',
'bun',
'bunnative',
'nativets',
'php',
'rust',
'csharp',
'nu',
'java'
// for related places search: ADD_NEW_LANG
].includes(lang ?? '')
$: showResourceTypePicker =
['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) ||
lang === 'python3' ||
lang === 'php'
let codeViewer: Drawer run(() => {
let codeObj: { language: SupportedLanguage; content: string } | undefined = undefined showContextVarPicker = [
'python3',
'bash',
'powershell',
'go',
'deno',
'bun',
'bunnative',
'nativets',
'php',
'rust',
'csharp',
'nu',
'java'
// for related places search: ADD_NEW_LANG
].includes(lang ?? '')
})
run(() => {
showVarPicker = [
'python3',
'bash',
'powershell',
'go',
'deno',
'bun',
'bunnative',
'nativets',
'php',
'rust',
'csharp',
'nu',
'java'
// for related places search: ADD_NEW_LANG
].includes(lang ?? '')
})
run(() => {
showResourcePicker = [
'python3',
'bash',
'powershell',
'go',
'deno',
'bun',
'bunnative',
'nativets',
'php',
'rust',
'csharp',
'nu',
'java'
// for related places search: ADD_NEW_LANG
].includes(lang ?? '')
})
run(() => {
showResourceTypePicker =
['typescript', 'javascript'].includes(scriptLangToEditorLang(lang)) ||
lang === 'python3' ||
lang === 'php'
})
let codeViewer: Drawer | undefined = $state()
let codeObj: { language: SupportedLanguage; content: string } | undefined = $state(undefined)
function addEditorActions() { function addEditorActions() {
editor?.addAction('insert-variable', 'Windmill: Insert variable', () => { editor?.addAction('insert-variable', 'Windmill: Insert variable', () => {
variablePicker.openDrawer() variablePicker?.openDrawer()
}) })
editor?.addAction('insert-resource', 'Windmill: Insert resource', () => { editor?.addAction('insert-resource', 'Windmill: Insert resource', () => {
resourcePicker.openDrawer() resourcePicker?.openDrawer()
}) })
} }
$: editor && addEditorActions() run(() => {
editor && addEditorActions()
})
async function loadVariables() { async function loadVariables() {
return await VariableService.listVariable({ workspace: $workspaceStore ?? '' }) return await VariableService.listVariable({ workspace: $workspaceStore ?? '' })
@@ -162,9 +199,9 @@
}) })
} }
let scriptPicker: Drawer let scriptPicker: Drawer | undefined = $state()
let pick_existing: 'hub' | 'workspace' = 'hub' let pick_existing: 'hub' | 'workspace' = $state('hub')
let filter = '' let filter = $state('')
async function onScriptPick(e: { detail: { path: string } }) { async function onScriptPick(e: { detail: { path: string } }) {
codeObj = undefined codeObj = undefined
@@ -462,21 +499,23 @@ string ${windmillPathToCamelCaseName(path)} = await client.GetStringAsync(uri);
itemName="Variable" itemName="Variable"
extraField="path" extraField="path"
loadItems={loadVariables} loadItems={loadVariables}
buttons={{ 'Edit/View': (x) => variableEditor.editVariable(x) }} buttons={{ 'Edit/View': (x) => variableEditor?.editVariable(x) }}
> >
<div slot="submission" class="flex flex-row"> {#snippet submission()}
<Button <div class="flex flex-row">
variant="border" <Button
color="blue" variant="border"
size="sm" color="blue"
startIcon={{ icon: Plus }} size="sm"
on:click={() => { startIcon={{ icon: Plus }}
variableEditor.initNew() on:click={() => {
}} variableEditor?.initNew()
> }}
New variable >
</Button> New variable
</div> </Button>
</div>
{/snippet}
</ItemPicker> </ItemPicker>
<ItemPicker <ItemPicker
@@ -548,24 +587,26 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
tooltip="Resources represent connections to third party systems. Resources are a good way to define a connection to a frequently used third party system such as a database." tooltip="Resources represent connections to third party systems. Resources are a good way to define a connection to a frequently used third party system such as a database."
documentationLink="https://www.windmill.dev/docs/core_concepts/resources_and_types" documentationLink="https://www.windmill.dev/docs/core_concepts/resources_and_types"
itemName="Resource" itemName="Resource"
buttons={{ 'Edit/View': (x) => resourceEditor.initEdit(x) }} buttons={{ 'Edit/View': (x) => resourceEditor?.initEdit(x) }}
extraField="description" extraField="description"
extraField2="resource_type" extraField2="resource_type"
loadItems={async () => loadItems={async () =>
await ResourceService.listResource({ workspace: $workspaceStore ?? 'NO_W' })} await ResourceService.listResource({ workspace: $workspaceStore ?? 'NO_W' })}
> >
<div slot="submission" class="flex flex-row gap-x-1 mr-2"> {#snippet submission()}
<Button <div class="flex flex-row gap-x-1 mr-2">
startIcon={{ icon: Plus }} <Button
target="_blank" startIcon={{ icon: Plus }}
variant="border" target="_blank"
color="blue" variant="border"
size="sm" color="blue"
href="{base}/resources?connect_app=undefined" size="sm"
> href="{base}/resources?connect_app=undefined"
Add resource >
</Button> Add resource
</div> </Button>
</div>
{/snippet}
</ItemPicker> </ItemPicker>
{#if showResourceTypePicker} {#if showResourceTypePicker}
@@ -650,7 +691,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
size="xs" size="xs"
spacingSize="md" spacingSize="md"
color="light" color="light"
on:click={resourceTypePicker.openDrawer} on:click={() => resourceTypePicker?.openDrawer}
{iconOnly} {iconOnly}
startIcon={{ icon: Package }} startIcon={{ icon: Package }}
> >
@@ -724,7 +765,9 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
}} }}
/> />
<Popover> <Popover>
<svelte:fragment slot="text">Toggle diff mode</svelte:fragment> {#snippet text()}
Toggle diff mode
{/snippet}
<DiffIcon class="ml-1 text-tertiary" size={14} /> <DiffIcon class="ml-1 text-tertiary" size={14} />
</Popover> </Popover>
</div> </div>
@@ -739,14 +782,16 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
on:change={() => dispatch('toggleCollabMode')} on:change={() => dispatch('toggleCollabMode')}
/> />
<Popover> <Popover>
<svelte:fragment slot="text">Multiplayer</svelte:fragment> {#snippet text()}
Multiplayer
{/snippet}
<Users class="ml-1 text-tertiary" size={14} /> <Users class="ml-1 text-tertiary" size={14} />
</Popover> </Popover>
{#if collabLive} {#if collabLive}
<button <button
title="Show invite link" title="Show invite link"
class="p-1 rounded hover:bg-gray-400 mx-1 border" class="p-1 rounded hover:bg-gray-400 mx-1 border"
on:click={() => dispatch('collabPopup')}><Link size={14} /></button onclick={() => dispatch('collabPopup')}><Link size={14} /></button
> >
<div class="isolate flex -space-x-2 pl-2"> <div class="isolate flex -space-x-2 pl-2">
{#each collabUsers as user} {#each collabUsers as user}
@@ -773,7 +818,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
</div> </div>
<div class="flex flex-row items-center gap-2"> <div class="flex flex-row items-center gap-2">
<slot name="right" /> {@render right?.()}
{#if scriptPath && !noHistory} {#if scriptPath && !noHistory}
<Button <Button
btnClasses="!font-medium text-tertiary" btnClasses="!font-medium text-tertiary"

View File

@@ -43,7 +43,6 @@
import type { FlowEditorContext, FlowInput, FlowInputEditorState } from './flows/types' import type { FlowEditorContext, FlowInput, FlowInputEditorState } from './flows/types'
import { cleanInputs } from './flows/utils' import { cleanInputs } from './flows/utils'
import { Calendar, Pen, Save, DiffIcon, HistoryIcon, FileJson, type Icon } from 'lucide-svelte' import { Calendar, Pen, Save, DiffIcon, HistoryIcon, FileJson, type Icon } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
import Awareness from './Awareness.svelte' import Awareness from './Awareness.svelte'
import { getAllModules } from './flows/flowExplorer' import { getAllModules } from './flows/flowExplorer'
import { type FlowCopilotContext } from './copilot/flow' import { type FlowCopilotContext } from './copilot/flow'
@@ -89,6 +88,24 @@
export let setSavedraftCb: ((cb: () => void) => void) | undefined = undefined export let setSavedraftCb: ((cb: () => void) => void) | undefined = undefined
export let draftTriggersFromUrl: Trigger[] | undefined = undefined export let draftTriggersFromUrl: Trigger[] | undefined = undefined
export let selectedTriggerIndexFromUrl: number | undefined = undefined export let selectedTriggerIndexFromUrl: number | undefined = undefined
export let onevent: {
deploy?: (path: string) => void
deployError?: (error: Error) => void
saveInitial?: (path: string) => void
saveDraft?: ({
path,
savedAtNewPath,
newFlow
}: {
path: string
savedAtNewPath: boolean
newFlow: boolean
}) => void
saveDraftError?: (error: Error) => void
saveDraftOnlyAtNewPath?: ({ path, selectedId }: { path: string; selectedId: string }) => void
seeDetails?: (path: string) => void
historyRestore?: () => void
} = {}
let initialPathStore = writable(initialPath) let initialPathStore = writable(initialPath)
$: initialPathStore.set(initialPath) $: initialPathStore.set(initialPath)
@@ -152,8 +169,6 @@
} }
} }
const dispatch = createEventDispatcher()
const primaryScheduleStore = writable<ScheduleTrigger | undefined | false>(savedPrimarySchedule) // kept for legacy reasons const primaryScheduleStore = writable<ScheduleTrigger | undefined | false>(savedPrimarySchedule) // kept for legacy reasons
const triggersCount = writable<TriggersCount | undefined>(undefined) const triggersCount = writable<TriggersCount | undefined>(undefined)
const simplifiedPoll = writable(false) const simplifiedPoll = writable(false)
@@ -275,18 +290,19 @@
let savedAtNewPath = false let savedAtNewPath = false
if (newFlow) { if (newFlow) {
dispatch('saveInitial', $pathStore) onevent.saveInitial?.($pathStore)
} else if (savedFlow?.draft_only && $pathStore !== initialPath) { } else if (savedFlow?.draft_only && $pathStore !== initialPath) {
savedAtNewPath = true savedAtNewPath = true
initialPath = $pathStore initialPath = $pathStore
// this is so we can use the flow builder outside of sveltekit // this is so we can use the flow builder outside of sveltekit
dispatch('saveDraftOnlyAtNewPath', { path: $pathStore, selectedId: getSelectedId() }) onevent.saveDraftOnlyAtNewPath?.({ path: $pathStore, selectedId: getSelectedId() })
} }
dispatch('saveDraft', { path: $pathStore, savedAtNewPath, newFlow })
onevent.saveDraft?.({ path: $pathStore, savedAtNewPath, newFlow })
sendUserToast('Saved as draft') sendUserToast('Saved as draft')
} catch (error) { } catch (error) {
sendUserToast(`Error while saving the flow as a draft: ${error.body || error.message}`, true) sendUserToast(`Error while saving the flow as a draft: ${error.body || error.message}`, true)
dispatch('saveDraftError', error) onevent.saveDraftError?.(error)
} }
loadingDraft = false loadingDraft = false
} }
@@ -450,9 +466,9 @@
} as Flow } as Flow
setDraftTriggers([]) setDraftTriggers([])
loadingSave = false loadingSave = false
dispatch('deploy', $pathStore) onevent.deploy?.($pathStore)
} catch (err) { } catch (err) {
dispatch('deployError', err) onevent.deployError?.(err)
sendUserToast(`The flow could not be saved: ${err.body}`, true) sendUserToast(`The flow could not be saved: ${err.body}`, true)
loadingSave = false loadingSave = false
} }
@@ -651,7 +667,7 @@
if (savedFlow?.draft_only === false || savedFlow?.draft_only === undefined) { if (savedFlow?.draft_only === false || savedFlow?.draft_only === undefined) {
dropdownItems.push({ dropdownItems.push({
label: 'Exit & see details', label: 'Exit & see details',
onClick: () => dispatch('details', $pathStore) onClick: () => onevent.seeDetails?.($pathStore)
}) })
} }
@@ -777,7 +793,11 @@
{#key renderCount} {#key renderCount}
{#if !$userStore?.operator} {#if !$userStore?.operator}
{#if $pathStore} {#if $pathStore}
<FlowHistory bind:this={flowHistory} path={$pathStore} on:historyRestore /> <FlowHistory
bind:this={flowHistory}
path={$pathStore}
onHistoryRestore={() => onevent.historyRestore?.()}
/>
{/if} {/if}
<FlowYamlEditor bind:drawer={yamlEditorDrawer} /> <FlowYamlEditor bind:drawer={yamlEditorDrawer} />
<FlowImportExportMenu bind:drawer={jsonViewerDrawer} /> <FlowImportExportMenu bind:drawer={jsonViewerDrawer} />

View File

@@ -7,23 +7,41 @@
import SearchItems from './SearchItems.svelte' import SearchItems from './SearchItems.svelte'
type Item = Record<string, any> type Item = Record<string, any>
export let pickCallback: (path: string, f: string) => void
export let loadItems: () => Promise<Item[] | undefined>
export let extraField: string = 'path'
export let extraField2: string | undefined = undefined
export let itemName: string
export let closeOnClick = true
/** Displayed if the load function returns no items. */
export let noItemMessage = 'There are no items in the list'
/** Displayed if the search returns no items. */
export let buttons: Record<string, (x: string) => void> = {}
export let tooltip: string = ''
export let documentationLink: string | undefined = undefined
let loading = false interface Props {
let items: Item[] | undefined = [] pickCallback: (path: string, f: string) => void
let filteredItems: Item[] | undefined = [] loadItems: () => Promise<Item[] | undefined>
let filter = '' extraField?: string
extraField2?: string | undefined
itemName: string
closeOnClick?: boolean
/** Displayed if the load function returns no items. */
noItemMessage?: string
/** Displayed if the search returns no items. */
buttons?: Record<string, (x: string) => void>
tooltip?: string
documentationLink?: string | undefined
submission?: import('svelte').Snippet
}
let {
pickCallback,
loadItems,
extraField = 'path',
extraField2 = undefined,
itemName,
closeOnClick = true,
noItemMessage = 'There are no items in the list',
buttons = {},
tooltip = '',
documentationLink = undefined,
submission
}: Props = $props()
let loading = $state(false)
let items: Item[] | undefined = $state([])
let filteredItems: Item[] | undefined = $state([])
let filter = $state('')
export function openDrawer() { export function openDrawer() {
loading = true loading = true
@@ -34,12 +52,12 @@
.finally(() => { .finally(() => {
loading = false loading = false
}) })
drawer.openDrawer?.() drawer?.openDrawer?.()
} }
let drawer: Drawer let drawer: Drawer | undefined = $state()
let refreshing = false let refreshing = $state(false)
</script> </script>
<SearchItems <SearchItems
@@ -65,7 +83,7 @@
> >
<div class="w-full h-full flex flex-col"> <div class="w-full h-full flex flex-col">
<div class="flex flex-row gap-2 pb-4"> <div class="flex flex-row gap-2 pb-4">
<!-- svelte-ignore a11y-autofocus --> <!-- svelte-ignore a11y_autofocus -->
<input <input
type="text" type="text"
placeholder="Search {itemName}s" placeholder="Search {itemName}s"
@@ -110,9 +128,9 @@
<button <button
class="py-2 px-1 gap-1 flex grow border-gray-300 border-opacity-0 class="py-2 px-1 gap-1 flex grow border-gray-300 border-opacity-0
text-primary" text-primary"
on:click={() => { onclick={() => {
if (closeOnClick) { if (closeOnClick) {
drawer.closeDrawer() drawer?.closeDrawer()
} }
pickCallback(obj['path'], obj[extraField]) pickCallback(obj['path'], obj[extraField])
}} }}
@@ -174,8 +192,8 @@
<NoItemFound /> <NoItemFound />
{/if} {/if}
</div> </div>
<svelte:fragment slot="actions"> {#snippet actions()}
<slot name="submission" /> {@render submission?.()}
</svelte:fragment> {/snippet}
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>

View File

@@ -68,7 +68,7 @@
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
import { defaultScriptLanguages, processLangs } from '$lib/scripts' import { defaultScriptLanguages, processLangs } from '$lib/scripts'
import DefaultScripts from './DefaultScripts.svelte' import DefaultScripts from './DefaultScripts.svelte'
import { createEventDispatcher, onMount, setContext } from 'svelte' import { onMount, setContext } from 'svelte'
import Summary from './Summary.svelte' import Summary from './Summary.svelte'
import type { ScriptBuilderWhitelabelCustomUi } from './custom_ui' import type { ScriptBuilderWhitelabelCustomUi } from './custom_ui'
import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte' import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte'
@@ -112,6 +112,22 @@
export let savedPrimarySchedule: ScheduleTrigger | undefined = undefined export let savedPrimarySchedule: ScheduleTrigger | undefined = undefined
export let functionExports: ((exports: ScriptBuilderFunctionExports) => void) | undefined = export let functionExports: ((exports: ScriptBuilderFunctionExports) => void) | undefined =
undefined undefined
export let onevent: {
deploy?: (newHash: string) => void
deployError?: (error: Error) => void
saveInitial?: (path: string) => void
saveDraft?: ({
path,
savedAtNewPath,
script
}: {
path: string
savedAtNewPath: boolean
script: NewScript
}) => void
saveDraftError?: (error: Error) => void
seeDetails?: (path: string) => void
} = {}
export function getInitialAndModifiedValues(): SavedAndModifiedValue { export function getInitialAndModifiedValues(): SavedAndModifiedValue {
return { return {
@@ -183,8 +199,6 @@
loadTriggers() loadTriggers()
} }
const dispatch = createEventDispatcher()
$: initialPath != '' && loadTriggers() $: initialPath != '' && loadTriggers()
onMount(() => { onMount(() => {
@@ -566,10 +580,10 @@
script.parent_hash = newHash script.parent_hash = newHash
sendUserToast('Deployed') sendUserToast('Deployed')
} else { } else {
dispatch('deploy', newHash) onevent.deploy?.(newHash)
} }
} catch (error) { } catch (error) {
dispatch('deployError', error) onevent.deployError?.(error)
sendUserToast(`Error while saving the script: ${error.body || error.message}`, true) sendUserToast(`Error while saving the script: ${error.body || error.message}`, true)
} }
loadingSave = false loadingSave = false
@@ -701,9 +715,9 @@
if (initialPath == '' || (savedScript?.draft_only && script.path !== initialPath)) { if (initialPath == '' || (savedScript?.draft_only && script.path !== initialPath)) {
savedAtNewPath = true savedAtNewPath = true
initialPath = script.path initialPath = script.path
dispatch('saveInitial', script.path) onevent.saveInitial?.(script.path)
} }
dispatch('saveDraft', { path: script.path, savedAtNewPath, script }) onevent.saveDraft?.({ path: script.path, savedAtNewPath, script })
sendUserToast('Saved as draft') sendUserToast('Saved as draft')
} catch (error) { } catch (error) {
@@ -711,7 +725,7 @@
`Error while saving the script as a draft: ${error.body || error.message}`, `Error while saving the script as a draft: ${error.body || error.message}`,
true true
) )
dispatch('saveDraftError', error) onevent.saveDraftError?.(error)
} }
loadingDraft = false loadingDraft = false
} }
@@ -769,7 +783,7 @@
{ {
label: 'Exit & See details', label: 'Exit & See details',
onClick: () => { onClick: () => {
dispatch('seeDetails', initialPath) onevent.seeDetails?.(initialPath)
} }
} }
] ]
@@ -1686,12 +1700,8 @@
{customUi} {customUi}
collabMode collabMode
edit={initialPath != ''} edit={initialPath != ''}
on:format={() => { onFormat={() => saveDraft()}
saveDraft() onSaveDraft={() => saveDraft()}
}}
on:saveDraft={() => {
saveDraft()
}}
on:openTriggers={openTriggers} on:openTriggers={openTriggers}
on:applyArgs={applyArgs} on:applyArgs={applyArgs}
on:addPreprocessor={addPreprocessor} on:addPreprocessor={addPreprocessor}

View File

@@ -13,7 +13,7 @@
import EditorBar, { EDITOR_BAR_WIDTH_THRESHOLD } from './EditorBar.svelte' import EditorBar, { EDITOR_BAR_WIDTH_THRESHOLD } from './EditorBar.svelte'
import TestJobLoader from './TestJobLoader.svelte' import TestJobLoader from './TestJobLoader.svelte'
import JobProgressBar from '$lib/components/jobs/JobProgressBar.svelte' import JobProgressBar from '$lib/components/jobs/JobProgressBar.svelte'
import { createEventDispatcher, onDestroy, onMount } from 'svelte' import { onDestroy, onMount } from 'svelte'
import { Button } from './common' import { Button } from './common'
import SplitPanesWrapper from './splitPanes/SplitPanesWrapper.svelte' import SplitPanesWrapper from './splitPanes/SplitPanesWrapper.svelte'
import WindmillIcon from './icons/WindmillIcon.svelte' import WindmillIcon from './icons/WindmillIcon.svelte'
@@ -42,78 +42,114 @@
import HideButton from './apps/editor/settingsPanel/HideButton.svelte' import HideButton from './apps/editor/settingsPanel/HideButton.svelte'
import { base } from '$lib/base' import { base } from '$lib/base'
import { SUPPORTED_CHAT_SCRIPT_LANGUAGES } from './copilot/chat/script/core' import { SUPPORTED_CHAT_SCRIPT_LANGUAGES } from './copilot/chat/script/core'
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
import { getStringError } from './copilot/chat/utils' import { getStringError } from './copilot/chat/utils'
import type { ScriptOptions } from './copilot/chat/ContextManager.svelte' import type { ScriptOptions } from './copilot/chat/ContextManager.svelte'
import { aiChatManager, AIMode } from './copilot/chat/AIChatManager.svelte' import { aiChatManager, AIMode } from './copilot/chat/AIChatManager.svelte'
import TriggerableByAI from './TriggerableByAI.svelte' import TriggerableByAI from './TriggerableByAI.svelte'
// Exported interface Props {
export let schema: Schema | any = emptySchema() // Exported
export let code: string schema?: Schema | any
export let path: string | undefined code: string
export let lang: Preview['language'] path: string | undefined
export let kind: string | undefined = undefined lang: Preview['language']
export let template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative' = kind?: string | undefined
'script' template?: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative'
export let tag: string | undefined tag: string | undefined
export let initialArgs: Record<string, any> = {} initialArgs?: Record<string, any>
export let fixedOverflowWidgets = true fixedOverflowWidgets?: boolean
export let noSyncFromGithub = false noSyncFromGithub?: boolean
export let editor: Editor | undefined = undefined editor?: Editor | undefined
export let diffEditor: DiffEditor | undefined = undefined diffEditor?: DiffEditor | undefined
export let collabMode = false collabMode?: boolean
export let edit = true edit?: boolean
export let noHistory = false noHistory?: boolean
export let saveToWorkspace = false saveToWorkspace?: boolean
export let watchChanges = false watchChanges?: boolean
export let customUi: ScriptEditorWhitelabelCustomUi | undefined = undefined customUi?: ScriptEditorWhitelabelCustomUi | undefined
export let args: Record<string, any> = initialArgs args?: Record<string, any>
export let selectedTab: 'main' | 'preprocessor' = 'main' selectedTab?: 'main' | 'preprocessor'
export let hasPreprocessor = false hasPreprocessor?: boolean
export let captureTable: CaptureTable | undefined = undefined captureTable?: CaptureTable | undefined
export let showCaptures: boolean = true showCaptures?: boolean
export let stablePathForCaptures: string = '' stablePathForCaptures?: string
export let lastSavedCode: string | undefined = undefined lastSavedCode?: string | undefined
export let lastDeployedCode: string | undefined = undefined lastDeployedCode?: string | undefined
editor_bar_right?: import('svelte').Snippet
onChange?: (code: string, schema: Schema) => void
onFormat?: () => void
onSaveDraft?: () => void
}
let showHistoryDrawer = false let {
schema = $bindable(emptySchema()),
code = $bindable(),
path,
lang,
kind = undefined,
template = 'script',
tag,
initialArgs = {},
fixedOverflowWidgets = true,
noSyncFromGithub = false,
editor = $bindable(undefined),
diffEditor = $bindable(undefined),
collabMode = false,
edit = true,
noHistory = false,
saveToWorkspace = false,
watchChanges = false,
customUi = undefined,
args = $bindable(initialArgs),
selectedTab = $bindable('main'),
hasPreprocessor = $bindable(false),
captureTable = $bindable(undefined),
showCaptures = true,
stablePathForCaptures = '',
lastSavedCode = undefined,
lastDeployedCode = undefined,
editor_bar_right,
onChange,
onFormat,
onSaveDraft
}: Props = $props()
// export let onC
let jobProgressReset: () => void let showHistoryDrawer = $state(false)
let diffMode = false
let websocketAlive = { let jobProgressReset: (() => void) | undefined = $state()
let diffMode = $state(false)
let websocketAlive = $state({
pyright: false, pyright: false,
deno: false, deno: false,
go: false, go: false,
ruff: false, ruff: false,
shellcheck: false shellcheck: false
} })
const dispatch = createEventDispatcher() $effect(() => {
const dispatchIfMounted = createDispatcherIfMounted(dispatch) watchChanges && (code != undefined || schema != undefined) && onChange?.(code, schema)
})
$: watchChanges && let width = $state(1200)
(code != undefined || schema != undefined) &&
dispatchIfMounted('change', { code, schema })
let width = 1200 let testJobLoader: TestJobLoader | undefined = $state()
let testJobLoader: TestJobLoader let isValid: boolean = $state(true)
let scriptProgress = $state(undefined)
let isValid: boolean = true
let scriptProgress = undefined
// Test // Test
let testIsLoading = false let testIsLoading = $state(false)
let testJob: Job | undefined let testJob: Job | undefined = $state()
let pastPreviews: CompletedJob[] = [] let pastPreviews: CompletedJob[] = $state([])
let validCode = true let validCode = $state(true)
let logPanel: LogPanel | undefined = $state()
let wsProvider: WebsocketProvider | undefined = undefined let wsProvider: WebsocketProvider | undefined = $state(undefined)
let yContent: Y.Text | undefined = undefined let yContent: Y.Text | undefined = $state(undefined)
let peers: { name: string }[] = [] let peers: { name: string }[] = $state([])
let showCollabPopup = false let showCollabPopup = $state(false)
const url = new URL(window.location.toString()) const url = new URL(window.location.toString())
let initialCollab = /true|1/i.test(url.searchParams.get('collab') ?? '0') let initialCollab = /true|1/i.test(url.searchParams.get('collab') ?? '0')
@@ -292,11 +328,15 @@
return `${url}?collab=1` + (edit ? '' : `&path=${path}`) return `${url}?collab=1` + (edit ? '' : `&path=${path}`)
} }
$: showTabs = hasPreprocessor let showTabs = $derived(hasPreprocessor)
$: !hasPreprocessor && (selectedTab = 'main') $effect(() => {
$: selectedTab && inferSchema(code) !hasPreprocessor && (selectedTab = 'main')
})
$effect(() => {
selectedTab && inferSchema(code)
})
let argsRender = 0 let argsRender = $state(0)
export async function updateArgs(newArgs: Record<string, any>) { export async function updateArgs(newArgs: Record<string, any>) {
if (Object.keys(newArgs).length > 0) { if (Object.keys(newArgs).length > 0) {
args = { ...newArgs } args = { ...newArgs }
@@ -304,17 +344,21 @@
} }
} }
let setFocusToLogs = () => {} function setFocusToLogs() {
logPanel?.setFocusToLogs()
}
setContext('disableTooltips', customUi?.disableTooltips === true) setContext('disableTooltips', customUi?.disableTooltips === true)
let codePanelSize = 70 let codePanelSize = $state(70)
let testPanelSize = 30 let testPanelSize = $state(30)
let storedTestPanelSize = testPanelSize let storedTestPanelSize = testPanelSize
$: !SUPPORTED_CHAT_SCRIPT_LANGUAGES.includes(lang ?? '') && $effect(() => {
!aiChatManager.open && !SUPPORTED_CHAT_SCRIPT_LANGUAGES.includes(lang ?? '') &&
aiChatManager.toggleOpen() !aiChatManager.open &&
aiChatManager.toggleOpen()
})
function toggleTestPanel() { function toggleTestPanel() {
if (testPanelSize > 0) { if (testPanelSize > 0) {
@@ -348,9 +392,9 @@
editor?.show() editor?.show()
} }
$: error = getError(testJob) let error = $derived(getError(testJob))
$: { $effect(() => {
const options: ScriptOptions = { const options: ScriptOptions = {
code, code,
lang: lang as ScriptLang, lang: lang as ScriptLang,
@@ -367,7 +411,7 @@
editor?.reviewAndApplyCode(code) editor?.reviewAndApplyCode(code)
} }
aiChatManager.scriptEditorShowDiffMode = showDiffMode aiChatManager.scriptEditorShowDiffMode = showDiffMode
} })
</script> </script>
<TestJobLoader <TestJobLoader
@@ -378,7 +422,7 @@
bind:job={testJob} bind:job={testJob}
/> />
<svelte:window on:keydown={onKeyDown} /> <svelte:window onkeydown={onKeyDown} />
<TriggerableByAI id="script-editor" description="Component to edit a script" /> <TriggerableByAI id="script-editor" description="Component to edit a script" />
@@ -431,7 +475,9 @@
{diffMode} {diffMode}
bind:showHistoryDrawer bind:showHistoryDrawer
> >
<slot name="editor-bar-right" slot="right" /> {#snippet right()}
{@render editor_bar_right?.()}
{/snippet}
</EditorBar> </EditorBar>
{#if !noSyncFromGithub && customUi?.editorBar?.useVsCode != false} {#if !noSyncFromGithub && customUi?.editorBar?.useVsCode != false}
<div class="py-1"> <div class="py-1">
@@ -490,7 +536,7 @@
aiChatManager.toggleOpen() aiChatManager.toggleOpen()
}} }}
> >
<svelte:fragment slot="popoverOverride"> {#snippet popoverOverride()}
<div class="text-sm"> <div class="text-sm">
Enable Windmill AI in the <a Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai" href="{base}/workspace_settings?tab=ai"
@@ -500,7 +546,7 @@
workspace settings <ExternalLink size={16} /> workspace settings <ExternalLink size={16} />
</a> </a>
</div> </div>
</svelte:fragment> {/snippet}
</HideButton> </HideButton>
{/if} {/if}
{/if} {/if}
@@ -518,7 +564,7 @@
on:change={(e) => { on:change={(e) => {
inferSchema(e.detail) inferSchema(e.detail)
}} }}
on:saveDraft on:saveDraft={() => onSaveDraft?.()}
on:toggleAiPanel={() => aiChatManager.toggleOpen()} on:toggleAiPanel={() => aiChatManager.toggleOpen()}
on:addSelectedLinesToAiChat={(e) => { on:addSelectedLinesToAiChat={(e) => {
const { lines, startLine, endLine } = e.detail const { lines, startLine, endLine } = e.detail
@@ -536,7 +582,7 @@
} catch (e) { } catch (e) {
console.error('Could not save last_save to local storage', e) console.error('Could not save last_save to local storage', e)
} }
dispatch('format') onFormat?.()
}} }}
class="flex flex-1 h-full !overflow-visible" class="flex flex-1 h-full !overflow-visible"
scriptLang={lang} scriptLang={lang}
@@ -670,7 +716,7 @@
</Pane> </Pane>
<Pane size={67} class="relative"> <Pane size={67} class="relative">
<LogPanel <LogPanel
bind:setFocusToLogs bind:this={logPanel}
{lang} {lang}
previewJob={testJob} previewJob={testJob}
{pastPreviews} {pastPreviews}
@@ -690,7 +736,7 @@
compact={true} compact={true}
/> />
{/if} {/if}
<svelte:fragment slot="capturesTab"> {#snippet capturesTab()}
<div class="h-full p-2"> <div class="h-full p-2">
<CaptureTable <CaptureTable
bind:this={captureTable} bind:this={captureTable}
@@ -704,7 +750,7 @@
on:addPreprocessor on:addPreprocessor
/> />
</div> </div>
</svelte:fragment> {/snippet}
</LogPanel> </LogPanel>
</Pane> </Pane>
</Splitpanes> </Splitpanes>

View File

@@ -79,6 +79,10 @@
path: string, path: string,
opt?: Record<string, any> opt?: Record<string, any>
) => window.history.pushState(null, '', path) ) => window.history.pushState(null, '', path)
export let onevent: {
savedNewAppPath?: (path: string) => void
restore?: (app: App) => void
} = {}
migrateApp(app) migrateApp(app)
@@ -805,7 +809,7 @@
<AppEditorHeader <AppEditorHeader
{newPath} {newPath}
{newApp} {newApp}
on:restore on:restore={(e) => onevent.restore?.(e.detail)}
{policy} {policy}
{fromHub} {fromHub}
bind:this={appEditorHeader} bind:this={appEditorHeader}
@@ -815,7 +819,7 @@
leftPanelHidden={leftPanelSize === 0} leftPanelHidden={leftPanelSize === 0}
rightPanelHidden={rightPanelSize === 0} rightPanelHidden={rightPanelSize === 0}
bottomPanelHidden={runnablePanelSize === 0} bottomPanelHidden={runnablePanelSize === 0}
on:savedNewAppPath on:savedNewAppPath={(e) => onevent.savedNewAppPath?.(e.detail)}
on:showLeftPanel={() => showLeftPanel()} on:showLeftPanel={() => showLeftPanel()}
on:showRightPanel={() => showRightPanel()} on:showRightPanel={() => showRightPanel()}
on:hideLeftPanel={() => hideLeftPanel()} on:hideLeftPanel={() => hideLeftPanel()}

View File

@@ -13,17 +13,33 @@
} from 'lucide-svelte' } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
export let btnClasses: string | undefined = undefined interface Props {
export let size: ButtonType.Size = 'xs' btnClasses?: string | undefined
size?: ButtonType.Size
variant?: ButtonType.Variant
color?: ButtonType.Color
direction?: 'left' | 'right' | 'bottom'
hidden?: boolean
shortcut?: string | undefined
panelName?: string | undefined
customHiddenIcon?: ButtonType.Icon | undefined
usePopoverOverride?: boolean
popoverOverride?: import('svelte').Snippet
}
export let variant: ButtonType.Variant = 'contained' let {
export let color: ButtonType.Color = 'light' btnClasses = undefined,
export let direction: 'left' | 'right' | 'bottom' = 'right' size = 'xs',
export let hidden: boolean = false variant = 'contained',
export let shortcut: string | undefined = undefined color = 'light',
export let panelName: string | undefined = undefined direction = 'right',
export let customHiddenIcon: ButtonType.Icon | undefined = undefined hidden = false,
export let usePopoverOverride: boolean = false shortcut = undefined,
panelName = undefined,
customHiddenIcon = undefined,
usePopoverOverride = false,
popoverOverride
}: Props = $props()
const OpenIconMap = { const OpenIconMap = {
left: PanelLeftOpen, left: PanelLeftOpen,
@@ -45,9 +61,9 @@
</script> </script>
<Popover> <Popover>
<svelte:fragment slot="text"> {#snippet text()}
{#if usePopoverOverride && $$slots.popoverOverride} {#if usePopoverOverride && popoverOverride}
<slot name="popoverOverride" /> {@render popoverOverride?.()}
{:else} {:else}
<div class="flex flex-row gap-1"> <div class="flex flex-row gap-1">
{hidden ? 'Show' : 'Hide '} the {panelName ?? direction} panel. {hidden ? 'Show' : 'Hide '} the {panelName ?? direction} panel.
@@ -57,7 +73,7 @@
</div> </div>
</div> </div>
{/if} {/if}
</svelte:fragment> {/snippet}
<Button <Button
iconOnly iconOnly
startIcon={hidden startIcon={hidden

View File

@@ -5,17 +5,35 @@
import TriggerableByAI from '$lib/components/TriggerableByAI.svelte' import TriggerableByAI from '$lib/components/TriggerableByAI.svelte'
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
export let aiId: string | undefined = undefined interface Props {
export let aiDescription: string | undefined = undefined aiId?: string | undefined
export let title: string | undefined = undefined aiDescription?: string | undefined
export let overflow_y = true title?: string | undefined
export let noPadding = false overflow_y?: boolean
export let forceOverflowVisible = false noPadding?: boolean
export let tooltip: string = '' forceOverflowVisible?: boolean
export let documentationLink: string | undefined = undefined tooltip?: string
export let CloseIcon: any | undefined = undefined documentationLink?: string | undefined
CloseIcon?: any | undefined
fullScreen?: boolean
actions?: import('svelte').Snippet
children?: import('svelte').Snippet
}
export let fullScreen: boolean = true let {
aiId = undefined,
aiDescription = undefined,
title = undefined,
overflow_y = true,
noPadding = false,
forceOverflowVisible = false,
tooltip = '',
documentationLink = undefined,
CloseIcon = undefined,
fullScreen = true,
actions,
children
}: Props = $props()
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
</script> </script>
@@ -39,9 +57,9 @@
{/if}</span {/if}</span
> >
</div> </div>
{#if $$slots.actions} {#if actions}
<div class="flex gap-2 items-center justify-end"> <div class="flex gap-2 items-center justify-end">
<slot name="actions" /> {@render actions?.()}
</div> </div>
{/if} {/if}
</div> </div>
@@ -54,6 +72,6 @@
)} )}
class:overflow-y-auto={overflow_y} class:overflow-y-auto={overflow_y}
> >
<slot /> {@render children?.()}
</div> </div>
</div> </div>

View File

@@ -0,0 +1,76 @@
<script lang="ts">
import { setContext } from 'svelte'
import { writable } from 'svelte/store'
import { twMerge } from 'tailwind-merge'
import type { TabsContext } from './tabs.svelte'
interface Props {
selected: string
hideTabs?: boolean
class?: string
wrapperClass?: string
style?: string
hashNavigation?: boolean
values?: string[] | undefined
children?: import('svelte').Snippet<[any]>
content?: import('svelte').Snippet
onSelectedChange?: (value: string) => void
}
let {
selected = $bindable(),
hideTabs = false,
class: c = '',
wrapperClass = '',
style = '',
hashNavigation = false,
values = undefined,
children,
content,
onSelectedChange
}: Props = $props()
const selectedStore = writable(selected)
setContext<TabsContext>('Tabs', {
selected: selectedStore,
update: (value: string) => {
selectedStore.set(value)
selected = value
},
hashNavigation
})
function updateSelected() {
selectedStore.set(selected)
}
function hashChange() {
if (hashNavigation) {
const hash = window.location.hash
if (hash && hashValues?.includes(hash)) {
const id = hash.replace('#', '')
selectedStore.set(id)
selected = id
}
}
}
$effect(() => {
selected && updateSelected()
})
$effect(() => {
$selectedStore && onSelectedChange?.($selectedStore)
})
let hashValues = $derived(values ? values.map((x) => '#' + x) : undefined)
</script>
<svelte:window onhashchange={hashChange} />
{#if !hideTabs}
<div class="overflow-x-auto {wrapperClass}">
<div class={twMerge('border-b flex flex-row whitespace-nowrap scrollbar-hidden', c)} {style}>
{@render children?.({ selected })}
</div>
</div>
{/if}
{@render content?.()}

View File

@@ -0,0 +1,7 @@
import type { Writable } from 'svelte/store'
export type TabsContext = {
selected: Writable<string>
update: (value: string) => void
hashNavigation: boolean
}

View File

@@ -1,18 +1,20 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'
import Drawer from '../common/drawer/Drawer.svelte' import Drawer from '../common/drawer/Drawer.svelte'
import DrawerContent from '../common/drawer/DrawerContent.svelte' import DrawerContent from '../common/drawer/DrawerContent.svelte'
import FlowHistoryInner from './FlowHistoryInner.svelte' import FlowHistoryInner from './FlowHistoryInner.svelte'
export let path: string interface Props {
let drawer: Drawer path: string
onHistoryRestore: () => void
export function open() {
drawer.openDrawer()
} }
const dispatch = createEventDispatcher() let { path, onHistoryRestore }: Props = $props()
let drawer: Drawer | undefined = undefined
export function open() {
drawer?.openDrawer()
}
</script> </script>
<Drawer bind:this={drawer} size="1200px"> <Drawer bind:this={drawer} size="1200px">
@@ -26,8 +28,8 @@
<FlowHistoryInner <FlowHistoryInner
allowFork allowFork
on:historyRestore={() => { on:historyRestore={() => {
drawer.closeDrawer() drawer?.closeDrawer()
dispatch('historyRestore') onHistoryRestore()
}} }}
{path} {path}
/> />

View File

@@ -198,9 +198,7 @@
{#key script.hash} {#key script.hash}
<ScriptEditor <ScriptEditor
showCaptures={false} showCaptures={false}
on:saveDraft={() => { onSaveDraft={() => saveScript()}
saveScript()
}}
noSyncFromGithub noSyncFromGithub
lang={script.language} lang={script.language}
path={script.path} path={script.path}
@@ -209,7 +207,7 @@
bind:code={script.content} bind:code={script.content}
bind:schema={script.schema} bind:schema={script.schema}
> >
<div slot="editor-bar-right"> <div slot="editor_bar_right">
<WorkerTagSelect bind:tag={script.tag} /> <WorkerTagSelect bind:tag={script.tag} />
</div> </div>
</ScriptEditor> </ScriptEditor>

View File

@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy'
import { import {
type CompletedJob, type CompletedJob,
type Job, type Job,
@@ -10,11 +12,9 @@
import { workspaceStore } from '$lib/stores' import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base' import { base } from '$lib/base'
import { displayDate } from '$lib/utils' import { displayDate } from '$lib/utils'
import Tabs from '../common/tabs/Tabs.svelte'
import Tab from '../common/tabs/Tab.svelte' import Tab from '../common/tabs/Tab.svelte'
import DisplayResult from '../DisplayResult.svelte' import DisplayResult from '../DisplayResult.svelte'
import Drawer from '../common/drawer/Drawer.svelte' import Drawer from '../common/drawer/Drawer.svelte'
import DrawerContent from '../common/drawer/DrawerContent.svelte'
import HighlightCode from '../HighlightCode.svelte' import HighlightCode from '../HighlightCode.svelte'
import LogViewer from '../LogViewer.svelte' import LogViewer from '../LogViewer.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes' import { Pane, Splitpanes } from 'svelte-splitpanes'
@@ -30,33 +30,54 @@
import Tooltip from '$lib/components/Tooltip.svelte' import Tooltip from '$lib/components/Tooltip.svelte'
import type { PreviewPanelUi } from '../custom_ui' import type { PreviewPanelUi } from '../custom_ui'
import { getStringError } from '../copilot/chat/utils' import { getStringError } from '../copilot/chat/utils'
import { DrawerContent } from '../common'
import TabsV2 from '../common/tabs/TabsV2.svelte'
export let lang: Preview['language'] | undefined interface Props {
export let previewIsLoading = false lang: Preview['language'] | undefined
export let previewJob: Job | undefined previewIsLoading?: boolean
export let pastPreviews: CompletedJob[] = [] previewJob: Job | undefined
export let editor: Editor | undefined = undefined pastPreviews?: CompletedJob[]
export let diffEditor: DiffEditor | undefined = undefined editor?: Editor | undefined
export let args: Record<string, any> | undefined = undefined diffEditor?: DiffEditor | undefined
export let workspace: string | undefined = undefined args?: Record<string, any> | undefined
export let showCaptures: boolean = false workspace?: string | undefined
export let customUi: PreviewPanelUi | undefined = undefined showCaptures?: boolean
customUi?: PreviewPanelUi | undefined
children?: import('svelte').Snippet
capturesTab?: import('svelte').Snippet
}
type DrawerContent = { let {
lang,
previewIsLoading = false,
previewJob,
pastPreviews = [],
editor = undefined,
diffEditor = undefined,
args = undefined,
workspace = undefined,
showCaptures = false,
customUi = undefined,
children,
capturesTab
}: Props = $props()
type DContent = {
mode: 'json' | Preview['language'] | 'plain' mode: 'json' | Preview['language'] | 'plain'
title: string title: string
content: any content: any
} }
let selectedTab = 'logs' let selectedTab = $state('logs')
let drawerOpen: boolean = false let drawerOpen: boolean = $state(false)
let drawerContent: DrawerContent | undefined = undefined let drawerContent: DContent | undefined = $state(undefined)
export function setFocusToLogs() { export function setFocusToLogs() {
selectedTab = 'logs' selectedTab = 'logs'
} }
function openDrawer(newContent: DrawerContent) { function openDrawer(newContent: DContent) {
drawerContent = newContent drawerContent = newContent
drawerOpen = true drawerOpen = true
} }
@@ -69,7 +90,7 @@
return x as Record<string, WorkflowStatus> return x as Record<string, WorkflowStatus>
} }
let forceJson = false let forceJson = $state(false)
</script> </script>
<Drawer bind:open={drawerOpen} size="800px"> <Drawer bind:open={drawerOpen} size="800px">
@@ -94,7 +115,7 @@
</Drawer> </Drawer>
<div class="h-full flex flex-col"> <div class="h-full flex flex-col">
<Tabs bind:selected={selectedTab} class="pt-1" wrapperClass="flex-none"> <TabsV2 bind:selected={selectedTab} class="pt-1" wrapperClass="flex-none">
<Tab value="logs" size="xs">Logs & Result</Tab> <Tab value="logs" size="xs">Logs & Result</Tab>
{#if customUi?.disableHistory !== true} {#if customUi?.disableHistory !== true}
<Tab value="history" size="xs">History</Tab> <Tab value="history" size="xs">History</Tab>
@@ -103,7 +124,7 @@
<Tab value="captures" size="xs">Trigger captures</Tab> <Tab value="captures" size="xs">Trigger captures</Tab>
{/if} {/if}
<svelte:fragment slot="content"> {#snippet content()}
<div class="grow min-h-0"> <div class="grow min-h-0">
{#if selectedTab === 'logs'} {#if selectedTab === 'logs'}
<SplitPanesWrapper> <SplitPanesWrapper>
@@ -128,7 +149,7 @@
/> />
</Pane> </Pane>
<Pane> <Pane>
<slot /> {@render children?.()}
{#if previewJob != undefined && 'result' in previewJob} {#if previewJob != undefined && 'result' in previewJob}
<div class="relative w-full h-full p-2"> <div class="relative w-full h-full p-2">
<div class="relative"> <div class="relative">
@@ -140,6 +161,7 @@
customUi={customUi?.displayResult} customUi={customUi?.displayResult}
language={lang} language={lang}
> >
<!-- @migration-task: migrate this slot by hand, `copilot-fix` is an invalid identifier -->
<svelte:fragment slot="copilot-fix"> <svelte:fragment slot="copilot-fix">
{#if lang && editor && diffEditor && args && previewJob && !previewJob.success && getStringError(previewJob.result)} {#if lang && editor && diffEditor && args && previewJob && !previewJob.success && getStringError(previewJob.result)}
<ScriptFix {lang} /> <ScriptFix {lang} />
@@ -203,7 +225,7 @@
<Cell> <Cell>
<button <button
class="text-xs" class="text-xs"
on:click|preventDefault={() => { onclick={preventDefault(() => {
openDrawer({ mode: 'json', content: undefined, title: 'Result' }) openDrawer({ mode: 'json', content: undefined, title: 'Result' })
JobService.getCompletedJobResult({ JobService.getCompletedJobResult({
workspace: workspace ?? $workspaceStore ?? 'NO_W', workspace: workspace ?? $workspaceStore ?? 'NO_W',
@@ -211,7 +233,7 @@
}).then((res) => { }).then((res) => {
drawerContent && (drawerContent.content = res) drawerContent && (drawerContent.content = res)
}) })
}} })}
> >
See Result See Result
</button> </button>
@@ -219,7 +241,7 @@
<Cell> <Cell>
<button <button
class="text-xs" class="text-xs"
on:click|preventDefault={async () => { onclick={preventDefault(async () => {
const code = ( const code = (
await JobService.getCompletedJob({ await JobService.getCompletedJob({
workspace: workspace ?? $workspaceStore ?? 'NO_W', workspace: workspace ?? $workspaceStore ?? 'NO_W',
@@ -231,7 +253,7 @@
content: String(code), content: String(code),
title: `Code ${lang}` title: `Code ${lang}`
}) })
}} })}
> >
View code View code
</button> </button>
@@ -239,7 +261,7 @@
<Cell last> <Cell last>
<button <button
class="text-xs" class="text-xs"
on:click|preventDefault={async () => { onclick={preventDefault(async () => {
const logs = await ( const logs = await (
await fetch( await fetch(
OpenAPI.BASE + OpenAPI.BASE +
@@ -252,7 +274,7 @@
content: String(logs), content: String(logs),
title: `Logs for ${id}` title: `Logs for ${id}`
}) })
}} })}
> >
View logs View logs
</button> </button>
@@ -264,9 +286,9 @@
</div> </div>
{/if} {/if}
{#if selectedTab === 'captures'} {#if selectedTab === 'captures'}
<slot name="capturesTab" /> {@render capturesTab?.()}
{/if} {/if}
</div> </div>
</svelte:fragment> {/snippet}
</Tabs> </TabsV2>
</div> </div>

View File

@@ -165,14 +165,16 @@
<!-- <div id="monaco-widgets-root" class="monaco-editor" style="z-index: 1200;" /> --> <!-- <div id="monaco-widgets-root" class="monaco-editor" style="z-index: 1200;" /> -->
<FlowBuilder <FlowBuilder
on:saveInitial={(e) => { onevent={{
goto(`/flows/edit/${e.detail}?selected=${getSelectedId?.()}`) saveInitial: (path) => {
}} goto(`/flows/edit/${path}?selected=${getSelectedId?.()}`)
on:deploy={(e) => { },
goto(`/flows/get/${e.detail}?workspace=${$workspaceStore}`) deploy: (path) => {
}} goto(`/flows/get/${path}?workspace=${$workspaceStore}`)
on:details={(e) => { },
goto(`/flows/get/${e.detail}?workspace=${$workspaceStore}`) seeDetails: (path) => {
goto(`/flows/get/${path}?workspace=${$workspaceStore}`)
}
}} }}
{initialPath} {initialPath}
{pathStoreInit} {pathStoreInit}

View File

@@ -244,18 +244,19 @@
<DiffDrawer bind:this={diffDrawer} {restoreDeployed} {restoreDraft} /> <DiffDrawer bind:this={diffDrawer} {restoreDeployed} {restoreDraft} />
<FlowBuilder <FlowBuilder
on:deploy={(e) => { onevent={{
goto(`/flows/get/${e.detail}?workspace=${$workspaceStore}`) deploy: (path) => {
}} goto(`/flows/get/${path}?workspace=${$workspaceStore}`)
on:details={(e) => { },
goto(`/flows/get/${e.detail}?workspace=${$workspaceStore}`) seeDetails: (path) => {
}} goto(`/flows/get/${path}?workspace=${$workspaceStore}`)
on:saveDraftOnlyAtNewPath={(e) => { },
const { path, selectedId } = e.detail saveDraftOnlyAtNewPath: ({ path, selectedId }) => {
goto(`/flows/edit/${path}?selected=${selectedId}`) goto(`/flows/edit/${path}?selected=${selectedId}`)
}} },
on:historyRestore={() => { historyRestore: () => {
loadFlow() loadFlow()
}
}} }}
{flowStore} {flowStore}
{flowStateStore} {flowStateStore}

View File

@@ -108,13 +108,13 @@
{initialArgs} {initialArgs}
bind:this={scriptBuilder} bind:this={scriptBuilder}
lockedLanguage={templatePath != null || hubPath != null} lockedLanguage={templatePath != null || hubPath != null}
on:deploy={(e) => { onevent={{
let newHash = e.detail deploy: (newHash) => {
goto(`/scripts/get/${newHash}?workspace=${$workspaceStore}`) goto(`/scripts/get/${newHash}?workspace=${$workspaceStore}`)
}} },
on:saveInitial={(e) => { saveInitial: (path) => {
let path = e.detail goto(`/scripts/edit/${path}`)
goto(`/scripts/edit/${path}`) }
}} }}
bind:getInitialAndModifiedValues bind:getInitialAndModifiedValues
searchParams={$page.url.searchParams} searchParams={$page.url.searchParams}

View File

@@ -27,7 +27,6 @@
let scriptLoadedFromUrl = initialState != undefined ? decodeState(initialState) : undefined let scriptLoadedFromUrl = initialState != undefined ? decodeState(initialState) : undefined
let script: (NewScript & { draft_triggers?: Trigger[] }) | undefined = undefined let script: (NewScript & { draft_triggers?: Trigger[] }) | undefined = undefined
let initialPath: string = '' let initialPath: string = ''
@@ -218,19 +217,18 @@
{diffDrawer} {diffDrawer}
{savedPrimarySchedule} {savedPrimarySchedule}
searchParams={$page.url.searchParams} searchParams={$page.url.searchParams}
on:deploy={(e) => { onevent={{
let newHash = e.detail deploy: (newHash) => {
goto(`/scripts/get/${newHash}?workspace=${$workspaceStore}`) goto(`/scripts/get/${newHash}?workspace=${$workspaceStore}`)
},
saveInitial: (path) => {
goto(`/scripts/edit/${path}`)
},
saveDraft: ({ path, savedAtNewPath, script }) => {
goto(`/scripts/edit/${path}`)
}
}} }}
bind:getInitialAndModifiedValues bind:getInitialAndModifiedValues
on:saveInitial={(e) => {
let path = e.detail
goto(`/scripts/edit/${path}`)
}}
on:seeDetails={(e) => {
let path = e.detail
goto(`/scripts/get/${path}?workspace=${$workspaceStore}`)
}}
replaceStateFn={(path) => { replaceStateFn={(path) => {
replaceState(path, $page.state) replaceState(path, $page.state)
}} }}