@electerm/electerm-react 3.10.0 → 3.11.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.
@@ -0,0 +1,157 @@
1
+ /**
2
+ * terminal interactive UI - renders a single interactive event modal
3
+ */
4
+
5
+ import { Form, Button } from 'antd'
6
+ import Modal from '../common/modal'
7
+ import InputAutoFocus from '../common/input-auto-focus'
8
+
9
+ const e = window.translate
10
+ const FormItem = Form.Item
11
+
12
+ export default function TermInteractiveUI ({
13
+ opts,
14
+ onSend,
15
+ onClose
16
+ }) {
17
+ const [form] = Form.useForm()
18
+
19
+ function onCancel () {
20
+ onSend({
21
+ id: opts.id,
22
+ results: []
23
+ })
24
+ onClose()
25
+ }
26
+ function onOk () {
27
+ form.submit()
28
+ }
29
+ function onConfirm () {
30
+ onSend({
31
+ id: opts.id,
32
+ results: [opts.options.confirmResult || 'yes']
33
+ })
34
+ onClose()
35
+ }
36
+ function onIgnore () {
37
+ onSend({
38
+ id: opts.id,
39
+ results: Object.keys(opts.options.prompts).map(() => '')
40
+ })
41
+ onClose()
42
+ }
43
+ function onFinish (res) {
44
+ onSend({
45
+ id: opts.id,
46
+ results: Object.values(res)
47
+ })
48
+ onClose()
49
+ }
50
+ function renderFormItem (pro, i) {
51
+ const {
52
+ prompt,
53
+ echo
54
+ } = pro
55
+ const note = (opts.options.instructions || [])[i]
56
+ const type = echo
57
+ ? 'input'
58
+ : 'password'
59
+ return (
60
+ <FormItem
61
+ key={prompt + i}
62
+ label={prompt}
63
+ rules={[{
64
+ required: true, message: 'required'
65
+ }]}
66
+ >
67
+ <div>
68
+ <pre>{note}</pre>
69
+ </div>
70
+ <FormItem noStyle name={'item' + i}>
71
+ <InputAutoFocus
72
+ type={type}
73
+ placeholder={note}
74
+ />
75
+ </FormItem>
76
+ </FormItem>
77
+ )
78
+ }
79
+ function renderConfirmBody () {
80
+ const instructions = opts.options.instructions || []
81
+ return (
82
+ <div>
83
+ {
84
+ instructions.map((note, index) => {
85
+ return <pre key={note + index}>{note}</pre>
86
+ })
87
+ }
88
+ <FormItem>
89
+ <Button
90
+ type='primary'
91
+ onClick={onConfirm}
92
+ >
93
+ {opts.options.submitText || e('submit')}
94
+ </Button>
95
+ <Button
96
+ className='mg1l'
97
+ onClick={onCancel}
98
+ >
99
+ {opts.options.cancelText || e('cancel')}
100
+ </Button>
101
+ </FormItem>
102
+ </div>
103
+ )
104
+ }
105
+ const props = {
106
+ maskClosable: false,
107
+ okText: e('submit'),
108
+ onCancel,
109
+ onOk,
110
+ closable: false,
111
+ open: true,
112
+ title: opts.options?.name || '?',
113
+ footer: null
114
+ }
115
+ return (
116
+ <Modal
117
+ {...props}
118
+ >
119
+ {
120
+ opts.options?.mode === 'confirm'
121
+ ? renderConfirmBody()
122
+ : (
123
+ <Form
124
+ form={form}
125
+ layout='vertical'
126
+ onFinish={onFinish}
127
+ >
128
+ {
129
+ opts.options.prompts.map(renderFormItem)
130
+ }
131
+ <FormItem>
132
+ <Button
133
+ type='primary'
134
+ htmlType='submit'
135
+ >
136
+ {e('submit')}
137
+ </Button>
138
+ <Button
139
+ type='dashed'
140
+ className='mg1l'
141
+ onClick={onIgnore}
142
+ >
143
+ {e('ignore')}
144
+ </Button>
145
+ <Button
146
+ className='mg1l'
147
+ onClick={onCancel}
148
+ >
149
+ {e('cancel')}
150
+ </Button>
151
+ </FormItem>
152
+ </Form>
153
+ )
154
+ }
155
+ </Modal>
156
+ )
157
+ }
@@ -1,30 +1,45 @@
1
1
  /**
2
- * handle terminal interactive operation
2
+ * handle terminal interactive operation - queue based
3
3
  */
