Added navigating panes by arrows

This commit is contained in:
Oskar Krawczyk
2026-06-29 11:22:02 +02:00
parent 69027331b2
commit 6acbac462e
7 changed files with 47 additions and 10 deletions
+1
View File
@@ -1,3 +1,4 @@
node_modules/ node_modules/
dist/ dist/
.DS_Store .DS_Store
package-lock.json
+3 -3
View File
@@ -1,11 +1,11 @@
{ {
"name": "torlink", "name": "torlnk",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "torlink", "name": "torlnk",
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -16,7 +16,7 @@
"webtorrent": "^2.4.1" "webtorrent": "^2.4.1"
}, },
"bin": { "bin": {
"torlink": "dist/index.js" "torlnk": "dist/index.js"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^25.9.2", "@types/node": "^25.9.2",
+8
View File
@@ -302,6 +302,14 @@ export function App({
setRegion(region === "sidebar" ? "content" : "sidebar"); setRegion(region === "sidebar" ? "content" : "sidebar");
return; return;
} }
if (key.rightArrow) {
if (region === "sidebar") setRegion("content");
return;
}
if (key.leftArrow) {
if (region === "content") setRegion("sidebar");
return;
}
if (key.escape) { if (key.escape) {
if (captureMode === "esc") return; if (captureMode === "esc") return;
if (region === "content") { if (region === "content") {
+10 -3
View File
@@ -100,6 +100,7 @@ export function Results() {
submitQuery, submitQuery,
section, section,
region, region,
setRegion,
setCaptureMode, setCaptureMode,
startDownload, startDownload,
contentWidth, contentWidth,
@@ -155,9 +156,13 @@ export function Results() {
setMode("search"); setMode("search");
return; return;
} }
if (key.upArrow) {
if (results.length > 0 && clamped > 0) setCursor(clamped - 1);
else setMode("search");
return;
}
if (results.length === 0) return; if (results.length === 0) return;
if (key.upArrow) setCursor(wrapStep(clamped, -1, results.length)); if (key.downArrow) setCursor(wrapStep(clamped, 1, results.length));
else if (key.downArrow) setCursor(wrapStep(clamped, 1, results.length));
else if (key.pageUp) setCursor(Math.max(0, clamped - pageJump)); else if (key.pageUp) setCursor(Math.max(0, clamped - pageJump));
else if (key.pageDown) setCursor(Math.min(results.length - 1, clamped + pageJump)); else if (key.pageDown) setCursor(Math.min(results.length - 1, clamped + pageJump));
else if (key.return) { else if (key.return) {
@@ -271,12 +276,14 @@ export function Results() {
editing={mode === "search"} editing={mode === "search"}
placeholder={PLACEHOLDER} placeholder={PLACEHOLDER}
onSubmit={onSubmit} onSubmit={onSubmit}
onExitDown={() => setMode("list")}
onExitLeft={() => setRegion("sidebar")}
/> />
<Box marginTop={1}> <Box marginTop={1}>
<Panel <Panel
title={mode === "detail" ? "details" : browsing ? "latest" : "results"} title={mode === "detail" ? "details" : browsing ? "latest" : "results"}
width={contentWidth} width={contentWidth}
focused={focused} focused={focused && mode !== "search"}
count={mode === "detail" ? undefined : count} count={mode === "detail" ? undefined : count}
height={panelOuter} height={panelOuter}
> >
+6
View File
@@ -10,6 +10,8 @@ interface SearchBarProps {
editing: boolean; editing: boolean;
onSubmit: (value: string) => void; onSubmit: (value: string) => void;
onChange?: (value: string) => void; onChange?: (value: string) => void;
onExitDown?: () => void;
onExitLeft?: () => void;
} }
export function SearchBar({ export function SearchBar({
@@ -19,6 +21,8 @@ export function SearchBar({
editing, editing,
onSubmit, onSubmit,
onChange, onChange,
onExitDown,
onExitLeft,
}: SearchBarProps) { }: SearchBarProps) {
return ( return (
<Panel title="search" width={width} focused={editing} height={2}> <Panel title="search" width={width} focused={editing} height={2}>
@@ -31,6 +35,8 @@ export function SearchBar({
placeholder={placeholder} placeholder={placeholder}
onSubmit={onSubmit} onSubmit={onSubmit}
onChange={onChange} onChange={onChange}
onExitDown={onExitDown}
onExitLeft={onExitLeft}
/> />
) : value ? ( ) : value ? (
<Text wrap="truncate-end">{value}</Text> <Text wrap="truncate-end">{value}</Text>
+13 -2
View File
@@ -7,6 +7,8 @@ export interface TextFieldProps {
placeholder?: string; placeholder?: string;
onChange?: (value: string) => void; onChange?: (value: string) => void;
onSubmit?: (value: string) => void; onSubmit?: (value: string) => void;
onExitDown?: () => void;
onExitLeft?: () => void;
} }
interface Edit { interface Edit {
@@ -48,6 +50,8 @@ export function TextField({
placeholder = "", placeholder = "",
onChange, onChange,
onSubmit, onSubmit,
onExitDown,
onExitLeft,
}: TextFieldProps) { }: TextFieldProps) {
const [value, setValue] = useState(defaultValue); const [value, setValue] = useState(defaultValue);
const [cursor, setCursor] = useState(defaultValue.length); const [cursor, setCursor] = useState(defaultValue.length);
@@ -60,8 +64,11 @@ export function TextField({
useInput( useInput(
(input, key) => { (input, key) => {
if (key.upArrow || key.downArrow || key.tab || (key.ctrl && input === "c")) if (key.downArrow) {
onExitDown?.();
return; return;
}
if (key.upArrow || key.tab || (key.ctrl && input === "c")) return;
if (key.return) { if (key.return) {
onSubmit?.(value); onSubmit?.(value);
@@ -91,7 +98,11 @@ export function TextField({
} }
if (key.leftArrow) { if (key.leftArrow) {
setCursor(Math.max(0, cursor - 1)); if (cursor === 0) {
onExitLeft?.();
return;
}
setCursor(cursor - 1);
return; return;
} }
if (key.rightArrow) { if (key.rightArrow) {
+6 -2
View File
@@ -14,7 +14,7 @@ export const HELP_GROUPS: HelpGroup[] = [
{ {
title: "Navigate", title: "Navigate",
hints: [ hints: [
{ keys: "↑ ↓", label: "Move" }, { keys: "↑ ↓ ← →", label: "Navigate content and panes" },
{ keys: "↵", label: "Open" }, { keys: "↵", label: "Open" },
{ keys: "tab", label: "Switch pane" }, { keys: "tab", label: "Switch pane" },
{ keys: "esc", label: "Back" }, { keys: "esc", label: "Back" },
@@ -48,6 +48,8 @@ export const HELP_GROUPS: HelpGroup[] = [
}, },
]; ];
const NAVIGATE: Hint = { keys: "↑ ↓ ← →", label: "Navigate content and panes" };
const ALWAYS: Hint = { keys: "?", label: "Keys" }; const ALWAYS: Hint = { keys: "?", label: "Keys" };
const SWITCH: Hint = { keys: "tab", label: "Switch pane" }; const SWITCH: Hint = { keys: "tab", label: "Switch pane" };
@@ -60,7 +62,7 @@ export function footerHints(
): Hint[] { ): Hint[] {
if (region === "sidebar") { if (region === "sidebar") {
return [ return [
{ keys: "↑↓", label: "Move" }, NAVIGATE,
{ keys: "↵", label: "Open" }, { keys: "↵", label: "Open" },
SWITCH, SWITCH,
ALWAYS, ALWAYS,
@@ -81,6 +83,7 @@ export function footerHints(
} }
if (downloadFocus === "recent") { if (downloadFocus === "recent") {
return [ return [
NAVIGATE,
{ keys: "d", label: "Download again" }, { keys: "d", label: "Download again" },
{ keys: "c", label: "Remove" }, { keys: "c", label: "Remove" },
{ keys: "x", label: "Clear" }, { keys: "x", label: "Clear" },
@@ -91,6 +94,7 @@ export function footerHints(
return [{ keys: "p", label: "Pause" }, { keys: "c", label: "Cancel" }, SWITCH, ALWAYS]; return [{ keys: "p", label: "Pause" }, { keys: "c", label: "Cancel" }, SWITCH, ALWAYS];
} }
return [ return [
NAVIGATE,
{ keys: "d", label: "Download" }, { keys: "d", label: "Download" },
{ keys: "/", label: "Search" }, { keys: "/", label: "Search" },
{ keys: "m", label: "Paste magnet" }, { keys: "m", label: "Paste magnet" },