From ea84630b1bb359b6dc46fc6a9a6a73381c01df0f Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Tue, 12 Nov 2024 10:56:27 +0000 Subject: [PATCH] wip: ui --- src/ui/cli/Input.tsx | 56 ++++++++++++++++++++----------------- src/ui/cli/Main.tsx | 27 +++++++++++++----- src/ui/cli/TesterCli.tsx | 19 +++++++++++-- src/ui/cli/TesterStatus.tsx | 2 ++ 4 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src/ui/cli/Input.tsx b/src/ui/cli/Input.tsx index 3d8e61e..1e0641f 100644 --- a/src/ui/cli/Input.tsx +++ b/src/ui/cli/Input.tsx @@ -7,13 +7,15 @@ export function Input({ title, initialValue, onChange, + width = 25, }: { id?: string title: string initialValue: string onChange: (s: string) => void + width?: number }) { - const {isFocused} = useFocus({id}) + const {focus, isFocused} = useFocus({id}) const [{value, pos}, set] = useState({value: initialValue, pos: initialValue.length}) useMemo(() => { if (value !== initialValue) { @@ -33,10 +35,15 @@ export function Input({ return } - if (key.return) { + if (key.escape) { + if (value !== initialValue) { + set({value: initialValue, pos: initialValue.length}) + } + } else if (key.return) { if (value !== initialValue) { onChange(value) } + focus('#main') } else if (key.ctrl && (key.backspace || key.delete || input === 'w')) { set({value: '', pos: 0}) } else if (key.backspace || key.delete) { @@ -60,29 +67,26 @@ export function Input({ } }) - return - - - + const innerWidth = width - 4 + + return + + ╭╴ + {title} + ╶{''.padEnd(innerWidth - title.length, '─')}╮ - - - {title} - {''.padEnd(28 - title.length, '─')} - + + ╰{value || isFocused ? '╸' : '─'} {isFocused - ? - : - {value.substring(0, 28)} - {''.padEnd(28 - value.length, '─')} - + ? + : {value.substring(0, innerWidth)} } - - - - + { + (value || isFocused ? '╺' : '─') + + (isFocused ? '' : ''.padEnd(innerWidth - value.length, '─')) + }╯ } @@ -90,13 +94,15 @@ export function Input({ function FocusedInputValue({ value, pos, + width, }: { value: string pos: number + width: number }) { - const head = value.substring(pos - Math.min(pos, Math.max(3, 27 - (value.length - pos))), pos) + const head = value.substring(pos - Math.min(pos, Math.max(3, width - 1 - (value.length - pos))), pos) const cursor = value.substring(pos, pos + 1) || ' ' - const tail = value.substring(pos + 1).substring(0, 28 - head.length) + const tail = value.substring(pos + 1).substring(0, width - head.length) return {tail} {''.padEnd(28 - head.length - cursor.length - tail.length, ' ')} + >{''.padEnd(width - head.length - cursor.length - tail.length, ' ')} } diff --git a/src/ui/cli/Main.tsx b/src/ui/cli/Main.tsx index e5a90c8..c549b67 100644 --- a/src/ui/cli/Main.tsx +++ b/src/ui/cli/Main.tsx @@ -6,19 +6,32 @@ import { Overview } from './Overview' import { useSubscribers } from './useSubscribers' import { TesterStatus } from './TesterStatus' import { useWindowSize } from './useWindowSize' +import { useTesterCli } from './TesterCli' export function Main() { const tester = useTester() + const cli = useTesterCli() const [currentRun, setCurrentRun] = useState() - const { isFocused } = useFocus({ id: '#main', autoFocus: true }) - useInput((input) => { - if (isFocused && input.toLowerCase() === 's') { - if (tester.active) { - void tester.stop() - } else { - void tester.start() + const { focus, isFocused } = useFocus({ id: '#main', autoFocus: true }) + useInput((input, key) => { + if (input === 'c' && key.ctrl && !key.shift) { + void cli.close() + return + } + + if (isFocused) { + if (input.toLowerCase() === 's') { + if (tester.active) { + void tester.stop() + } else { + void tester.start() + } + } else if (input.toLowerCase() === 'f') { + focus('filterSuites') + } else if (input.toLowerCase() === 't') { + focus('filterTests') } } }) diff --git a/src/ui/cli/TesterCli.tsx b/src/ui/cli/TesterCli.tsx index 644f47a..15a9979 100644 --- a/src/ui/cli/TesterCli.tsx +++ b/src/ui/cli/TesterCli.tsx @@ -3,6 +3,7 @@ import { render, Instance } from 'ink' import { Tester } from '../Tester' import { TesterContext } from '../TesterContext' import { Main } from './Main' +import { createProvidedContext } from '../../util/react/context' export class TesterCli { constructor( @@ -18,12 +19,24 @@ export class TesterCli { this.#instance = render(( -
+ +
+ - )) + ), {exitOnCtrlC: false}) } - close() { + async close() { + await this.tester.stop() this.#instance?.unmount() + await this.tester[Symbol.asyncDispose]() + + process.stdin.destroy() + process.stdout.destroy() + process.exit() } } + +const TesterCliContext = createProvidedContext('TesterCli') + +export const useTesterCli = TesterCliContext.useContext diff --git a/src/ui/cli/TesterStatus.tsx b/src/ui/cli/TesterStatus.tsx index 45cb985..57594ee 100644 --- a/src/ui/cli/TesterStatus.tsx +++ b/src/ui/cli/TesterStatus.tsx @@ -28,11 +28,13 @@ export function TesterStatus() { {tester.conductors.size} conductors tester.filterSuites.set(s ? new RegExp(s, 'i') : undefined)} initialValue={getRegexpSource(tester.filterSuites.get())} /> tester.filterTests.set(s ? new RegExp(s, 'i') : undefined)} initialValue={getRegexpSource(tester.filterTests.get())}