mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 08:02:19 +00:00
fix(frontend): forking UI issues (#7215)
* fix(frontend): forking UI issues * fix dark mode
This commit is contained in:
@@ -138,7 +138,7 @@
|
||||
}
|
||||
|
||||
export async function reset() {
|
||||
if (path == '' || path == 'u//') {
|
||||
if (path == '' || path == 'u//' || path?.startsWith('tmp/')) {
|
||||
if ($lastMetaUsed == undefined || $lastMetaUsed.owner != $userStore?.username) {
|
||||
meta = {
|
||||
ownerKind: hideUser ? 'folder' : 'user',
|
||||
@@ -323,12 +323,12 @@
|
||||
|
||||
async function initPath() {
|
||||
await tick()
|
||||
if (path != undefined && path != '') {
|
||||
if (path != undefined && path != '' && !path?.startsWith('tmp/')) {
|
||||
meta = pathToMeta(path, hideUser)
|
||||
onMetaChange()
|
||||
return
|
||||
}
|
||||
if (initialPath == undefined || initialPath == '') {
|
||||
if (initialPath == undefined || initialPath == '' || initialPath?.startsWith('tmp/')) {
|
||||
reset()
|
||||
} else {
|
||||
meta = pathToMeta(initialPath, hideUser)
|
||||
|
||||
@@ -379,7 +379,7 @@
|
||||
if (templateScript) {
|
||||
script.content += '\r\n' + templateScript
|
||||
}
|
||||
scriptEditor?.inferSchema(script.content, language, true)
|
||||
scriptEditor?.inferSchema(script.content, { nlang: language, resetArgs: true })
|
||||
if (script.content != editor?.getCode()) {
|
||||
setCode(script.content)
|
||||
}
|
||||
@@ -567,7 +567,7 @@
|
||||
if (!disableHistoryChange) {
|
||||
history.replaceState(history.state, '', `/scripts/edit/${script.path}`)
|
||||
}
|
||||
if (stay || script.kind !== 'script' || script.no_main_func) {
|
||||
if (stay || (script.no_main_func && script.kind !== 'preprocessor')) {
|
||||
script.parent_hash = newHash
|
||||
sendUserToast('Deployed')
|
||||
} else {
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
enablePreprocessorSnippet = false
|
||||
}: Props = $props()
|
||||
|
||||
let initialArgs = structuredClone($state.snapshot(args))
|
||||
|
||||
$effect.pre(() => {
|
||||
if (schema == undefined) {
|
||||
schema = emptySchema()
|
||||
@@ -269,7 +271,18 @@
|
||||
})
|
||||
}
|
||||
|
||||
export async function inferSchema(code: string, nlang?: SupportedLanguage, resetArgs = false) {
|
||||
export async function inferSchema(
|
||||
code: string,
|
||||
{
|
||||
nlang,
|
||||
resetArgs = false,
|
||||
applyInitialArgs = false
|
||||
}: {
|
||||
nlang?: SupportedLanguage
|
||||
resetArgs?: boolean
|
||||
applyInitialArgs?: boolean
|
||||
} = {}
|
||||
) {
|
||||
let nschema = schema ?? emptySchema()
|
||||
|
||||
try {
|
||||
@@ -297,6 +310,10 @@
|
||||
if (resetArgs) {
|
||||
args = {}
|
||||
}
|
||||
if (applyInitialArgs) {
|
||||
// we reapply initial args as the schema form might have cleared them between mount and the schema inference
|
||||
args = initialArgs
|
||||
}
|
||||
schema = nschema
|
||||
} catch (e) {
|
||||
validCode = false
|
||||
@@ -338,7 +355,7 @@
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
inferSchema(code)
|
||||
inferSchema(code, { applyInitialArgs: true })
|
||||
loadPastTests()
|
||||
aiChatManager.saveAndClear()
|
||||
aiChatManager.changeMode(AIMode.SCRIPT)
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<div class="flex flex-col h-screen bg-surface">
|
||||
<!-- Top bar -->
|
||||
<div class="h-12 px-4 border-b flex items-center gap-4">
|
||||
<div class="w-8 h-8 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
<div class="flex-1 h-6 max-w-md rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
</div>
|
||||
|
||||
<!-- Editor bar -->
|
||||
<div class="border-b shadow-sm px-4 py-2 flex gap-2">
|
||||
<div class="w-20 h-6 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
<div class="w-20 h-6 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
<div class="w-20 h-6 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
<div class="flex-1"></div>
|
||||
<div class="w-24 h-6 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
</div>
|
||||
|
||||
<!-- Main content area -->
|
||||
<div class="flex flex-1 overflow-hidden">
|
||||
<!-- Left panel - Editor -->
|
||||
<div class="flex-1 min-w-0 border-r bg-surface-secondary">
|
||||
<div class="w-full h-full p-4">
|
||||
<div class="w-32 h-4 mb-3 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
<div class="w-48 h-4 mb-3 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
<div class="w-40 h-4 mb-3 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
<div class="w-56 h-4 mb-3 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right panel - Test/Preview with horizontal split -->
|
||||
<div class="w-2/5 min-w-0 flex flex-col bg-surface">
|
||||
<!-- Top section - test inputs -->
|
||||
<div class="flex-1 border-b flex flex-col">
|
||||
<div class="h-10 border-b px-4 flex items-center">
|
||||
<div class="w-24 h-4 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
</div>
|
||||
<div class="flex-1 p-4 space-y-3">
|
||||
<div class="w-full h-8 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
<div class="w-full h-8 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
<div class="w-32 h-8 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom section - results -->
|
||||
<div class="flex-1 flex flex-col">
|
||||
<div class="h-10 border-b px-4 flex items-center">
|
||||
<div class="w-24 h-4 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
</div>
|
||||
<div class="flex-1 p-4">
|
||||
<div class="w-full h-4 mb-2 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
<div class="w-3/4 h-4 rounded animate-skeleton dark:bg-frost-900/50"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,6 +29,9 @@
|
||||
{disabled}
|
||||
class={disabled ? 'border-0' : ''}
|
||||
allowClear={!disabled}
|
||||
onCreateItem={(c) => worker_tags.push(c)}
|
||||
onCreateItem={(c) => {
|
||||
worker_tags.push(c)
|
||||
dispatch('dirty')
|
||||
}}
|
||||
createText="Press Enter to use this tag"
|
||||
/>
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
<Row
|
||||
aiId={`script-run-button-${script.path}`}
|
||||
aiDescription={`Button to access the form to run the script ${script.summary ?? script.path}`}
|
||||
href={script.draft_only || script.kind !== 'script' || script.no_main_func
|
||||
href={script.draft_only || (script.no_main_func && script.kind !== 'preprocessor')
|
||||
? `${base}/scripts/edit/${script.path}`
|
||||
: `${base}/scripts/get/${script.hash}?workspace=${$workspaceStore}`}
|
||||
kind="script"
|
||||
|
||||
@@ -27,8 +27,13 @@
|
||||
let nodraft = page.url.searchParams.get('nodraft')
|
||||
const initialState = nodraft ? undefined : localStorage.getItem(`flow-${page.params.path}`)
|
||||
let stateLoadedFromUrl = initialState != undefined ? decodeState(initialState) : undefined
|
||||
|
||||
const urlArgs = page.url.searchParams.get('initial_args')
|
||||
|
||||
let initialArgs = $state({})
|
||||
if ($initialArgsStore) {
|
||||
if (urlArgs) {
|
||||
initialArgs = decodeState(urlArgs)
|
||||
} else if ($initialArgsStore) {
|
||||
initialArgs = $initialArgsStore
|
||||
$initialArgsStore = undefined
|
||||
}
|
||||
|
||||
@@ -320,7 +320,8 @@
|
||||
flow: { value: job?.raw_flow },
|
||||
path: job?.script_path + '_fork'
|
||||
}
|
||||
window.open(`/flows/add#${encodeState(state)}`)
|
||||
const encodedArgs = encodeState(job?.args)
|
||||
window.open(`/flows/add?initial_args=${encodedArgs}#${encodeState(state)}`)
|
||||
} else {
|
||||
$initialArgsStore = job?.args
|
||||
let n: NewScript = {
|
||||
@@ -328,9 +329,11 @@
|
||||
summary: 'Fork of preview of ' + job?.script_path,
|
||||
language: job?.language as NewScript['language'],
|
||||
description: '',
|
||||
content: job?.raw_code ?? ''
|
||||
content: job?.raw_code ?? '',
|
||||
kind: 'script'
|
||||
}
|
||||
window.open(`/scripts/add#${encodeState(n)}`)
|
||||
const encodedArgs = encodeState(job?.args)
|
||||
window.open(`/scripts/add?initial_args=${encodedArgs}#${encodeState(n)}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { type NewScript, ScriptService, type Script } from '$lib/gen'
|
||||
import { type NewScript, ScriptService, type ScriptLang } from '$lib/gen'
|
||||
|
||||
import { page } from '$app/stores'
|
||||
import { defaultScripts, initialArgsStore, workspaceStore } from '$lib/stores'
|
||||
import ScriptBuilder from '$lib/components/ScriptBuilder.svelte'
|
||||
import type { Schema } from '$lib/common'
|
||||
import { decodeState, emptySchema, emptyString } from '$lib/utils'
|
||||
import { decodeState, emptySchema, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { goto } from '$lib/navigation'
|
||||
import { replaceState } from '$app/navigation'
|
||||
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
|
||||
@@ -13,6 +13,13 @@
|
||||
import type { Trigger } from '$lib/components/triggers/utils'
|
||||
import { get } from 'svelte/store'
|
||||
import { untrack } from 'svelte'
|
||||
import ScriptEditorSkeleton from '$lib/components/ScriptEditorSkeleton.svelte'
|
||||
|
||||
type Script = NewScript & {
|
||||
draft_triggers?: Trigger[]
|
||||
hash?: string
|
||||
extra_perms?: Record<string, any>
|
||||
}
|
||||
|
||||
// Default
|
||||
let schema: Schema = emptySchema()
|
||||
@@ -20,8 +27,9 @@
|
||||
const templatePath = $page.url.searchParams.get('template')
|
||||
const hubPath = $page.url.searchParams.get('hub')
|
||||
const showMeta = /true|1/i.test($page.url.searchParams.get('show_meta') ?? '0')
|
||||
const urlArgs = $page.url.searchParams.get('initial_args')
|
||||
|
||||
let initialArgs = get(initialArgsStore) ?? {}
|
||||
let initialArgs = urlArgs ? decodeState(urlArgs) : (get(initialArgsStore) ?? {})
|
||||
if (get(initialArgsStore)) $initialArgsStore = undefined
|
||||
|
||||
const path = $page.url.searchParams.get('path')
|
||||
@@ -40,54 +48,74 @@
|
||||
}
|
||||
}
|
||||
|
||||
function defaultScript() {
|
||||
function defaultScript(): Script {
|
||||
return {
|
||||
hash: '',
|
||||
path: path ?? '',
|
||||
summary: '',
|
||||
content: '',
|
||||
description: '',
|
||||
schema: schema,
|
||||
is_template: false,
|
||||
extra_perms: {},
|
||||
language:
|
||||
$defaultScripts?.order?.filter(
|
||||
(x) => $defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x)
|
||||
)?.[0] ?? 'bun',
|
||||
language: ($defaultScripts?.order?.filter(
|
||||
(x) => $defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x)
|
||||
)?.[0] ?? 'bun') as ScriptLang,
|
||||
kind: 'script'
|
||||
}
|
||||
}
|
||||
|
||||
let script: NewScript & { draft_triggers: Trigger[] } = $state(
|
||||
!path && initialState != undefined ? decodeStateAndHandleError(initialState) : defaultScript()
|
||||
let script: Script | undefined = $state(
|
||||
templatePath || hubPath
|
||||
? undefined
|
||||
: !path && initialState != undefined
|
||||
? decodeStateAndHandleError(initialState)
|
||||
: defaultScript()
|
||||
)
|
||||
|
||||
async function loadTemplate(): Promise<void> {
|
||||
if (templatePath) {
|
||||
const template = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: templatePath
|
||||
})
|
||||
|
||||
// Only copy the summary if it's not empty
|
||||
script.summary = !emptyString(template.summary) ? `Copy of ${template.summary}` : ''
|
||||
script.description = template.description
|
||||
script.content = template.content
|
||||
script.schema = template.schema
|
||||
script.language = template.language
|
||||
scriptBuilder?.setCode(script.content)
|
||||
try {
|
||||
const template = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: templatePath
|
||||
})
|
||||
script = {
|
||||
...defaultScript(),
|
||||
summary: !emptyString(template.summary) ? `Copy of ${template.summary}` : '',
|
||||
description: template.description,
|
||||
content: template.content,
|
||||
schema: template.schema,
|
||||
language: template.language,
|
||||
path: template.path + '_fork'
|
||||
}
|
||||
} catch (err) {
|
||||
script = defaultScript()
|
||||
console.error('Error loading template', err)
|
||||
sendUserToast('Error loading template: ' + err.message, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHub(): Promise<void> {
|
||||
if (hubPath) {
|
||||
const { content, language, summary } = await ScriptService.getHubScriptByPath({
|
||||
path: hubPath
|
||||
})
|
||||
script.description = `Fork of ${hubPath}`
|
||||
script.content = replaceScriptPlaceholderWithItsValues(hubPath, content)
|
||||
script.summary = summary ?? ''
|
||||
script.language = language as Script['language']
|
||||
scriptBuilder?.setCode(script.content)
|
||||
try {
|
||||
const { content, language, summary } = await ScriptService.getHubScriptByPath({
|
||||
path: hubPath
|
||||
})
|
||||
script = {
|
||||
...defaultScript(),
|
||||
description: `Fork of ${hubPath}`,
|
||||
content: replaceScriptPlaceholderWithItsValues(hubPath, content),
|
||||
summary: summary ?? '',
|
||||
language: language as Script['language'],
|
||||
path: hubPath + '_fork'
|
||||
}
|
||||
} catch (err) {
|
||||
script = defaultScript()
|
||||
console.error('Error loading script from hub', err)
|
||||
sendUserToast('Error loading script from hub: ' + err.message, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,22 +128,26 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<ScriptBuilder
|
||||
{initialArgs}
|
||||
bind:this={scriptBuilder}
|
||||
lockedLanguage={templatePath != null || hubPath != null}
|
||||
onDeploy={(e) => {
|
||||
goto(`/scripts/get/${e.hash}?workspace=${$workspaceStore}`)
|
||||
}}
|
||||
onSaveInitial={(e) => {
|
||||
goto(`/scripts/edit/${e.path}`)
|
||||
}}
|
||||
searchParams={$page.url.searchParams}
|
||||
bind:script
|
||||
{showMeta}
|
||||
replaceStateFn={(path) => replaceState(path, $page.state)}
|
||||
>
|
||||
<UnsavedConfirmationModal
|
||||
getInitialAndModifiedValues={scriptBuilder?.getInitialAndModifiedValues}
|
||||
/>
|
||||
</ScriptBuilder>
|
||||
{#if script}
|
||||
<ScriptBuilder
|
||||
{initialArgs}
|
||||
bind:this={scriptBuilder}
|
||||
lockedLanguage={templatePath != null || hubPath != null}
|
||||
onDeploy={(e) => {
|
||||
goto(`/scripts/get/${e.hash}?workspace=${$workspaceStore}`)
|
||||
}}
|
||||
onSaveInitial={(e) => {
|
||||
goto(`/scripts/edit/${e.path}`)
|
||||
}}
|
||||
searchParams={$page.url.searchParams}
|
||||
bind:script
|
||||
{showMeta}
|
||||
replaceStateFn={(path) => replaceState(path, $page.state)}
|
||||
>
|
||||
<UnsavedConfirmationModal
|
||||
getInitialAndModifiedValues={scriptBuilder?.getInitialAndModifiedValues}
|
||||
/>
|
||||
</ScriptBuilder>
|
||||
{:else}
|
||||
<ScriptEditorSkeleton />
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user