({ artifacts, onExit }: Props)
| 9 | }; |
| 10 | |
| 11 | export function ArtifactsMenu({ artifacts, onExit }: Props): React.ReactElement { |
| 12 | const [selected, setSelected] = React.useState(0); |
| 13 | |
| 14 | useInput((input, key) => { |
| 15 | if (input === 'q' || key.escape) { |
| 16 | onExit(); |
| 17 | return; |
| 18 | } |
| 19 | if (artifacts.length === 0) return; |
| 20 | if (key.upArrow) { |
| 21 | setSelected(s => (s - 1 + artifacts.length) % artifacts.length); |
| 22 | return; |
| 23 | } |
| 24 | if (key.downArrow) { |
| 25 | setSelected(s => (s + 1) % artifacts.length); |
| 26 | return; |
| 27 | } |
| 28 | if (key.return) { |
| 29 | const target = artifacts[selected]; |
| 30 | if (target.url) { |
| 31 | void openBrowser(target.url); |
| 32 | } |
| 33 | return; |
| 34 | } |
| 35 | if (input === 'c') { |
| 36 | const target = artifacts[selected]; |
| 37 | if (target.url) { |
| 38 | void setClipboard(target.url).then(raw => { |
| 39 | if (raw) process.stdout.write(raw); |
| 40 | }); |
| 41 | } |
| 42 | } |
| 43 | }); |
| 44 | |
| 45 | return ( |
| 46 | <Box flexDirection="column" paddingX={1} paddingY={0}> |
| 47 | <Box marginBottom={1}> |
| 48 | <Text bold>Artifacts ({artifacts.length})</Text> |
| 49 | </Box> |
| 50 | |
| 51 | {artifacts.length === 0 ? ( |
| 52 | <Text color="subtle">No artifacts uploaded this session. Run /use-artifacts to learn how.</Text> |
| 53 | ) : ( |
| 54 | <Box flexDirection="column"> |
| 55 | {artifacts.map((a, idx) => ( |
| 56 | <ArtifactRow key={a.toolUseId} artifact={a} isSelected={idx === selected} /> |
| 57 | ))} |
| 58 | <Box marginTop={1}> |
| 59 | <Text color="subtle">{'↑/↓ select · Enter open · c copy URL · Esc exit'}</Text> |
| 60 | </Box> |
| 61 | </Box> |
| 62 | )} |
| 63 | </Box> |
| 64 | ); |
| 65 | } |
| 66 | |
| 67 | function ArtifactRow({ artifact, isSelected }: { artifact: ArtifactInfo; isSelected: boolean }): React.ReactElement { |
| 68 | const marker = isSelected ? '›' : ' '; |
nothing calls this directly
no test coverage detected