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<
|
type PainterEffect<
|
||||||
E,
|
E,
|
||||||
T extends object = {},
|
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'>
|
type EffectState = <T extends object = {}>(x: T) => T & Tag<'PainterState'>
|
||||||
const effectState: EffectState = x => ({_tag: 'PainterState', ...x})
|
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 {
|
import {
|
||||||
exit as EXIT,
|
exit as EXIT,
|
||||||
type PainterEffect,
|
type PainterEffect,
|
||||||
type PainterState,
|
type PainterState,
|
||||||
type PainterTask,
|
type PainterTask,
|
||||||
|
tagState,
|
||||||
task as TASK,
|
task as TASK,
|
||||||
} from '../painter/lifecycle'
|
} from '../painter/lifecycle'
|
||||||
import {
|
import {
|
||||||
@ -13,8 +15,9 @@ import {
|
|||||||
writeAnsiU,
|
writeAnsiU,
|
||||||
} from '../util/ansi'
|
} from '../util/ansi'
|
||||||
import { DoEither, type Either, left, right } from '../util/basic/either'
|
import { DoEither, type Either, left, right } from '../util/basic/either'
|
||||||
import { some } from '../util/basic/option'
|
import { DoOption, getOrElse, map, none, type Option, some } from '../util/basic/option'
|
||||||
import type { Tag } from '../util/basic/utility'
|
import { hasTag, type Tag } from '../util/basic/utility'
|
||||||
|
import type { RecursiveRequired } from '../util/types'
|
||||||
|
|
||||||
interface SelectOptions {
|
interface SelectOptions {
|
||||||
keyup: Array<Key>
|
keyup: Array<Key>
|
||||||
@ -37,18 +40,87 @@ interface SelectOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Event extends Tag<'KEYUP' | 'KEYDOWN' | 'EXIT' | 'ACCEPT'> {
|
type PartialSelectOptions =
|
||||||
value?: any
|
& Partial<Omit<SelectOptions, 'options' | 'header'>>
|
||||||
}
|
& {
|
||||||
const EVENT: Record<string, Event | ((...args: any[]) => Event)> = {
|
options: SelectOptions['options'],
|
||||||
keyup: {_tag: 'KEYUP'},
|
header?: {
|
||||||
keydown: {_tag: 'KEYDOWN'},
|
text: NonNullable<SelectOptions['header']>['text'],
|
||||||
exit: <T>(x: T): Event => ({_tag: 'EXIT', value: x}),
|
format?: NonNullable<SelectOptions['header']>['format'],
|
||||||
accept: (x: number): Event => ({_tag: 'ACCEPT', value: x}),
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type RenderText =
|
type SelectOptionsConfig = (x: PartialSelectOptions) => SelectOptions
|
||||||
(x: SelectOptions & Tag<'PainterState'>) => Promise<Either<number, Error>>
|
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: {_tag: 'EXIT'},
|
||||||
|
accept: {_tag: 'ACCEPT'},
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => {
|
const renderText: RenderText = async (x) => {
|
||||||
try {
|
try {
|
||||||
return right(
|
return right(
|
||||||
@ -62,14 +134,14 @@ const renderText: RenderText = async (x) => {
|
|||||||
ansiText(x.selector.selected),
|
ansiText(x.selector.selected),
|
||||||
'RESET' as AnsiBufferKey,
|
'RESET' as AnsiBufferKey,
|
||||||
...x.format.text.defaultSelected,
|
...x.format.text.defaultSelected,
|
||||||
ansiText(i),
|
ansiText(i + '\n'),
|
||||||
'RESET' as AnsiBufferKey,
|
'RESET' as AnsiBufferKey,
|
||||||
] : [
|
] : [
|
||||||
...x.format.selector.default,
|
...x.format.selector.default,
|
||||||
ansiText(x.selector.default),
|
ansiText(x.selector.default),
|
||||||
'RESET' as AnsiBufferKey,
|
'RESET' as AnsiBufferKey,
|
||||||
...x.format.text.default,
|
...x.format.text.default,
|
||||||
ansiText(i),
|
ansiText(i + '\n'),
|
||||||
'RESET' as AnsiBufferKey,
|
'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