Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
omni-code / tui / dist / views / DiffViewer.js
Size: Mime:
import React, { useCallback, useEffect, useState } from "react";
import { Box, Text, useInput, useStdout } from "ink";
import { api } from "../rpc.js";
import { Panel } from "../components/Panel.js";
import { HelpOverlay } from "../components/HelpOverlay.js";
function clamp(value, max) {
    if (max <= 0)
        return 0;
    return Math.max(0, Math.min(value, max - 1));
}
const HELP_HINTS = [
    { key: "j/k", label: "navigate files" },
    { key: "enter", label: "view file patch" },
    { key: "r", label: "refresh" },
    { key: "?", label: "help" },
    { key: "esc", label: "back" },
];
const BAR_HINTS = [
    { key: "j/k", label: "navigate" },
    { key: "enter", label: "view patch" },
    { key: "r", label: "refresh" },
    { key: "?", label: "help" },
    { key: "esc", label: "back" },
];
export function DiffViewer({ ticketId, ticketTitle, projectId, onBack, onQuit, onHints, onStatus }) {
    const [diff, setDiff] = useState(null);
    const [selected, setSelected] = useState(0);
    const [patch, setPatch] = useState(null);
    const [patchScroll, setPatchScroll] = useState(0);
    const [status, setStatus] = useState(null);
    const [loading, setLoading] = useState(true);
    const [showHelp, setShowHelp] = useState(false);
    const { stdout } = useStdout();
    const visibleRows = (stdout?.rows ?? 24) - 8;
    const refresh = useCallback(async () => {
        try {
            const result = await api.gitDiff(ticketId, projectId);
            setDiff(result);
            setLoading(false);
        }
        catch (err) {
            setStatus(`Error: ${err instanceof Error ? err.message : String(err)}`);
            setLoading(false);
        }
    }, [ticketId, projectId]);
    useEffect(() => { refresh(); }, [refresh]);
    useEffect(() => {
        if (!status)
            return;
        const timer = setTimeout(() => setStatus(null), 4000);
        return () => clearTimeout(timer);
    }, [status]);
    // Report status/hints to parent for app-level StatusBar
    useEffect(() => { onStatus?.(status ?? null); }, [status]);
    useEffect(() => {
        onHints?.(patch !== null
            ? [{ key: "j/k", label: "scroll" }, { key: "esc", label: "back to files" }]
            : BAR_HINTS);
    }, [patch !== null]);
    const loadPatch = useCallback(async (file) => {
        try {
            const result = await api.gitDiffFile(ticketId, file.path, projectId);
            setPatch(result.patch || "(no diff available)");
            setPatchScroll(0);
        }
        catch (err) {
            setPatch(`(error: ${err instanceof Error ? err.message : String(err)})`);
        }
    }, [ticketId, projectId]);
    useInput((input, key) => {
        if (showHelp)
            return;
        if (input === "?") {
            setShowHelp(true);
            return;
        }
        if (key.escape) {
            if (patch !== null) {
                setPatch(null);
                return;
            }
            onBack();
            return;
        }
        if (input === "q") {
            onQuit();
            return;
        }
        if (input === "r") {
            setLoading(true);
            refresh();
            setStatus("Refreshed");
            return;
        }
        if (patch !== null) {
            const patchLines = patch.split("\n");
            if (key.upArrow || input === "k") {
                setPatchScroll((v) => Math.max(0, v - 1));
                return;
            }
            if (key.downArrow || input === "j") {
                setPatchScroll((v) => Math.min(Math.max(0, patchLines.length - visibleRows), v + 1));
                return;
            }
            return;
        }
        const files = diff?.files ?? [];
        if (key.upArrow || input === "k") {
            setSelected((v) => clamp(v - 1, files.length));
            return;
        }
        if (key.downArrow || input === "j") {
            setSelected((v) => clamp(v + 1, files.length));
            return;
        }
        if (key.return && files[selected]) {
            loadPatch(files[selected]);
            return;
        }
    });
    if (showHelp) {
        return (React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
            React.createElement(HelpOverlay, { title: "Git Diff", hints: HELP_HINTS, onClose: () => setShowHelp(false) })));
    }
    const diffSummary = diff
        ? `${diff.total_files} files +${diff.total_additions} -${diff.total_deletions}`
        : "";
    return (React.createElement(Box, { flexDirection: "column", flexGrow: 1 }, patch !== null ? (
    // Patch view
    React.createElement(Panel, { title: diff?.files[selected]?.path ?? "Patch", focused: true, flexGrow: 1 },
        React.createElement(Box, { flexDirection: "column", paddingX: 1 }, patch.split("\n").slice(patchScroll, patchScroll + visibleRows).map((line, i) => {
            let color;
            if (line.startsWith("+") && !line.startsWith("+++"))
                color = "green";
            else if (line.startsWith("-") && !line.startsWith("---"))
                color = "red";
            else if (line.startsWith("@@"))
                color = "cyan";
            return React.createElement(Text, { key: i, color: color, wrap: "truncate" }, line);
        })))) : (
    // File list
    React.createElement(Panel, { title: `Diff \u2014 ${ticketTitle}`, badge: diffSummary ? React.createElement(Text, { color: "gray" }, diffSummary) : undefined, focused: true, flexGrow: 1 },
        React.createElement(Box, { flexDirection: "column", paddingX: 1 }, loading ? (React.createElement(Text, { color: "gray" }, "Loading diff...")) : !diff || diff.files.length === 0 ? (React.createElement(Text, { color: "gray" }, "No changes detected.")) : (diff.files.map((file, i) => {
            const isSelected = i === selected;
            return (React.createElement(Text, { key: file.path, color: isSelected ? "cyan" : undefined, wrap: "truncate" },
                isSelected ? "\u25B8 " : "  ",
                file.untracked ? React.createElement(Text, { color: "yellow" }, "? ") : file.staged ? React.createElement(Text, { color: "green" }, "S ") : React.createElement(Text, { color: "blue" }, "M "),
                file.path,
                file.is_binary ? (React.createElement(Text, { color: "gray" }, " (binary)")) : (React.createElement(Text, { color: "gray" },
                    " ",
                    React.createElement(Text, { color: "green" },
                        "+",
                        file.additions),
                    " ",
                    React.createElement(Text, { color: "red" },
                        "-",
                        file.deletions)))));
        })))))));
}
//# sourceMappingURL=DiffViewer.js.map