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); + } + }); });