diff --git a/src/components/compress.tsx b/src/components/compress.tsx
index 05327bf..0e71f7e 100644
--- a/src/components/compress.tsx
+++ b/src/components/compress.tsx
@@ -65,6 +65,12 @@ export function CompressUI() {
// Register file list items and remove buttons
fl.files().forEach((_, index) => {
+ nav.registerElement({
+ id: `file-${index}-open`,
+ type: "button",
+ onEnter: () => openFile(fl.files()[index]!),
+ });
+
nav.registerElement({
id: `file-${index}`,
type: "list-item",
@@ -77,6 +83,7 @@ export function CompressUI() {
type: "button",
onEnter: () => fl.removeFile(index),
});
+
});
// Register buttons
diff --git a/src/components/decrypt.tsx b/src/components/decrypt.tsx
index fcb1660..f4e75e8 100644
--- a/src/components/decrypt.tsx
+++ b/src/components/decrypt.tsx
@@ -62,6 +62,12 @@ export function DecryptUI() {
// Register file list items and remove buttons
fl.files().forEach((_, index) => {
+ nav.registerElement({
+ id: `file-${index}-open`,
+ type: "button",
+ onEnter: () => openFile(fl.files()[index]!),
+ });
+
nav.registerElement({
id: `file-${index}`,
type: "list-item",
diff --git a/src/components/delete.tsx b/src/components/delete.tsx
index 457f24d..49d1213 100644
--- a/src/components/delete.tsx
+++ b/src/components/delete.tsx
@@ -82,6 +82,12 @@ export function DeleteUI() {
// Register file list items and remove buttons
fl.files().forEach((_, index) => {
+ nav.registerElement({
+ id: `file-${index}-open`,
+ type: "button",
+ onEnter: () => openFile(fl.files()[index]!),
+ });
+
nav.registerElement({
id: `file-${index}`,
type: "list-item",
diff --git a/src/components/images-to-pdf.tsx b/src/components/images-to-pdf.tsx
index d9a0b9d..5dc73cf 100644
--- a/src/components/images-to-pdf.tsx
+++ b/src/components/images-to-pdf.tsx
@@ -63,6 +63,12 @@ export function ImagesToPDFUI() {
nav.clearElements();
// Register file list items and their action buttons
fl.files().forEach((_, index) => {
+ nav.registerElement({
+ id: `file-${index}-open`,
+ type: "button",
+ onEnter: () => openFile(fl.files()[index]!),
+ });
+
// Move up button
nav.registerElement({
id: `file-${index}-up`,
diff --git a/src/components/merge.tsx b/src/components/merge.tsx
index f864190..b65a052 100644
--- a/src/components/merge.tsx
+++ b/src/components/merge.tsx
@@ -45,6 +45,12 @@ export function MergeUI() {
// Register file list items and their action buttons
fl.files().forEach((_, index) => {
+ nav.registerElement({
+ id: `file-${index}-open`,
+ type: "button",
+ onEnter: () => openFile(fl.files()[index]!),
+ });
+
// Move up button
nav.registerElement({
id: `file-${index}-up`,
diff --git a/src/components/pdf-to-images.tsx b/src/components/pdf-to-images.tsx
index d67904c..d6f1633 100644
--- a/src/components/pdf-to-images.tsx
+++ b/src/components/pdf-to-images.tsx
@@ -1,6 +1,6 @@
import { createSignal, Show, createEffect, onCleanup } from "solid-js";
import { pdfToImages } from "../tools/pdf-to-images";
-import { getOutputDir, openOutputFolder } from "../utils/utils";
+import { getOutputDir, openFile, openOutputFolder } from "../utils/utils";
import {
ToolContainer,
Label,
@@ -71,6 +71,12 @@ export function PDFToImagesUI() {
// Register file list items and remove buttons
fl.files().forEach((_, index) => {
+ nav.registerElement({
+ id: `file-${index}-open`,
+ type: "button",
+ onEnter: () => openFile(fl.files()[index]!),
+ });
+
nav.registerElement({
id: `file-${index}`,
type: "list-item",
diff --git a/src/components/protect.tsx b/src/components/protect.tsx
index 7710fae..902c699 100644
--- a/src/components/protect.tsx
+++ b/src/components/protect.tsx
@@ -69,6 +69,12 @@ export function ProtectUI() {
// Register file list items and remove buttons
fl.files().forEach((_, index) => {
+ nav.registerElement({
+ id: `file-${index}-open`,
+ type: "button",
+ onEnter: () => openFile(fl.files()[index]!),
+ });
+
nav.registerElement({
id: `file-${index}`,
type: "list-item",
diff --git a/src/components/rotate.tsx b/src/components/rotate.tsx
index 807d99f..a94980d 100644
--- a/src/components/rotate.tsx
+++ b/src/components/rotate.tsx
@@ -85,6 +85,12 @@ export function RotateUI() {
// Register file list items and remove buttons
fl.files().forEach((_, index) => {
+ nav.registerElement({
+ id: `file-${index}-open`,
+ type: "button",
+ onEnter: () => openFile(fl.files()[index]!),
+ });
+
nav.registerElement({
id: `file-${index}`,
type: "list-item",
diff --git a/src/components/split-extract.tsx b/src/components/split-extract.tsx
index 18bb59f..489bf03 100644
--- a/src/components/split-extract.tsx
+++ b/src/components/split-extract.tsx
@@ -1,7 +1,7 @@
import { createSignal, Show, createEffect, onCleanup } from "solid-js";
import { useTerminalDimensions } from "@opentui/solid";
import { extractPDF, splitPDF } from "../tools/split-extract";
-import { getOutputDir, openOutputFolder } from "../utils/utils";
+import { getOutputDir, openFile, openOutputFolder } from "../utils/utils";
import { useFileList } from "../hooks/useFileList";
import { useKeyboardNav } from "../hooks/useKeyboardNav";
import {
@@ -180,6 +180,12 @@ export function SplitExtractUI() {
// Register file list items and remove buttons
fl.files().forEach((_, index) => {
+ nav.registerElement({
+ id: `file-${index}-open`,
+ type: "button",
+ onEnter: () => openFile(fl.files()[index]!),
+ });
+
nav.registerElement({
id: `file-${index}`,
type: "list-item",
diff --git a/src/components/ui.tsx b/src/components/ui.tsx
index a4b0702..f8c1dc8 100644
--- a/src/components/ui.tsx
+++ b/src/components/ui.tsx
@@ -4,7 +4,7 @@ import { Show, Index, createSignal, createResource } from "solid-js";
import type { JSX, Accessor, Setter } from "solid-js";
import { EmptyBorderChars, STATUS_COLORS } from "../constants/constants";
import type { StatusType } from "../model/models";
-import { getFormattedFileMetadata, handleFileExplorer } from "../utils/utils";
+import { getFormattedFileMetadata, handleFileExplorer, openFile } from "../utils/utils";
// ============ Layout Components ============
export function ToolContainer(props: { children: JSX.Element }) {
@@ -267,6 +267,7 @@ export function FileList(props: FileListProps) {
}),
);
const [rowHovered, setRowHovered] = createSignal(false);
+ const [openHovered, setOpenHovered] = createSignal(false);
const [upHovered, setUpHovered] = createSignal(false);
const [downHovered, setDownHovered] = createSignal(false);
const [removeHovered, setRemoveHovered] = createSignal(false);
@@ -296,13 +297,23 @@ export function FileList(props: FileListProps) {
return parts.join(" • ");
};
+
+ const isOpenHighlighted = () =>
+ props.focusedButton?.() === `file-${index}-open` || openHovered();
+
const isUpHighlighted = () =>
- canMoveUp() && (props.focusedButton?.() === `file-${index}-up` || upHovered());
+ canMoveUp() &&
+ (props.focusedButton?.() === `file-${index}-up` || upHovered());
+
const isDownHighlighted = () =>
canMoveDown() &&
- (props.focusedButton?.() === `file-${index}-down` || downHovered());
+ (props.focusedButton?.() === `file-${index}-down` ||
+ downHovered());
+
const isRemoveHighlighted = () =>
- props.focusedButton?.() === `file-${index}-remove` || removeHovered();
+ props.focusedButton?.() === `file-${index}-remove` ||
+ removeHovered();
+
return (
-
+
+ {
+ e.stopPropagation?.();
+ void openFile(file());
+ }}
+ onMouseOver={() => setOpenHovered(true)}
+ onMouseOut={() => setOpenHovered(false)}
+ height={1}
+ paddingTop={1}
+ paddingBottom={1}
+ paddingLeft={2}
+ paddingRight={2}
+ minWidth={3}
+ justifyContent="center"
+ alignItems="center"
+ >
+
+
{
const file = new URL("../../src/components/ui.tsx", import.meta.url);
expect(await Bun.file(file).exists()).toBe(true);
});
+
+ it("wires the file-list open action to open the file instead of reordering", async () => {
+ const file = new URL("../../src/components/ui.tsx", import.meta.url);
+ const code = await Bun.file(file).text();
+ const openButtonIndex = code.indexOf('content={"↗"}');
+
+ expect(openButtonIndex).toBeGreaterThan(-1);
+
+ const openButtonBlock = code.slice(Math.max(0, openButtonIndex - 1200), openButtonIndex + 200);
+
+ expect(openButtonBlock.includes("void openFile(file());")).toBe(true);
+ expect(openButtonBlock.includes('props.onMove?.(index, "up")')).toBe(false);
+ });
+
+ it("registers keyboard open actions for every file-list screen", async () => {
+ const componentPaths = [
+ "../../src/components/compress.tsx",
+ "../../src/components/decrypt.tsx",
+ "../../src/components/delete.tsx",
+ "../../src/components/images-to-pdf.tsx",
+ "../../src/components/merge.tsx",
+ "../../src/components/pdf-to-images.tsx",
+ "../../src/components/protect.tsx",
+ "../../src/components/rotate.tsx",
+ "../../src/components/split-extract.tsx",
+ ];
+
+ for (const componentPath of componentPaths) {
+ const code = await Bun.file(new URL(componentPath, import.meta.url)).text();
+ expect(code.includes('id: `file-${index}-open`')).toBe(true);
+ }
+ });
});