@hitools/tui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/element.js +46 -0
- package/elements/confirm.js +56 -0
- package/elements/error.js +22 -0
- package/elements/index.js +19 -0
- package/elements/loader.js +47 -0
- package/elements/message.js +23 -0
- package/elements/multiselect.js +62 -0
- package/elements/question.js +52 -0
- package/elements/select.js +60 -0
- package/elements/task.js +25 -0
- package/examples/index.js +1 -0
- package/index.js +85 -0
- package/package.json +29 -0
- package/prompt.js +152 -0
- package/readme.md +555 -0
- package/theme.js +328 -0
package/element.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export default class Element {
|
|
2
|
+
|
|
3
|
+
static types = {}
|
|
4
|
+
|
|
5
|
+
static registerType(type, component) {
|
|
6
|
+
this.types[type] = component
|
|
7
|
+
|
|
8
|
+
if(component.alias) {
|
|
9
|
+
this.types[component.alias] = component
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor (params = {}, {theme} = {}) {
|
|
15
|
+
|
|
16
|
+
if (typeof params === 'string') {
|
|
17
|
+
params = { type: 'message', text: params }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { type } = params
|
|
21
|
+
if (!type) throw new Error('Element type required')
|
|
22
|
+
|
|
23
|
+
const Component = Element.types[type]
|
|
24
|
+
if (!Component) throw new Error(`Unknown type "${type}"`)
|
|
25
|
+
|
|
26
|
+
const data = {}
|
|
27
|
+
|
|
28
|
+
for (const key in Component.schema) {
|
|
29
|
+
const rule = Component.schema[key]
|
|
30
|
+
const value = params[key] ?? rule.default
|
|
31
|
+
|
|
32
|
+
if (rule.required && (value === undefined || value === null)) {
|
|
33
|
+
throw new Error(`"${key}" required for type "${type}"`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
data[key] = value
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (theme) data.theme = theme
|
|
40
|
+
if (params.template) data.template = params.template
|
|
41
|
+
if (params.before) data.before = params.before
|
|
42
|
+
if (params.after) data.after = params.after
|
|
43
|
+
|
|
44
|
+
return new Component(data)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import Prompt from '../prompt.js'
|
|
2
|
+
|
|
3
|
+
export default class Confirm extends Prompt {
|
|
4
|
+
|
|
5
|
+
type = 'confirm'
|
|
6
|
+
|
|
7
|
+
constructor(data) {
|
|
8
|
+
super(data)
|
|
9
|
+
this.value = this.defaultValue ?? true
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static schema = {
|
|
13
|
+
label: { required: true },
|
|
14
|
+
defaultValue: { default: false },
|
|
15
|
+
name: { required: false },
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
actions() {
|
|
19
|
+
return {
|
|
20
|
+
'y': () => this.value = true,
|
|
21
|
+
'n': () => this.value = false,
|
|
22
|
+
'left': () => this.value = true,
|
|
23
|
+
'right': () => this.value = false,
|
|
24
|
+
'return': () => this.finished = true,
|
|
25
|
+
'space': () => this.finished = true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
build () {
|
|
30
|
+
this.clearLastRender()
|
|
31
|
+
|
|
32
|
+
const options = [
|
|
33
|
+
{ title: 'Yes', value: true },
|
|
34
|
+
{ title: 'No', value: false }
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
let output
|
|
38
|
+
|
|
39
|
+
if (this.template) {
|
|
40
|
+
const renderedOptions = options.map(opt => {
|
|
41
|
+
const state = opt.value === this.value ? this.useTemplate(opt, this.template.option.selected) : this.useTemplate(opt, this.template.option.unselected)
|
|
42
|
+
return state
|
|
43
|
+
}).join(' /')
|
|
44
|
+
|
|
45
|
+
output = this.useTemplate({label: this.label, options: renderedOptions}, this.template.current)
|
|
46
|
+
|
|
47
|
+
} else {
|
|
48
|
+
const yes = this.value ? '> Yes' : ' Yes'
|
|
49
|
+
const no = !this.value ? '> No' : ' No'
|
|
50
|
+
output = `\n${this.label}\n${yes} / ${no}\n`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.lines = output.split('\n').length
|
|
54
|
+
return output
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Prompt from '../prompt.js'
|
|
2
|
+
|
|
3
|
+
export default class ErrorMessage extends Prompt {
|
|
4
|
+
|
|
5
|
+
type = 'error'
|
|
6
|
+
|
|
7
|
+
static schema = {
|
|
8
|
+
label: { required: true }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async build() {
|
|
12
|
+
this.finished = true
|
|
13
|
+
|
|
14
|
+
if(this.template) {
|
|
15
|
+
return this.useTemplate(this, this.template)
|
|
16
|
+
} else {
|
|
17
|
+
return `Error: ${this.label}\n`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Message from './message.js'
|
|
2
|
+
import Select from './select.js'
|
|
3
|
+
import MultiSelect from './multiselect.js'
|
|
4
|
+
import Confirm from './confirm.js'
|
|
5
|
+
import ErrorMessage from './error.js'
|
|
6
|
+
import Loader from './loader.js'
|
|
7
|
+
import Question from './question.js'
|
|
8
|
+
import Task from './task.js'
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
message: Message,
|
|
12
|
+
question: Question,
|
|
13
|
+
confirm: Confirm,
|
|
14
|
+
select: Select,
|
|
15
|
+
multiselect: MultiSelect,
|
|
16
|
+
error: ErrorMessage,
|
|
17
|
+
loader: Loader,
|
|
18
|
+
task: Task,
|
|
19
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import Prompt from '../prompt.js'
|
|
2
|
+
|
|
3
|
+
export default class Loader extends Prompt {
|
|
4
|
+
|
|
5
|
+
type = 'loader'
|
|
6
|
+
|
|
7
|
+
static schema = {
|
|
8
|
+
label: { required: true },
|
|
9
|
+
time: { required: true }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async build() {
|
|
13
|
+
|
|
14
|
+
return await new Promise( (resolve, reject) => {
|
|
15
|
+
|
|
16
|
+
Prompt.out.write( this.useTemplate(this, this.template) )
|
|
17
|
+
const spinnerFrames = Prompt.theme.spinner
|
|
18
|
+
|
|
19
|
+
let i = 0
|
|
20
|
+
|
|
21
|
+
const loader = setInterval(() => {
|
|
22
|
+
Prompt.out.clearLine(0)
|
|
23
|
+
Prompt.out.cursorTo(0)
|
|
24
|
+
Prompt.out.write(spinnerFrames[i])
|
|
25
|
+
i = (i + 1) % spinnerFrames.length
|
|
26
|
+
}, 80)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
setTimeout( () => {
|
|
30
|
+
clearInterval(loader)
|
|
31
|
+
Prompt.out.clearLine(0)
|
|
32
|
+
Prompt.out.cursorTo(0)
|
|
33
|
+
this.finished = true
|
|
34
|
+
if(this.template) {
|
|
35
|
+
// resolve(this.useTemplate('✔ Done\n', this.template))
|
|
36
|
+
resolve('✔ Done')
|
|
37
|
+
// resolve(this.useTemplate('✔ Done\n'))
|
|
38
|
+
} else {
|
|
39
|
+
return `Done\n`
|
|
40
|
+
}
|
|
41
|
+
}, this.time)
|
|
42
|
+
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Prompt from '../prompt.js'
|
|
2
|
+
|
|
3
|
+
export default class Message extends Prompt {
|
|
4
|
+
|
|
5
|
+
type = 'message'
|
|
6
|
+
|
|
7
|
+
static schema = {
|
|
8
|
+
label: { required: true },
|
|
9
|
+
text: { required: false }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
build() {
|
|
13
|
+
this.finished = true
|
|
14
|
+
|
|
15
|
+
if(this.template) {
|
|
16
|
+
return this.useTemplate(this, this.template)
|
|
17
|
+
} else {
|
|
18
|
+
return `${this.label}\n`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import Prompt from '../prompt.js'
|
|
2
|
+
|
|
3
|
+
export default class MultiSelect extends Prompt {
|
|
4
|
+
|
|
5
|
+
type = 'multiselect'
|
|
6
|
+
|
|
7
|
+
constructor(data) {
|
|
8
|
+
super(data)
|
|
9
|
+
this.value = this.defaultValue
|
|
10
|
+
this.index = 0
|
|
11
|
+
|
|
12
|
+
this.options = this.prepareOptions(this.options)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static schema = {
|
|
16
|
+
label: { required: true },
|
|
17
|
+
options: { required: true },
|
|
18
|
+
defaultValue: { default: null },
|
|
19
|
+
name: { required: false },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
actions() {
|
|
23
|
+
return {
|
|
24
|
+
up: () => this.index = Math.max(0, this.index-1),
|
|
25
|
+
down: () => this.index = Math.min(this.options.length-1, this.index+1),
|
|
26
|
+
space: () => this.options[this.index].selected = this.options[this.index].selected ? false : true,
|
|
27
|
+
return: () => {
|
|
28
|
+
this.finished = true
|
|
29
|
+
this.value = this.options.filter(i => i.selected)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
build () {
|
|
35
|
+
|
|
36
|
+
this.clearLastRender()
|
|
37
|
+
|
|
38
|
+
let output
|
|
39
|
+
|
|
40
|
+
if (this.template) {
|
|
41
|
+
|
|
42
|
+
const renderedOptions = this.options.map( (opt, i) => {
|
|
43
|
+
let state = i === this.index ? this.template.option.startCurrent : this.template.option.start
|
|
44
|
+
state += opt.selected ? this.useTemplate(opt, this.template.option.selected) : this.useTemplate(opt, this.template.option.unselected)
|
|
45
|
+
return state
|
|
46
|
+
}).join('')
|
|
47
|
+
|
|
48
|
+
output = this.useTemplate({label: this.label, options: renderedOptions}, this.finished ? this.template.finished : this.template.current)
|
|
49
|
+
|
|
50
|
+
} else {
|
|
51
|
+
output = `\n${this.label}`
|
|
52
|
+
options.forEach( (opt, i) => {
|
|
53
|
+
output += i === this.index ? `\n > [ ] ${opt.title}` : `\n [ ] ${opt.title}`
|
|
54
|
+
})
|
|
55
|
+
output += '\n'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.lines = output.split('\n').length
|
|
59
|
+
return output
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import Prompt from '../prompt.js'
|
|
2
|
+
|
|
3
|
+
export default class Question extends Prompt {
|
|
4
|
+
|
|
5
|
+
type = 'question'
|
|
6
|
+
static alias = 'ask'
|
|
7
|
+
|
|
8
|
+
constructor (data) {
|
|
9
|
+
super(data)
|
|
10
|
+
this.value = this.defaultValue
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static schema = {
|
|
14
|
+
label: { required: true },
|
|
15
|
+
placeholder: { required: true },
|
|
16
|
+
defaultValue: { default: '' },
|
|
17
|
+
name: { required: false },
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
actions () {
|
|
21
|
+
return {
|
|
22
|
+
return: () => {
|
|
23
|
+
if(!this.value) this.value = this.placeholder
|
|
24
|
+
this.finished = true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
build () {
|
|
30
|
+
|
|
31
|
+
this.clearLastRender()
|
|
32
|
+
|
|
33
|
+
let output
|
|
34
|
+
|
|
35
|
+
if (this.template) {
|
|
36
|
+
output = this.useTemplate(this, this.template)
|
|
37
|
+
} else {
|
|
38
|
+
|
|
39
|
+
output = `\n${this.label}`
|
|
40
|
+
if(this.value) {
|
|
41
|
+
output += ` ${this.value}`
|
|
42
|
+
} else {
|
|
43
|
+
output += ` ${this.placeholder}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.lines = output.split('\n').length
|
|
49
|
+
return output
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Prompt from '../prompt.js'
|
|
2
|
+
|
|
3
|
+
export default class Select extends Prompt {
|
|
4
|
+
|
|
5
|
+
type = 'select'
|
|
6
|
+
|
|
7
|
+
constructor(data) {
|
|
8
|
+
super(data)
|
|
9
|
+
this.value = this.defaultValue
|
|
10
|
+
this.index = 0
|
|
11
|
+
|
|
12
|
+
this.options = this.prepareOptions(this.options)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static schema = {
|
|
16
|
+
label: { required: true },
|
|
17
|
+
options: { required: true },
|
|
18
|
+
defaultValue: { default: null },
|
|
19
|
+
name: { required: false },
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
actions() {
|
|
23
|
+
return {
|
|
24
|
+
up: () => this.index = Math.max(0, this.index-1),
|
|
25
|
+
down: () => this.index = Math.min(this.options.length-1, this.index+1),
|
|
26
|
+
return: () => {
|
|
27
|
+
this.finished = true
|
|
28
|
+
this.value = this.options[this.index]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
build () {
|
|
34
|
+
|
|
35
|
+
this.clearLastRender()
|
|
36
|
+
|
|
37
|
+
let output
|
|
38
|
+
|
|
39
|
+
if (this.template) {
|
|
40
|
+
|
|
41
|
+
const renderedOptions = this.options.map( (opt, i) => {
|
|
42
|
+
const state = i === this.index ? this.useTemplate(opt, this.template.option.selected) : this.useTemplate(opt, this.template.option.unselected)
|
|
43
|
+
return state
|
|
44
|
+
}).join('')
|
|
45
|
+
|
|
46
|
+
output = this.useTemplate({label: this.label, options: renderedOptions}, this.finished ? this.template.finished : this.template.current)
|
|
47
|
+
|
|
48
|
+
} else {
|
|
49
|
+
output = `\n${this.label}`
|
|
50
|
+
options.forEach( (opt, i) => {
|
|
51
|
+
output += i === this.index ? `\n > ${opt.title}` : `\n ${opt.title}`
|
|
52
|
+
})
|
|
53
|
+
output += '\n'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.lines = output.split('\n').length
|
|
57
|
+
return output
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
package/elements/task.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Prompt from '../prompt.js'
|
|
2
|
+
|
|
3
|
+
export default class Task extends Prompt {
|
|
4
|
+
|
|
5
|
+
type = 'task'
|
|
6
|
+
|
|
7
|
+
static schema = {
|
|
8
|
+
run: { required: true },
|
|
9
|
+
params: { required: false },
|
|
10
|
+
name: { required: false },
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async build () {
|
|
14
|
+
|
|
15
|
+
Prompt.out.write( this.useTemplate(this, this.template) )
|
|
16
|
+
|
|
17
|
+
let {value, finished} = await this.run(this, this.params)
|
|
18
|
+
if(value) this.value = value
|
|
19
|
+
if(finished) this.finished = finished
|
|
20
|
+
|
|
21
|
+
return value ?? null
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// in progress
|
package/index.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Components from './elements/index.js'
|
|
2
|
+
import Element from './element.js'
|
|
3
|
+
import Prompt from './prompt.js'
|
|
4
|
+
|
|
5
|
+
import Theme from './theme.js'
|
|
6
|
+
import Template from '/Users/hodunay/Packages/drafts/templates/index.js'
|
|
7
|
+
import Validator from '/Users/hodunay/Packages/drafts/cli-manager/validate/validate.js'
|
|
8
|
+
|
|
9
|
+
// Registering Components
|
|
10
|
+
Object.keys(Components).forEach(c => Element.registerType(c.toLowerCase(), Components[c]))
|
|
11
|
+
|
|
12
|
+
export default class TUI {
|
|
13
|
+
|
|
14
|
+
constructor(config) {
|
|
15
|
+
|
|
16
|
+
config.theme = true
|
|
17
|
+
config.validator = true
|
|
18
|
+
|
|
19
|
+
Prompt.use('in', process.stdin)
|
|
20
|
+
Prompt.use('out', process.stdout)
|
|
21
|
+
|
|
22
|
+
this.useTypes()
|
|
23
|
+
this.setup(config)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
useTypes () {
|
|
27
|
+
Object.keys(Element.types).map( type => {
|
|
28
|
+
this[type] = (args) => {
|
|
29
|
+
|
|
30
|
+
let params = {}
|
|
31
|
+
if (typeof args === 'string') {
|
|
32
|
+
params.type = 'message'
|
|
33
|
+
params.label = args
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// todo: refactor
|
|
37
|
+
let exists = Element.types[type]
|
|
38
|
+
if (exists) {
|
|
39
|
+
params = {
|
|
40
|
+
type: exists.type ?? exists.alias,
|
|
41
|
+
...args
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.flow.push( new Element(params) )
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setup (config) {
|
|
51
|
+
|
|
52
|
+
if(Array.isArray(config)) {
|
|
53
|
+
this.flow = config.map(el => new Element(el))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if(config.theme) {
|
|
57
|
+
Prompt.use('theme', Theme)
|
|
58
|
+
Prompt.use('templateEngine', new Template())
|
|
59
|
+
// Prompt.use('theme', config.theme)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if(config.flow) {
|
|
63
|
+
this.flow = config.flow ? config.flow.map(el => new Element(el)) : []
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if(config.validator) {
|
|
67
|
+
Prompt.use('validate', Validator)
|
|
68
|
+
// Prompt.use('validator', config.validator)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if(config.elements) {
|
|
72
|
+
config.elements.map( el => {
|
|
73
|
+
Element.registerType(el.name, el.component)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async run(context = {}) {
|
|
79
|
+
for (const el of this.flow) {
|
|
80
|
+
const result = await el.render()
|
|
81
|
+
if (el.name && result) context[el.name] = result.value
|
|
82
|
+
}
|
|
83
|
+
return context
|
|
84
|
+
}
|
|
85
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hitools/tui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Terminal UI with basic components set like Question, Select, Multi-select, Message. ",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "jest"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/hodunay/tui.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"tui",
|
|
16
|
+
"cli",
|
|
17
|
+
"nodejs",
|
|
18
|
+
"javascript"
|
|
19
|
+
],
|
|
20
|
+
"author": "Igor Hodunay",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/hodunay/tui/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/hodunay/tui#readme",
|
|
26
|
+
"directories": {
|
|
27
|
+
"example": "examples"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/prompt.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import readline from 'readline'
|
|
2
|
+
|
|
3
|
+
function hideCursor () {
|
|
4
|
+
process.stdout.write('\x1B[?25l')
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function showCursor () {
|
|
8
|
+
process.stdout.write('\x1B[?25l')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
process.on('exit', showCursor)
|
|
12
|
+
process.on('SIGINT', showCursor)
|
|
13
|
+
|
|
14
|
+
export default class Prompt {
|
|
15
|
+
|
|
16
|
+
constructor (data) {
|
|
17
|
+
Object.assign(this, data)
|
|
18
|
+
this.finished = false
|
|
19
|
+
this.lines = 0
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static use (type, data) {
|
|
23
|
+
this[type] = data
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setup () {
|
|
27
|
+
readline.emitKeypressEvents(Prompt.in)
|
|
28
|
+
if (Prompt.in.isTTY) Prompt.in.setRawMode(true)
|
|
29
|
+
|
|
30
|
+
if(Prompt.templateEngine)
|
|
31
|
+
this.template = Prompt.theme ? Prompt.theme.template[this.type] : false
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
clear () {
|
|
35
|
+
Prompt.out.cursorTo(0, 0)
|
|
36
|
+
Prompt.out.clearScreenDown()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
teardown () {
|
|
40
|
+
if (Prompt.in.isTTY) Prompt.in.setRawMode(false)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
prepareOptions (options, assign = false) {
|
|
44
|
+
if(Array.isArray(options)) {
|
|
45
|
+
options = options.map( (item, i) => {
|
|
46
|
+
let opt = { title: item, value: i }
|
|
47
|
+
// if(assign) opt = Object.assign(opt, assign)
|
|
48
|
+
return opt
|
|
49
|
+
})
|
|
50
|
+
return options
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
else if (typeof options === 'object') {
|
|
54
|
+
return options
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
clearLastRender () {
|
|
59
|
+
if (!this.lines) return
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < this.lines -1; i++) {
|
|
62
|
+
Prompt.out.clearLine(0)
|
|
63
|
+
Prompt.out.moveCursor(0, -1)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Prompt.out.cursorTo(0)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async render () {
|
|
70
|
+
|
|
71
|
+
if (this.before) this.before(this)
|
|
72
|
+
|
|
73
|
+
this.setup()
|
|
74
|
+
|
|
75
|
+
if(!this.actions?.()) {
|
|
76
|
+
let response = await this.build()
|
|
77
|
+
Prompt.out.write( response )
|
|
78
|
+
return response
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return await new Promise((resolve, reject) => {
|
|
82
|
+
|
|
83
|
+
const cleanup = () => {
|
|
84
|
+
Prompt.in.off('keypress', handler)
|
|
85
|
+
this.teardown()
|
|
86
|
+
Prompt.out.write('\x1B[?25h') // show cursor
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const handler = (chunk, key) => {
|
|
90
|
+
|
|
91
|
+
if (key?.ctrl && key.name === 'c') {
|
|
92
|
+
cleanup()
|
|
93
|
+
return reject(new Error('Interrupted'))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const actions = this.actions?.()
|
|
97
|
+
|
|
98
|
+
if (typeof actions?.[key?.name] === 'function') {
|
|
99
|
+
hideCursor()
|
|
100
|
+
actions[key.name]()
|
|
101
|
+
Prompt.out.write( this.build() )
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
else if (!this.finished && key?.name && chunk) {
|
|
105
|
+
|
|
106
|
+
if (key?.name === 'backspace') {
|
|
107
|
+
this.value = this.value.slice(0, -1)
|
|
108
|
+
} else {
|
|
109
|
+
this.value = this.value + chunk.toString()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Prompt.out.clearLine()
|
|
113
|
+
Prompt.out.cursorTo(0)
|
|
114
|
+
Prompt.out.write(this.build())
|
|
115
|
+
|
|
116
|
+
// validate realtime
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (this.finished) {
|
|
120
|
+
cleanup()
|
|
121
|
+
if (this.after) this.after(this)
|
|
122
|
+
resolve(this)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// to clear full screen
|
|
126
|
+
// this.clear()
|
|
127
|
+
// Prompt.out.write(this.build())
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if(Prompt.validator) {
|
|
133
|
+
// console.log(Prompt.validator)
|
|
134
|
+
// const validation = Prompt.validator.check( state.value.length ? state.value : state.defaultValue, validator, { live: false } )
|
|
135
|
+
// state.error = !validation.valid
|
|
136
|
+
// state.errorMessage = validation.error
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Prompt.in.on('keypress', handler)
|
|
140
|
+
let response = this.build()
|
|
141
|
+
Prompt.out.write( response )
|
|
142
|
+
showCursor()
|
|
143
|
+
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
useTemplate (data, template = false) {
|
|
149
|
+
return Prompt.templateEngine ? Prompt.templateEngine(data, template) : false
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
}
|