Upgrade async GPU analysis and workbench UX
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useId, useRef, useState, type KeyboardEvent } from 'react'
|
||||
import { BadgeCheck, Boxes, Cpu, Image, ScanSearch } from 'lucide-react'
|
||||
|
||||
interface AiPipelineIllustrationProps {
|
||||
@@ -29,14 +29,47 @@ export function AiPipelineIllustration({
|
||||
const readiness = [hasImagery, hasTiles, gpuReady, hasDetections, hasQualityEvidence]
|
||||
const firstIncomplete = readiness.findIndex((ready) => !ready)
|
||||
const [selectedIndex, setSelectedIndex] = useState(firstIncomplete === -1 ? 4 : firstIncomplete)
|
||||
const tabRefs = useRef<Array<HTMLButtonElement | null>>([])
|
||||
const componentId = useId()
|
||||
const titleId = `${componentId}-title`
|
||||
const panelId = `${componentId}-panel`
|
||||
const selected = pipelineStages[selectedIndex]
|
||||
|
||||
const selectAndFocus = (index: number) => {
|
||||
setSelectedIndex(index)
|
||||
tabRefs.current[index]?.focus()
|
||||
}
|
||||
|
||||
const handleTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {
|
||||
let nextIndex: number | null = null
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowRight':
|
||||
nextIndex = (index + 1) % pipelineStages.length
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
nextIndex = (index - 1 + pipelineStages.length) % pipelineStages.length
|
||||
break
|
||||
case 'Home':
|
||||
nextIndex = 0
|
||||
break
|
||||
case 'End':
|
||||
nextIndex = pipelineStages.length - 1
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
selectAndFocus(nextIndex)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={running ? 'ai-pipeline ai-pipeline-running' : 'ai-pipeline'} aria-labelledby="ai-pipeline-title">
|
||||
<section className={running ? 'ai-pipeline ai-pipeline-running' : 'ai-pipeline'} aria-labelledby={titleId}>
|
||||
<div className="ai-pipeline-heading">
|
||||
<div>
|
||||
<p className="eyebrow">Van pixel naar bewijs</p>
|
||||
<h3 id="ai-pipeline-title">Van luchtbeeld naar controleerbare detectie</h3>
|
||||
<h3 id={titleId}>Van luchtbeeld naar controleerbare detectie</h3>
|
||||
<p>Open een schakel om te zien welke technische context GeoIntel door de volledige analyse bewaart.</p>
|
||||
</div>
|
||||
<span className={gpuReady ? 'ai-pipeline-gpu ai-pipeline-gpu-ready' : 'ai-pipeline-gpu'}>
|
||||
@@ -49,13 +82,16 @@ export function AiPipelineIllustration({
|
||||
{pipelineStages.map(({ key, label, icon: Icon }, index) => (
|
||||
<button
|
||||
key={key}
|
||||
id={`ai-pipeline-${key}`}
|
||||
id={`${componentId}-${key}`}
|
||||
ref={(element) => { tabRefs.current[index] = element }}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={selectedIndex === index}
|
||||
aria-controls="ai-pipeline-detail"
|
||||
aria-controls={panelId}
|
||||
tabIndex={selectedIndex === index ? 0 : -1}
|
||||
className={readiness[index] ? 'ai-pipeline-stage ai-pipeline-stage-ready' : 'ai-pipeline-stage'}
|
||||
onClick={() => setSelectedIndex(index)}
|
||||
onKeyDown={(event) => handleTabKeyDown(event, index)}
|
||||
>
|
||||
<span><Icon aria-hidden="true" /></span>
|
||||
<strong>{label}</strong>
|
||||
@@ -65,10 +101,11 @@ export function AiPipelineIllustration({
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="ai-pipeline-detail"
|
||||
id={panelId}
|
||||
className="ai-pipeline-detail"
|
||||
role="tabpanel"
|
||||
aria-labelledby={`ai-pipeline-${selected.key}`}
|
||||
aria-labelledby={`${componentId}-${selected.key}`}
|
||||
tabIndex={0}
|
||||
key={selected.key}
|
||||
>
|
||||
<span>{String(selectedIndex + 1).padStart(2, '0')}</span>
|
||||
|
||||
Reference in New Issue
Block a user