fix(frontend): forking UI issues (#7215)

* fix(frontend): forking UI issues

* fix dark mode
This commit is contained in:
hugocasa
2025-11-24 22:41:05 +01:00
committed by GitHub
parent a2d3297343
commit dccee1dba0
9 changed files with 177 additions and 62 deletions
+3 -3
View File
@@ -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}