feat(tui): results across the top, with panes you can hide, zoom and Tab through
The results pane now has the whole width, where a long file name needs it, and Searches, Downloads and Info share the row underneath. Tab and Shift-Tab walk the panes, z zooms the focused one to the window, w hides it so the others widen, and its number brings it back. Every list takes the same navigation keys, the shortcuts bar wraps instead of cutting keys off, and ? lists every key by pane, scrolling on a short terminal. Follows up on #55.
This commit is contained in:
@@ -182,6 +182,11 @@ export const SHORTCUTS: readonly (readonly [string, string])[] = [
|
||||
['c', 'chat'],
|
||||
['/', 'filter'],
|
||||
['a/A', 'select all/none'],
|
||||
['1-3', 'focus pane'],
|
||||
['h/l', 'scroll name'],
|
||||
['g/G', 'top/end'],
|
||||
['Tab/1-3', 'pane'],
|
||||
['z', 'zoom'],
|
||||
['w', 'hide'],
|
||||
['?', 'keys'],
|
||||
['q', 'quit'],
|
||||
]
|
||||
|
||||
@@ -37,45 +37,44 @@ export const HeroTerminal = () => {
|
||||
>
|
||||
<StatusLine counts={demo.counts} pct={demo.pct} />
|
||||
|
||||
<div className="mt-3 grid gap-2.5" style={{ gridTemplateColumns: '300px 1fr' }}>
|
||||
<div className="min-h-[498px]">
|
||||
<SearchesPane
|
||||
searches={demo.searches}
|
||||
activeId={demo.activeId}
|
||||
active={demo.focus === 1}
|
||||
onPick={demo.pickSearch}
|
||||
onSubmit={demo.submitSearch}
|
||||
{/* Results across the top with the whole width for long names;
|
||||
Searches, Transfers and Info share the row underneath. */}
|
||||
<div className="mt-3 grid gap-2.5" style={{ gridTemplateRows: '330px 300px' }}>
|
||||
<div className="relative min-h-0 overflow-hidden">
|
||||
<ResultsPane
|
||||
query={demo.query}
|
||||
rows={demo.rows}
|
||||
selected={demo.selected}
|
||||
active={demo.focus === 2}
|
||||
onSelect={demo.selectRow}
|
||||
onQueue={demo.queueRow}
|
||||
/>
|
||||
{demo.searching && (
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-[color-mix(in_srgb,var(--color-sleeve)_35%,transparent)] text-[13.5px] tracking-[0.02em] text-[var(--color-dust)]">
|
||||
searching the network
|
||||
<span className="ml-1.5 inline-block h-[15px] w-2 translate-y-[2px] rounded-[1.5px] bg-[var(--color-oxide)] motion-safe:animate-cursor-blink" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="grid min-h-0 gap-2.5" style={{ gridTemplateRows: '1fr 300px' }}>
|
||||
<div className="relative min-h-[250px] overflow-hidden">
|
||||
<ResultsPane
|
||||
query={demo.query}
|
||||
rows={demo.rows}
|
||||
selected={demo.selected}
|
||||
active={demo.focus === 2}
|
||||
onSelect={demo.selectRow}
|
||||
onQueue={demo.queueRow}
|
||||
<div
|
||||
className="grid min-h-0 gap-2.5"
|
||||
style={{ gridTemplateColumns: '320px 1fr 340px' }}
|
||||
>
|
||||
<div className="min-h-0 min-w-0 overflow-hidden">
|
||||
<SearchesPane
|
||||
searches={demo.searches}
|
||||
activeId={demo.activeId}
|
||||
active={demo.focus === 1}
|
||||
onPick={demo.pickSearch}
|
||||
onSubmit={demo.submitSearch}
|
||||
/>
|
||||
{demo.searching && (
|
||||
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-[color-mix(in_srgb,var(--color-sleeve)_35%,transparent)] text-[13.5px] tracking-[0.02em] text-[var(--color-dust)]">
|
||||
searching the network
|
||||
<span className="ml-1.5 inline-block h-[15px] w-2 translate-y-[2px] rounded-[1.5px] bg-[var(--color-oxide)] motion-safe:animate-cursor-blink" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="grid min-h-0 gap-2.5"
|
||||
style={{ gridTemplateColumns: '1fr 340px' }}
|
||||
>
|
||||
<div className="min-h-0 min-w-0 overflow-hidden">
|
||||
<TransfersPane rows={demo.transfers.slice(0, 7)} active={demo.focus === 3} />
|
||||
</div>
|
||||
<div className="min-h-0 min-w-0 overflow-hidden">
|
||||
<InfoPane row={demo.infoRow} />
|
||||
</div>
|
||||
<div className="min-h-0 min-w-0 overflow-hidden">
|
||||
<TransfersPane rows={demo.transfers.slice(0, 7)} active={demo.focus === 3} />
|
||||
</div>
|
||||
<div className="min-h-0 min-w-0 overflow-hidden">
|
||||
<InfoPane row={demo.infoRow} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { TuiPane } from './chrome'
|
||||
import type { ResultRow, SearchRow, TransferRow, TransferStatus } from './data'
|
||||
|
||||
const RESULT_COLS = '30px 1fr 74px 92px 78px 74px 42px'
|
||||
const TRANSFER_COLS = '48px 1fr 96px 150px 74px'
|
||||
const TRANSFER_COLS = '48px 1fr 96px 168px 74px'
|
||||
|
||||
interface SearchesPaneProps {
|
||||
searches: readonly SearchRow[]
|
||||
|
||||
@@ -25,10 +25,20 @@ const GLOBAL_KEYS: readonly (readonly [string, string])[] = [
|
||||
['i', 'inbox (unread counter)'],
|
||||
['/', 'filter the current list'],
|
||||
['a / A', 'select all / none'],
|
||||
['1–3', 'focus a pane'],
|
||||
['?', 'every key, grouped by pane'],
|
||||
['q', 'quit'],
|
||||
]
|
||||
|
||||
const PANE_KEYS: readonly (readonly [string, string])[] = [
|
||||
['Tab · ⇧Tab', 'next / previous pane'],
|
||||
['1–3', 'focus a pane, and bring it back if hidden'],
|
||||
['z', 'zoom the focused pane to the whole window'],
|
||||
['w', 'hide the focused pane; the others widen'],
|
||||
['↑ ↓ · j k', 'move a row in any list'],
|
||||
['Home End · g G', 'first / last row'],
|
||||
['PgUp PgDn · ^u ^d', 'a page, or half a page'],
|
||||
]
|
||||
|
||||
const ROOM_KEYS: readonly (readonly [string, string])[] = [
|
||||
['Enter', 'join room / send message'],
|
||||
['Tab · ⇧Tab', 'switch between open rooms'],
|
||||
@@ -40,16 +50,16 @@ const ROOM_KEYS: readonly (readonly [string, string])[] = [
|
||||
]
|
||||
|
||||
const PANES: readonly { num?: number; title: string; body: string }[] = [
|
||||
{
|
||||
num: 2,
|
||||
title: 'Results',
|
||||
body: 'Files from the network across the whole width: size, user, bitrate, speed, free slots. Space selects, Enter queues a download. h/l or ←/→ scroll a long name, 0 and $ jump to either end.',
|
||||
},
|
||||
{
|
||||
num: 1,
|
||||
title: 'Searches',
|
||||
body: 'Your queries and how many results each returned. Press s to search the network; each search keeps its own result set.',
|
||||
},
|
||||
{
|
||||
num: 2,
|
||||
title: 'Results',
|
||||
body: 'Files from the network: size, user, bitrate, speed, free slots. Space selects, Enter queues a download. h/l or ←/→ scroll a long name, 0 and $ jump to either end; Home/End, g/G, PgUp/PgDn and Ctrl-f/b/d/u move through the list.',
|
||||
},
|
||||
{
|
||||
num: 3,
|
||||
title: 'Downloads / Uploads',
|
||||
@@ -106,8 +116,10 @@ const FirstRun = () => (
|
||||
const Layout = () => (
|
||||
<Section>
|
||||
<SectionHead eyebrow="2 · the layout" title="Four panes.">
|
||||
The whole client is these four boxes. Press <Code>1</Code>–<Code>3</Code> to focus
|
||||
one; the focused pane’s legend turns green.
|
||||
The whole client is these four boxes: results across the top, the rest in a row
|
||||
underneath. <Code>Tab</Code> or <Code>1</Code>–<Code>3</Code> focuses one and its legend
|
||||
turns green; <Code>z</Code> gives it the whole window, <Code>w</Code> hides it so the
|
||||
others widen.
|
||||
</SectionHead>
|
||||
<Cols start>
|
||||
<div className="overflow-hidden">
|
||||
@@ -127,7 +139,8 @@ const Layout = () => (
|
||||
const Keys = () => (
|
||||
<Section>
|
||||
<SectionHead eyebrow="3 · keys" title="It's all keyboard.">
|
||||
The bottom bar always shows the keys for where you are. The essentials:
|
||||
The bottom bar always shows the keys for where you are, and <Code>?</Code> lists every
|
||||
one. The essentials:
|
||||
</SectionHead>
|
||||
<Cols>
|
||||
<div>
|
||||
@@ -138,6 +151,16 @@ const Keys = () => (
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Eyebrow className="mb-4 block">panes and lists</Eyebrow>
|
||||
<div className="flex flex-col">
|
||||
{PANE_KEYS.map(([combo, description]) => (
|
||||
<KeyRow key={combo} combo={combo} description={description} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Cols>
|
||||
<Cols className="mt-8">
|
||||
<div>
|
||||
<Eyebrow className="mb-4 block">
|
||||
in the chat-rooms popup <span className="text-muted">(press c)</span>
|
||||
|
||||
Reference in New Issue
Block a user