Compare commits

...

3 Commits

Author SHA1 Message Date
Guilhem
f511ba8d26 scroll to result if hash 2026-02-06 16:34:54 +00:00
Guilhem
bfa128a3ac nit 2026-02-06 16:30:44 +00:00
Guilhem
bd0600fff7 Add hash navigation to share results in detail page 2026-02-06 16:17:47 +00:00
2 changed files with 121 additions and 10 deletions

View File

@@ -24,7 +24,7 @@
import ModuleStatus from './ModuleStatus.svelte'
import { clone, isScriptPreview, msToSec, readFieldsRecursively, truncateRev } from '$lib/utils'
import JobArgs from './JobArgs.svelte'
import { ChevronDown, ExternalLink, Hourglass } from 'lucide-svelte'
import { ChevronDown, ExternalLink, Hourglass, Hash } from 'lucide-svelte'
import { deepEqual } from 'fast-equals'
import FlowTimeline from './FlowTimeline.svelte'
import { dfs } from './flows/dfs'
@@ -1295,6 +1295,21 @@
let totalEventsWaiting = $derived(
Object.values(suspendStatus?.val ?? {}).reduce((a, b) => a + (b?.nb ?? 0), 0)
)
// Hash navigation functions
function scrollToSection(sectionId: string) {
const element = document.getElementById(sectionId)
if (element) {
element.scrollIntoView({ behavior: 'smooth' })
}
}
function handleSectionHashClick(sectionId: string) {
const url = new URL(window.location.href)
url.hash = sectionId
window.history.pushState({}, '', url.toString())
scrollToSection(sectionId)
}
</script>
<JobLoader workspaceOverride={workspaceId} {noLogs} noCode bind:this={jobLoader} />
@@ -1375,8 +1390,24 @@
</div>
{:else}
<!-- Default single-column result -->
<h3 class="text-xs font-semibold text-emphasis mb-1">Result</h3>
<div class="flex-1 overflow-auto rounded-md border bg-surface-tertiary p-4 max-h-screen">
<div id="result">
<h3
class="text-sm font-semibold text-emphasis mb-1 flex items-center gap-2 group sticky top-0 bg-surface py-2 z-50"
>
Result
<button
type="button"
onclick={() => handleSectionHashClick('result')}
class="text-muted-foreground hover:text-primary transition-colors opacity-0 group-hover:opacity-100"
title="Link to this section"
>
<Hash size={14} />
</button>
</h3>
</div>
<div
class="flex-1 overflow-auto rounded-md border bg-surface-tertiary p-4 max-h-[calc(100vh-60px)]"
>
{#if job !== undefined && (job.result_stream || (job.type == 'CompletedJob' && 'result' in job && job.result !== undefined))}
<DisplayResult
workspaceId={job?.workspace_id}

View File

@@ -34,7 +34,8 @@
Code2,
ClipboardCopy,
GitBranch,
EllipsisVertical
EllipsisVertical,
Hash
} from 'lucide-svelte'
import DisplayResult from '$lib/components/DisplayResult.svelte'
@@ -115,6 +116,9 @@
let lastJobId: string | undefined = $state(undefined)
let concurrencyKey: string | undefined = $state(undefined)
// Hash navigation state
let currentHash: string = $state('')
setContext(
'FlowGraphAssetContext',
initFlowGraphAssetsCtx({ getModules: () => job?.raw_flow?.modules ?? [] })
@@ -354,6 +358,29 @@
}
}
// Hash navigation functions
function scrollToSection(sectionId: string) {
const element = document.getElementById(sectionId)
if (element) {
element.scrollIntoView({ behavior: 'smooth' })
}
}
function handleSectionHashClick(sectionId: string) {
const url = new URL(window.location.href)
url.hash = sectionId
window.history.pushState({}, '', url.toString())
currentHash = sectionId
scrollToSection(sectionId)
}
function handleHashChange() {
currentHash = window.location.hash.slice(1) // Remove the # symbol
if (currentHash) {
scrollToSection(currentHash)
}
}
$effect(() => {
job?.id && lastJobId !== job.id && untrack(() => getConcurrencyKey(job))
})
@@ -369,6 +396,33 @@
$effect(() => {
job && untrack(() => onJobLoaded())
})
$effect(() => {
// Scroll to hash when job loads and hash is present
if (job && currentHash) {
setTimeout(() => scrollToSection(currentHash), 100)
}
})
$effect(() => {
// Initialize hash on mount and listen for hash changes
currentHash = window.location.hash.slice(1)
if (currentHash) {
// Wait for job data and DOM to be fully rendered
const scrollToHash = () => {
if (document.getElementById(currentHash)) {
scrollToSection(currentHash)
} else {
// Retry if element not found yet (job data might still be loading)
setTimeout(scrollToHash, 200)
}
}
setTimeout(scrollToHash, 100)
}
window.addEventListener('hashchange', handleHashChange)
return () => {
window.removeEventListener('hashchange', handleHashChange)
}
})
</script>
<HighlightTheme />
@@ -714,8 +768,20 @@
{/if}
<!-- Arguments and actions -->
<div class="max-w-7xl mx-auto w-full px-4 mt-12">
<div class="text-xs text-emphasis font-semibold mb-1">Inputs</div>
<div class="max-w-7xl mx-auto w-full px-4 mt-12" id="inputs">
<div
class="text-sm text-emphasis font-semibold mb-1 flex items-center gap-2 group sticky top-0 bg-surface py-2 z-50"
>
Inputs
<button
type="button"
onclick={() => handleSectionHashClick('inputs')}
class="text-muted-foreground hover:text-primary transition-colors opacity-0 group-hover:opacity-100"
title="Link to this section"
>
<Hash size={14} />
</button>
</div>
<div class="flex flex-col gap-y-6">
<JobArgs
workspace={job?.workspace_id ?? $workspaceStore ?? 'no_w'}
@@ -769,9 +835,23 @@
<!-- Result Section (moved outside tabs) -->
{#if job}
<div class="mr-2 sm:mr-0 mt-12 mb-6">
<h3 class="text-xs font-semibold text-emphasis mb-1">Result</h3>
<div class="border rounded-md bg-surface-tertiary p-4 overflow-auto max-h-screen">
<div class="mr-2 sm:mr-0 mt-12 mb-6" id="result">
<h3
class="text-sm font-semibold text-emphasis mb-1 flex items-center gap-2 group sticky top-0 bg-surface py-2 z-50"
>
Result
<button
type="button"
onclick={() => handleSectionHashClick('result')}
class="text-muted-foreground hover:text-primary transition-colors opacity-0 group-hover:opacity-100"
title="Link to this section"
>
<Hash size={14} />
</button>
</h3>
<div
class="border rounded-md bg-surface-tertiary p-4 overflow-auto max-h-[calc(100vh-60px)]"
>
{#if job.result_stream || (job.type == 'CompletedJob' && job.result !== undefined)}
<DisplayResult
workspaceId={job?.workspace_id}
@@ -789,7 +869,7 @@
{/if}
<!-- Logs and outputs-->
<div class="mr-2 sm:mr-0 mt-6">
<div class="mr-2 sm:mr-0 mt-6" id="metrics">
<Tabs bind:selected={viewTab}>
<Tab value="logs" label="Logs" />
<Tab value="stats" label="Metrics" />