Progress on select-options (more)
This commit is contained in:
parent
4eea9bce6f
commit
eb7f59e19b
@ -42,7 +42,7 @@ const exit: ExitConstructor = x => ({_tag: 'PainterExit', value: x})
|
||||
type PainterEffect<
|
||||
E,
|
||||
T extends object = {},
|
||||
> = (task: PainterTask<T>) => Either<PainterTask<T>, PainterExit<E>>
|
||||
> = (task: PainterTask<T>) => Promise<Either<PainterTask<E, T>, PainterExit<E>>>
|
||||
|
||||
type EffectState = <T extends object = {}>(x: T) => T & Tag<'PainterState'>
|
||||
const effectState: EffectState = x => ({_tag: 'PainterState', ...x})
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import type { Key } from 'node:readline'
|
||||
import * as readline from 'node:readline'
|
||||
import { emitKeypressEvents, type Key } from 'node:readline'
|
||||
import {
|
||||
exit as EXIT,
|
||||
type PainterEffect,
|
||||
type PainterState,
|
||||
type PainterTask,
|
||||
tagState,
|
||||
task as TASK,
|
||||
} from '../painter/lifecycle'
|
||||
import {
|
||||
@ -13,8 +15,9 @@ import {
|
||||
writeAnsiU,
|
||||
} from '../util/ansi'
|
||||
import { DoEither, type Either, left, right } from '../util/basic/either'
|
||||
import { some } from '../util/basic/option'
|
||||
import type { Tag } from '../util/basic/utility'
|
||||
import { DoOption, getOrElse, map, none, type Option, some } from '../util/basic/option'
|
||||
import { hasTag, type Tag } from '../util/basic/utility'
|
||||
import type { RecursiveRequired } from '../util/types'
|
||||
|
||||
interface SelectOptions {
|
||||
keyup: Array<Key>
|
||||
@ -37,18 +40,87 @@ interface SelectOptions {
|
||||
}
|
||||
}
|
||||
|
||||
interface Event extends Tag<'KEYUP' | 'KEYDOWN' | 'EXIT' | 'ACCEPT'> {
|
||||
value?: any
|
||||
}
|
||||
const EVENT: Record<string, Event | ((...args: any[]) => Event)> = {
|
||||
type PartialSelectOptions =
|
||||
& Partial<Omit<SelectOptions, 'options' | 'header'>>
|
||||
& {
|
||||
options: SelectOptions['options'],
|
||||
header?: {
|
||||
text: NonNullable<SelectOptions['header']>['text'],
|
||||
format?: NonNullable<SelectOptions['header']>['format'],
|
||||
},
|
||||
}
|
||||
|
||||
type SelectOptionsConfig = (x: PartialSelectOptions) => SelectOptions
|
||||
const selectOptionsConfig: SelectOptionsConfig = x => ({
|
||||
keyup: x.keyup ?? [{name: 'up'}, {name: 'k'}],
|
||||
keydown: x.keydown ?? [{name: 'down'}, {name: 'j'}],
|
||||
exit: x.exit ?? [{name: 'esc'}],
|
||||
accept: x.accept ?? [{name: 'return'}],
|
||||
selector: {
|
||||
selected: x.selector?.selected ?? '> ',
|
||||
default: x.selector?.default ?? ' ',
|
||||
},
|
||||
selected: x.selected ?? 0,
|
||||
options: x.options,
|
||||
header: !x.header ? undefined
|
||||
: {text: x.header.text, format: x.header.format ?? ['WHITE']},
|
||||
format: {
|
||||
text: {
|
||||
default: x.format?.text?.default ?? ['WHITE'],
|
||||
defaultSelected: x.format?.text?.defaultSelected ?? ['WHITE', 'BOLD'],
|
||||
},
|
||||
selector: {
|
||||
default: x.format?.selector?.default ?? x.format?.text?.default
|
||||
?? ['WHITE'],
|
||||
defaultSelected: x.format?.selector?.defaultSelected ?? ['BLUE', 'BOLD'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
interface Event extends Tag<'KEYUP' | 'KEYDOWN' | 'EXIT' | 'ACCEPT'> {}
|
||||
const EVENT: Record<string, Event> = {
|
||||
keyup: {_tag: 'KEYUP'},
|
||||
keydown: {_tag: 'KEYDOWN'},
|
||||
exit: <T>(x: T): Event => ({_tag: 'EXIT', value: x}),
|
||||
accept: (x: number): Event => ({_tag: 'ACCEPT', value: x}),
|
||||
exit: {_tag: 'EXIT'},
|
||||
accept: {_tag: 'ACCEPT'},
|
||||
}
|
||||
|
||||
type RenderText =
|
||||
(x: SelectOptions & Tag<'PainterState'>) => Promise<Either<number, Error>>
|
||||
type KeyCheck = (x: Key) => (y: Key) => boolean
|
||||
const keyCheck: KeyCheck = x => y =>
|
||||
x.name !== y.name ? false
|
||||
: x.ctrl !== y.ctrl ? false
|
||||
: x.shift !== y.shift ? false
|
||||
: x.meta !== y.meta ? false
|
||||
: true
|
||||
|
||||
type OnKeypress =
|
||||
(x: Pick<SelectOptions, 'keyup' | 'keydown' | 'exit' | 'accept'>) =>
|
||||
(y: Key) => Option<Event>
|
||||
const onKeypress: OnKeypress = x => y =>
|
||||
x.keyup.some(a => keyCheck(a)(y)) ? some(EVENT.keyup) as Option<Event>
|
||||
: x.keydown.some(a => keyCheck(a)(y)) ? some(EVENT.keydown) as Option<Event>
|
||||
: x.exit.some(a => keyCheck(a)(y)) ? some(EVENT.exit) as Option<Event>
|
||||
: x.accept.some(a => keyCheck(a)(y)) ? some(EVENT.accept) as Option<Event>
|
||||
: none
|
||||
|
||||
type KeyListener =
|
||||
(x: Pick<SelectOptions, 'keyup' | 'keydown' | 'exit' | 'accept'>) => Promise<
|
||||
Event
|
||||
>
|
||||
const keyListener: KeyListener = x => {
|
||||
readline.emitKeypressEvents(process.stdin)
|
||||
if (process.stdin.isTTY) process.stdin.setRawMode(true)
|
||||
return new Promise((resolve) => {
|
||||
process.stdin.on(
|
||||
'keypress',
|
||||
a => onKeypress(x)(a) ? resolve(onKeypress(x)(a)) : none,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
type RenderText = (x: SelectOptions & Tag<'PainterState'>) => Promise<
|
||||
Either<number, Error>
|
||||
>
|
||||
const renderText: RenderText = async (x) => {
|
||||
try {
|
||||
return right(
|
||||
@ -62,14 +134,14 @@ const renderText: RenderText = async (x) => {
|
||||
ansiText(x.selector.selected),
|
||||
'RESET' as AnsiBufferKey,
|
||||
...x.format.text.defaultSelected,
|
||||
ansiText(i),
|
||||
ansiText(i + '\n'),
|
||||
'RESET' as AnsiBufferKey,
|
||||
] : [
|
||||
...x.format.selector.default,
|
||||
ansiText(x.selector.default),
|
||||
'RESET' as AnsiBufferKey,
|
||||
...x.format.text.default,
|
||||
ansiText(i),
|
||||
ansiText(i + '\n'),
|
||||
'RESET' as AnsiBufferKey,
|
||||
]
|
||||
),
|
||||
@ -80,5 +152,29 @@ const renderText: RenderText = async (x) => {
|
||||
}
|
||||
}
|
||||
|
||||
const selectOptions: PainterEffect<SelectOptions> = x => {
|
||||
const selectOptions: PainterEffect<SelectOptions> = async (x) => {
|
||||
const render = renderText(x)
|
||||
const event = DoOption.start()
|
||||
.bind('state', x.task.state)
|
||||
.
|
||||
}
|
||||
|
||||
renderText(
|
||||
tagState(
|
||||
selectOptionsConfig({
|
||||
options: ['Option one', 'Option two', 'Option three'],
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const test = selectOptionsConfig({
|
||||
options: ['on', 'two', 'three'],
|
||||
})
|
||||
console.log(
|
||||
onKeypress({
|
||||
keyup: test.keyup,
|
||||
keydown: test.keydown,
|
||||
exit: test.exit,
|
||||
accept: test.accept,
|
||||
})({name: 'return'}),
|
||||
)
|
||||
|
||||
7
src/lib/util/types.ts
Normal file
7
src/lib/util/types.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export type RecursiveRequired<T> = Required<
|
||||
{
|
||||
[P in keyof T]: T[P] extends object | undefined
|
||||
? RecursiveRequired<Required<T[P]>>
|
||||
: T[P]
|
||||
}
|
||||
>
|
||||
Loading…
Reference in New Issue
Block a user