import React, { useEffect, useReducer } from "react"; import { Box, Text } from "ink"; import Spinner from "ink-spinner"; import SelectInput from "ink-select-input"; import { startRun } from "../lib/runner.js"; const MAX_LOG_LINES = 12; function reducer(state, action) { switch (action.type) { case "step-start": return { ...state, currentIndex: action.index, statuses: { ...state.statuses, [action.index]: "running" }, }; case "step-done": return { ...state, statuses: { ...state.statuses, [action.index]: action.status }, errors: action.error != null ? { ...state.errors, [action.index]: action.error } : state.errors, }; case "log": { const next = [...state.log, action]; if (next.length > MAX_LOG_LINES * 4) { next.splice(0, next.length - MAX_LOG_LINES * 4); } return { ...state, log: next }; } case "done": return { ...state, finished: true, success: action.success, cancelled: !!action.cancelled }; default: return state; } } export default function RunLog({ nav, plan, origin }) { const [state, dispatch] = useReducer(reducer, { statuses: {}, errors: {}, currentIndex: -1, log: [], finished: false, success: false, cancelled: false, }); useEffect(() => { const run = startRun(plan.steps); run.on("event", (event) => { dispatch(event); }); return () => run.cancel(); }, [plan]); const recentLog = state.log.slice(-MAX_LOG_LINES); return ( {plan.title} {plan.steps.map((step, index) => ( ))} {recentLog.length > 0 ? ( {recentLog.map((entry, i) => ( {entry.text} ))} ) : null} {state.finished ? : ( running… )} ); } function StepRow({ index, step, status, error }) { const symbol = symbolFor(status); const color = colorFor(status); return ( {symbol} {`${index + 1}.`} {step.label} {error ? {error} : null} ); } function symbolFor(status) { switch (status) { case "running": return "◐"; case "ok": return "✓"; case "failed": return "✗"; case "cancelled": return "⊘"; default: return "·"; } } function colorFor(status) { switch (status) { case "running": return "cyan"; case "ok": return "green"; case "failed": return "red"; case "cancelled": return "yellow"; default: return "gray"; } } function FinishedFooter({ state, nav, origin }) { const items = [ { label: "Back to main menu", value: "home" }, ...(origin === "frp" ? [{ label: "Back to FRP menu", value: "frp" }] : []), { label: "Quit", value: "quit" }, ]; const handleSelect = (item) => { if (item.value === "home") { nav.home(); } else if (item.value === "frp") { nav.replace({ name: "frp" }); // collapse stack down to just main + frp // (replace just swaps top; we want clean stack) } else if (item.value === "quit") { nav.exit(); } }; return ( {state.cancelled ? ( Cancelled. ) : state.success ? ( All steps completed successfully. ) : ( Run failed. )} ); }