4
4
 
5
- import { useEffect, useState } from 'react'
6
- import { Form, Button } from 'antd'
7
- import Modal from '../common/modal'
8
- import InputAutoFocus from '../common/input-auto-focus'
5
+ import { useEffect, useState, useRef, useCallback } from 'react'
9
6
  import wait from '../../common/wait'
10
-
11
- const e = window.translate
12
- const FormItem = Form.Item
7
+ import TermInteractiveUI from './terminal-interactive-ui'
13
8
 
14
9
  export default function TermInteractive () {
15
- const [opts, setter] = useState(null)
16
- const [form] = Form.useForm()
10
+ const [current, setCurrent] = useState(null)
11
+ const queueRef = useRef([])
12
+ const hasCurrentRef = useRef(false)
13
+
17
14
  function updateTab (data) {
18
15
  window.store.updateTab(data.tabId, data.update)
19
16
  }
20
- function onMsg (e) {
17
+
18
+ function processNext () {
19
+ const next = queueRef.current.shift()
20
+ if (next) {
21
+ setCurrent(next)
22
+ } else {
23
+ hasCurrentRef.current = false
24
+ setCurrent(null)
25
+ }
26
+ }
27
+
28
+ const onMsgRef = useRef(null)
29
+ onMsgRef.current = function onMsg (e) {
21
30
  if (
22
31
  e &&
23
32
  e.data &&
24
33
  typeof e.data === 'string' &&
25
34
  e.data.includes('session-interactive')
26
35
  ) {
27
- setter(JSON.parse(e.data))
36
+ const parsed = JSON.parse(e.data)
37
+ if (hasCurrentRef.current) {
38
+ queueRef.current.push(parsed)
39
+ } else {
40
+ hasCurrentRef.current = true
41
+ setCurrent(parsed)
42
+ }
28
43
  } else if (
29
44
  e &&
30
45
  e.data &&
@@ -34,166 +49,52 @@ export default function TermInteractive () {
34
49
  updateTab(JSON.parse(e.data))
35
50
  }
36
51
  }
37
- function clear () {
38
- setter(null)
39
- form.resetFields()
40
- }
41
- function onCancel () {
42
- window.et.commonWs.s({
43
- id: opts.id,
44
- results: []
45
- })
46
- clear()
47
- }
48
- function onOk () {
49
- form.submit()
50
- }
51
- function onConfirm () {
52
- window.et.commonWs.s({
53
- id: opts.id,
54
- results: [opts.options.confirmResult || 'yes']
55
- })
56
- clear()
57
- }
58
- function onIgnore () {
59
- window.et.commonWs.s({
60
- id: opts.id,
61
- results: Object.keys(opts.options.prompts).map(() => '')
62
- })
63
- clear()
64
- }
65
- function onFinish (res) {
66
- window.et.commonWs.s({
67
- id: opts.id,
68
- results: Object.values(res)
69
- })
70
- clear()
71
- }
72
- function renderFormItem (pro, i) {
73
- const {
74
- prompt,
75
- echo
76
- } = pro
77
- const note = (opts.options.instructions || [])[i]
78
- const type = echo
79
- ? 'input'
80
- : 'password'
81
- return (
82
- <FormItem
83
- key={prompt + i}
84
- label={prompt}
85
- rules={[{
86
- required: true, message: 'required'
87
- }]}
88
- >
89
- <div>
90
- <pre>{note}</pre>
91
- </div>
92
- <FormItem noStyle name={'item' + i}>
93
- <InputAutoFocus
94
- type={type}
95
- placeholder={note}
96
- />
97
- </FormItem>
98
- </FormItem>
99
- )
52
+
53
+ function onSend (data) {
54
+ window.et.commonWs.s(data)
100
55
  }
101
- function renderConfirmBody () {
102
- const instructions = opts.options.instructions || []
103
- return (
104
- <div>
105
- {
106
- instructions.map((note, index) => {
107
- return <pre key={note + index}>{note}</pre>
108
- })
56
+
57
+ const onClose = useCallback(() => {
58
+ processNext()
59
+ }, [])
60
+
61
+ useEffect(() => {
62
+ let cancelled = false
63
+ function handler (e) {
64
+ if (!cancelled) {
65
+ onMsgRef.current(e)
66
+ }
67
+ }
68
+ async function initWatch () {
69
+ for (;;) {
70
+ if (cancelled) {
71
+ return
72
+ }
73
+ if (window.et.commonWs) {
74
+ window.et.commonWs.addEventListener('message', handler)
75
+ return
109
76
  }
110
- <FormItem>
111
- <Button
112
- type='primary'
113
- onClick={onConfirm}
114
- >
115
- {opts.options.submitText || e('submit')}
116
- </Button>
117
- <Button
118
- className='mg1l'
119
- onClick={onCancel}
120
- >
121
- {opts.options.cancelText || e('cancel')}
122
- </Button>
123
- </FormItem>
124
- </div>
125
- )
126
- }
127
- async function initWatch () {
128
- let done = false
129
- while (!done) {
130
- if (window.et.commonWs) {
131
- window.et.commonWs.addEventListener('message', onMsg)
132
- done = true
133
- } else {
134
77
  await wait(400)
135
78
  }
136
79
  }
137
- }
138
- function init () {
139
80
  initWatch()
140
- }
141
- useEffect(() => {
142
- init()
81
+ return () => {
82
+ cancelled = true
83
+ if (window.et.commonWs) {
84
+ window.et.commonWs.removeEventListener('message', handler)
85
+ }
86
+ }
143
87
  }, [])
144
- if (!opts) {
88
+
89
+ if (!current) {
145
90
  return null
146
91
  }
147
- const props = {
148
- maskClosable: false,
149
- okText: e('submit'),
150
- onCancel,
151
- onOk,
152
- closable: false,
153
- open: true,
154
- title: opts.options?.name || '?',
155
- footer: null
156
- }
92
+
157
93
  return (
158
- <Modal
159
- {...props}
160
- >
161
- {
162
- opts.options?.mode === 'confirm'
163
- ? renderConfirmBody()
164
- : (
165
- <Form
166
- form={form}
167
- layout='vertical'
168
- onFinish={onFinish}
169
- >
170
- {
171
- opts.options.prompts.map(renderFormItem)
172
- }
173
- <FormItem>
174
- <Button
175
- type='primary'
176
- htmlType='submit'
177
- >
178
- {e('submit')}
179
- </Button>
180
- <Button
181
- type='dashed'
182
- className='mg1l'
183
- onClick={onIgnore}
184
- >
185
- {e('ignore')}
186
- </Button>
187
- <Button
188
- className='mg1l'
189
- onClick={onCancel}
190
- >
191
- {e('cancel')}
192
- </Button>
193
- </FormItem>
194
- </Form>
195
- )
196
- }
197
- </Modal>
94
+ <TermInteractiveUI
95
+ opts={current}
96
+ onSend={onSend}
97
+ onClose={onClose}
98
+ />
198
99
  )
199
100
  }
@@ -0,0 +1,11 @@
1
+ import { lazy, Suspense } from 'react'
2
+
3
+ const TerminalInfo = lazy(() => import('./terminal-info'))
4
+
5
+ export default function TerminalInfoEntry (props) {
6
+ return (
7
+ <Suspense fallback={null}>
8
+ <TerminalInfo {...props} />
9
+ </Suspense>
10
+ )
11
+ }
@@ -0,0 +1,11 @@
1
+ import { lazy, Suspense } from 'react'
2
+
3
+ const TextEditor = lazy(() => import('./text-editor'))
4
+
5
+ export default function TextEditorEntry (props) {
6
+ return (
7
+ <Suspense fallback={null}>
8
+ <TextEditor {...props} />
9
+ </Suspense>
10
+ )
11
+ }
@@ -2,9 +2,10 @@
2
2
  * Widget form component
3
3
  */
4
4
  import React, { useState, useEffect } from 'react'
5
- import { Form, Input, InputNumber, Switch, Select, Button, Tooltip, Alert } from 'antd'
5
+ import { Form, Input, InputNumber, Switch, Select, Button, Tooltip, Alert, Space } from 'antd'
6
6
  import { formItemLayout, tailFormItemLayout } from '../../common/form-layout'
7
7
  import HelpIcon from '../common/help-icon'
8
+ import { nanoid } from 'nanoid'
8
9
  import BatchOpEditor from '../batch-op/batch-op-editor'
9
10
 
10
11
  export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInstance }) {
@@ -43,12 +44,36 @@ export default function WidgetForm ({ widget, onSubmit, loading, hasRunningInsta
43
44
  }
44
45
 
45
46
  const renderFormItem = (config) => {
46
- const { name, type, description, choices } = config
47
+ const { name, type, description, choices, showGenerator } = config
47
48
  let control = null
48
49
 
49
50
  switch (type) {
50
51
  case 'string':
51
52
  control = <Input placeholder={description} />
53
+ if (showGenerator) {
54
+ return (
55
+ <Form.Item
56
+ key={name}
57
+ {...formItemLayout}
58
+ label={name}
59
+ tooltip={description}
60
+ >
61
+ <Space.Compact style={{ width: '100%' }}>
62
+ <Form.Item
63
+ noStyle
64
+ name={name}
65
+ >
66
+ <Input placeholder={description} />
67
+ </Form.Item>
68
+ <Button
69
+ onClick={() => form.setFieldValue(name, 'ett_' + nanoid())}
70
+ >
71
+ Generate
72
+ </Button>
73
+ </Space.Compact>
74
+ </Form.Item>
75
+ )
76
+ }
52
77
  break
53
78
  case 'textarea':
54
79
  control = <Input.TextArea autoSize={{ minRows: 3 }} placeholder={description} />
@@ -114,7 +114,10 @@ async function onMsg (e) {
114
114
  } else if (action === 'addEventListener') {
115
115
  const ws = self.insts[wsId]
116
116
  if (ws) {
117
- ws.cb = (e) => {
117
+ if (!ws.cbs) {
118
+ ws.cbs = {}
119
+ }
120
+ const cb = (e) => {
118
121
  send({
119
122
  wsId,
120
123
  id,
@@ -123,13 +126,14 @@ async function onMsg (e) {
123
126
  }
124
127
  })
125
128
  }
126
- ws.addEventListener(type, ws.cb)
129
+ ws.cbs[id] = cb
130
+ ws.addEventListener(type, cb)
127
131
  }
128
132
  } else if (action === 'removeEventListener') {
129
133
  const ws = self.insts[wsId]
130
- if (ws) {
131
- ws.removeEventListener(type, ws.cb)
132
- delete ws.cb
134
+ if (ws && ws.cbs && ws.cbs[id]) {
135
+ ws.removeEventListener(type, ws.cbs[id])
136
+ delete ws.cbs[id]
133
137
  }
134
138
  }
135
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "3.10.0",
3
+ "version": "3.11.0",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",