diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 4f764065e4..3d72311db1 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -682,7 +682,7 @@ } break case 'ArrowDown': { - if (!$insertButtonOpen) { + if (!$insertButtonOpen && !flowPreviewButtons?.getPreviewOpen()) { let ids = generateIds() let idx = ids.indexOf($selectedIdStore) if (idx > -1 && idx < ids.length - 1) { @@ -693,7 +693,7 @@ break } case 'ArrowUp': { - if (!$insertButtonOpen) { + if (!$insertButtonOpen && !flowPreviewButtons?.getPreviewOpen()) { let ids = generateIds() let idx = ids.indexOf($selectedIdStore) if (idx > 0 && idx < ids.length) { diff --git a/frontend/src/lib/components/FlowLogRow.svelte b/frontend/src/lib/components/FlowLogRow.svelte new file mode 100644 index 0000000000..fe22c580d8 --- /dev/null +++ b/frontend/src/lib/components/FlowLogRow.svelte @@ -0,0 +1,92 @@ + + +
  • + + + {#if isExpanded(id) || !isCollapsible} +
    +
    + {@render children()} +
    +
    + {/if} +
  • + + diff --git a/frontend/src/lib/components/FlowLogViewer.svelte b/frontend/src/lib/components/FlowLogViewer.svelte index fafe7eace3..9e1ba6c1bf 100644 --- a/frontend/src/lib/components/FlowLogViewer.svelte +++ b/frontend/src/lib/components/FlowLogViewer.svelte @@ -1,18 +1,17 @@ -
    +
    diff --git a/frontend/src/lib/components/LogViewer.svelte b/frontend/src/lib/components/LogViewer.svelte index 8ab94ead38..a07596856b 100644 --- a/frontend/src/lib/components/LogViewer.svelte +++ b/frontend/src/lib/components/LogViewer.svelte @@ -34,6 +34,7 @@ customEmptyMessage?: string tagLabel?: string noPadding?: boolean + navigationId?: string } let { @@ -51,7 +52,8 @@ download = true, customEmptyMessage = 'No logs are available yet', tagLabel = undefined, - noPadding = false + noPadding = false, + navigationId = undefined }: Props = $props() // @ts-ignore @@ -217,78 +219,85 @@ -
    -
    -
    -
    - {#if jobId && download} -
    - - -
    - {/if} - - {#if !noAutoScroll} -
    - Auto scroll - -
    - {/if} -
    -
    - {#if isLoading} -
    - - {#if tag} -
    -
    {tagLabel ?? 'tag'}: {tag}
    - -
    - {/if} -
    - {:else if duration} - took {duration}ms - {/if} - {#if mem} - mem peak: {(mem / 1024).toPrecision(4)}MB - {/if} -
    {#if content}{@const len =
    -					(content?.length ?? 0) +
    -					(loadedFromObjectStore?.length ?? 0)}{#if downloadStartUrl}
    {:else if len > LOG_LIMIT}(truncated to the last {LOG_LIMIT} characters)

    {/if}{@html html}{:else if !isLoading}{customEmptyMessage}{/if}
    +
    +
    +
    +
    + {#if jobId && download} +
    + + +
    + {/if} + + {#if !noAutoScroll} +
    + Auto scroll + +
    + {/if} +
    +
    + {#if isLoading} +
    + + {#if tag} +
    +
    {tagLabel ?? 'tag'}: {tag}
    + +
    + {/if} +
    + {:else if duration} + took {duration}ms + {/if} + {#if mem} + mem peak: {(mem / 1024).toPrecision(4)}MB + {/if} +
    {#if content}{@const len =
    +						(content?.length ?? 0) +
    +						(loadedFromObjectStore?.length ?? 0)}{#if downloadStartUrl}
    {:else if len > LOG_LIMIT}(truncated to the last {LOG_LIMIT} characters)

    {/if}{@html html}{:else if !isLoading}{customEmptyMessage}{/if}
    +
    @@ -496,4 +505,9 @@ .dark .ansi-bright-white-bg { background-color: rgb(229, 233, 240); } + + [data-nav-id]:focus-visible { + outline: none; + outline-offset: 0; + } diff --git a/frontend/src/lib/keyboardChain.ts b/frontend/src/lib/keyboardChain.ts new file mode 100644 index 0000000000..6177df1b66 --- /dev/null +++ b/frontend/src/lib/keyboardChain.ts @@ -0,0 +1,47 @@ +export type NavigationChain = Record + +// Update navigation chain: remove old chain between first/last, insert new chain +export function updateLinks(navigationChain: NavigationChain, newChain: NavigationChain) { + if (Object.keys(newChain).length === 0) return navigationChain + + const firstLink = Object.keys(newChain).find((link) => newChain[link].upId === null) + const lastLink = Object.keys(newChain).find((link) => newChain[link].downId === null) + + //If the chain has no end or start, return the navigationChain + if (!firstLink || !lastLink) return navigationChain + + const cleaned = { ...navigationChain } + const inserted = { ...newChain } + + // Check if the chain needs to be inserted + if (!cleaned[lastLink] && cleaned[firstLink]) { + // connect the beginning of the new chain to the beginning of the chain + inserted[firstLink].upId = cleaned[firstLink]?.upId + + //connect the end of new the new chain to the beginning of the chain + const nextLink = cleaned[firstLink]?.downId + if (nextLink) { + inserted[lastLink].downId = nextLink + cleaned[nextLink].upId = lastLink + } + } else if (cleaned[lastLink] && cleaned[firstLink]) { + // If the end of the chain matches a link in the navigationChain, and the beginning delete the chain in the middle and replace it with the new chain + inserted[firstLink].upId = cleaned[firstLink]?.upId + inserted[lastLink].downId = cleaned[lastLink]?.downId + + if (firstLink !== lastLink) { + const toDelete: string[] = [] + + let current = cleaned[firstLink]?.downId + while (current && current !== lastLink && !toDelete.includes(current)) { + toDelete.push(current) + current = cleaned[current]?.downId + } + toDelete.forEach((itemId) => { + delete cleaned[itemId] + }) + } + } + + return { ...cleaned, ...inserted } +}