@electerm/electerm-react 5.5.25 → 5.5.56

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.
Files changed (66) hide show
  1. package/README.md +5 -5
  2. package/client/common/bookmark-schemas.js +4 -0
  3. package/client/common/constants.js +3 -0
  4. package/client/common/create-title.jsx +5 -1
  5. package/client/common/default-setting.js +2 -0
  6. package/client/common/parse-quick-connect.js +1 -0
  7. package/client/common/parse-vv.js +119 -25
  8. package/client/common/parse-widget-log.js +57 -0
  9. package/client/common/vv-to-tab.js +120 -0
  10. package/client/components/ai/agent-tool-call-card.jsx +26 -7
  11. package/client/components/ai/agent.js +205 -45
  12. package/client/components/ai/ai-auto-compress.js +119 -0
  13. package/client/components/ai/ai-chat-history-item.jsx +133 -84
  14. package/client/components/ai/ai-chat.jsx +135 -30
  15. package/client/components/ai/ai-compress.js +95 -0
  16. package/client/components/ai/ai-config-props.js +12 -1
  17. package/client/components/ai/ai-config.jsx +43 -16
  18. package/client/components/ai/ai-config.styl +20 -0
  19. package/client/components/ai/ai-context-indicator.jsx +83 -0
  20. package/client/components/ai/ai-context.js +272 -0
  21. package/client/components/ai/ai-output.jsx +5 -6
  22. package/client/components/ai/ai-presets.js +1 -16
  23. package/client/components/ai/ai.styl +105 -6
  24. package/client/components/ai/copy-icon.jsx +31 -0
  25. package/client/components/batch-op/batch-op-runner.jsx +1 -0
  26. package/client/components/bookmark-form/bookmark-schema.js +1 -0
  27. package/client/components/bookmark-form/common/ssh-agent.jsx +31 -21
  28. package/client/components/bookmark-form/common/vv-file-field.jsx +3 -2
  29. package/client/components/bookmark-form/config/spice.js +21 -0
  30. package/client/components/bookmark-form/config/ssh.js +1 -0
  31. package/client/components/bookmark-form/fix-bookmark-default.js +1 -0
  32. package/client/components/file-transfer/transfer.jsx +3 -119
  33. package/client/components/footer/cmd-history.jsx +279 -65
  34. package/client/components/footer/cmd-history.styl +68 -1
  35. package/client/components/footer/footer-entry.jsx +7 -1
  36. package/client/components/layout/layout.jsx +12 -19
  37. package/client/components/main/main.jsx +15 -1
  38. package/client/components/quick-commands/qm.styl +58 -0
  39. package/client/components/quick-commands/quick-commands-box.jsx +180 -83
  40. package/client/components/quick-commands/quick-commands-select.jsx +11 -0
  41. package/client/components/session/session-control.jsx +35 -2
  42. package/client/components/session/session-control.styl +5 -0
  43. package/client/components/session/session.jsx +13 -0
  44. package/client/components/setting-panel/setting-common.jsx +2 -0
  45. package/client/components/setting-panel/tab-widgets.jsx +27 -7
  46. package/client/components/sftp/file-item.jsx +0 -13
  47. package/client/components/side-panel-r/side-panel-r.jsx +11 -4
  48. package/client/components/sidebar/bookmark-select.jsx +14 -2
  49. package/client/components/sidebar/history-item.jsx +20 -4
  50. package/client/components/sidebar/history.jsx +7 -0
  51. package/client/components/terminal/mixins/term-touch.js +18 -0
  52. package/client/components/tree-list/tree-list.jsx +31 -3
  53. package/client/components/widgets/widget-control.jsx +4 -2
  54. package/client/components/widgets/widget-instance-detail.jsx +370 -0
  55. package/client/components/widgets/widget-instance.jsx +30 -13
  56. package/client/components/widgets/widget-instances.jsx +3 -1
  57. package/client/components/widgets/widgets-list.jsx +8 -0
  58. package/client/components/widgets/widgets.styl +100 -0
  59. package/client/store/common.js +200 -54
  60. package/client/store/init-state.js +15 -0
  61. package/client/store/load-data.js +73 -0
  62. package/client/store/store.js +6 -0
  63. package/client/store/watch.js +3 -1
  64. package/client/store/widgets.js +23 -5
  65. package/package.json +1 -1
  66. package/client/components/file-transfer/zip.js +0 -42
@@ -2,6 +2,58 @@
2
2
  max-height calc(100vh - 100px)
3
3
  overflow-y scroll
4
4
  padding 20px 0 10px 0
5
+
6
+ // Docked in the right side panel the panel *is* the container, so the quick
7
+ // commands fill it instead of sizing themselves like the footer box. The parent
8
+ // (.right-side-panel-content) is an absolutely positioned box with top and
9
+ // bottom set, so 100% resolves to a real height and the list scrolls inside it
10
+ // rather than scrolling the panel.
11
+ .qm-panel-in-right-panel
12
+ display flex
13
+ flex-direction column
14
+ width 100%
15
+ height 100%
16
+ > .pd2
17
+ display flex
18
+ flex-direction column
19
+ height 100%
20
+ min-height 0
21
+ width 100%
22
+ .qm-flex
23
+ flex-shrink 0
24
+ // the footer box is as wide as the footer; the right panel is not, and the
25
+ // search + label + sort + actions row does not fit in one line there
26
+ flex-wrap wrap
27
+ // ...so give the search a line of its own and let it fill it, instead of
28
+ // sitting at its footer width with a gap beside it. `flex-basis: 100%` is what
29
+ // forces the wrap; the controls then share the line below.
30
+ .qm-search-input
31
+ flex 1 1 100%
32
+ max-width none
33
+ .qm-list-wrap
34
+ flex 1
35
+ min-height 0
36
+ max-height none
37
+ overflow-y auto
38
+
39
+ .qm-panel-actions
40
+ display flex
41
+ align-items center
42
+ gap 6px
43
+
44
+ .qm-move-icon
45
+ font-size 14px
46
+ padding 4px
47
+ cursor pointer
48
+ &:hover
49
+ color var(--primary)
50
+
51
+ // The footer Q stays lit while the panel is docked in the right panel, same
52
+ // contract as the info, AI and cmd history icons
53
+ .quick-command-trigger
54
+ &.active
55
+ color var(--success)
56
+
5
57
  .qm-wrap-tooltip
6
58
  background var(--main)
7
59
  position absolute
@@ -80,6 +132,12 @@
80
132
  width 100%
81
133
  .qm-label-select
82
134
  width 100%
135
+ // The docked panel's search claims a whole row with `flex-basis: 100%`, which
136
+ // is a width only while `.qm-flex` is a row. Here it is a column, so the same
137
+ // basis would be 100% of the *height* and would push the controls off the
138
+ // panel. Same specificity as the rule above it, declared later, so it wins.
139
+ .qm-panel-in-right-panel .qm-search-input
140
+ flex none
83
141
 
84
142
  // On mobile, stack the quick-command row (name / delay / command / remove)
85
143
  // vertically instead of side by side — the horizontal Space.Compact layout
@@ -1,17 +1,29 @@
1
1
  /**
2
- * quick commands footer selection wrap
2
+ * quick commands panel
3
+ *
4
+ * The panel has two homes: the floating footer box (default) and the right side
5
+ * panel. Which one it is in is a stored preference
6
+ * (store.quickCommandsInRightPanel), so the same component renders both —
7
+ * `inline` drops the floating box and lets the right panel's own container wrap
8
+ * the panel body.
3
9
  */
4
10
 
5
11
  import { useState } from 'react'
6
- import { quickCommandLabelsLsKey, pinnedQuickCommandBarKey } from '../../common/constants'
7
- import { sortBy } from 'lodash-es'
12
+ import { auto } from 'manate/react'
13
+ import {
14
+ quickCommandLabelsLsKey,
15
+ pinnedQuickCommandBarKey
16
+ } from '../../common/constants'
17
+ import { sortBy, pick } from 'lodash-es'
8
18
  import { Button, Input, Select, Space, Flex } from 'antd'
9
19
  import * as ls from '../../common/safe-local-storage'
10
20
  import CmdItem from './quick-command-item'
11
21
  import {
12
22
  EditOutlined,
13
23
  CloseCircleOutlined,
14
- PushpinOutlined
24
+ PushpinOutlined,
25
+ VerticalLeftOutlined,
26
+ VerticalAlignBottomOutlined
15
27
  } from '@ant-design/icons'
16
28
  import classNames from 'classnames'
17
29
  import onDropFunc from './on-drop'
@@ -22,9 +34,42 @@ const e = window.translate
22
34
  const addQuickCommands = 'addQuickCommands'
23
35
  const { Option } = Select
24
36
 
25
- export default function QuickCommandsFooterBox (props) {
37
+ export default auto(function QuickCommandsFooterBox (props) {
38
+ const { store, inline } = props
26
39
  const [keyword, setKeyword] = useState('')
27
40
  const [label, setLabel] = useState(ls.getItem(quickCommandLabelsLsKey, ''))
41
+ // read through the store so the panel re-renders on its own: it is rendered
42
+ // in two different places (footer box and right panel), and only one of them
43
+ // gets props from the layout
44
+ const {
45
+ openQuickCommandBar,
46
+ pinnedQuickCommandBar,
47
+ qmSortByFrequency,
48
+ quickCommandsInRightPanel,
49
+ inActiveTerminal,
50
+ leftSidePanelWidth,
51
+ leftSideBarWidth,
52
+ openedSideBar,
53
+ rightPanelVisible,
54
+ rightPanelPinned,
55
+ rightPanelWidth,
56
+ isMobile
57
+ } = pick(store, [
58
+ 'openQuickCommandBar',
59
+ 'pinnedQuickCommandBar',
60
+ 'qmSortByFrequency',
61
+ 'quickCommandsInRightPanel',
62
+ 'inActiveTerminal',
63
+ 'leftSidePanelWidth',
64
+ 'leftSideBarWidth',
65
+ 'openedSideBar',
66
+ 'rightPanelVisible',
67
+ 'rightPanelPinned',
68
+ 'rightPanelWidth',
69
+ 'isMobile'
70
+ ])
71
+ const all = store.currentQuickCommands
72
+ const quickCommandTags = store.quickCommandTags
28
73
 
29
74
  function handleTogglePinned () {
30
75
  const current = !window.store.pinnedQuickCommandBar
@@ -58,12 +103,12 @@ export default function QuickCommandsFooterBox (props) {
58
103
  setLabel(v)
59
104
  }
60
105
 
61
- // function filterFunc (v, opt) {
62
- // const c = opt.props.children.toLowerCase()
63
- // const m = opt.props.cmd.toLowerCase()
64
- // const vv = v.toLowerCase()
65
- // return c.includes(vv) || m.includes(vv)
66
- // }
106
+ // Dock the panel into the right side panel. The footer box unmounts on the
107
+ // way out (it is gated on quickCommandsInRightPanel), so there is nothing to
108
+ // clean up here — unlike the cmd history popover, which has to be closed.
109
+ function handleMoveToRightPanel () {
110
+ window.store.moveQuickCommandsToRightPanel()
111
+ }
67
112
 
68
113
  function onDragOver (e) {
69
114
  e.preventDefault()
@@ -114,9 +159,6 @@ export default function QuickCommandsFooterBox (props) {
114
159
  }
115
160
 
116
161
  function renderItem (item) {
117
- const {
118
- qmSortByFrequency
119
- } = props
120
162
  return (
121
163
  <CmdItem
122
164
  item={item}
@@ -156,26 +198,69 @@ export default function QuickCommandsFooterBox (props) {
156
198
  })
157
199
  }
158
200
 
159
- const {
160
- openQuickCommandBar,
161
- pinnedQuickCommandBar,
162
- qmSortByFrequency,
163
- inActiveTerminal,
164
- leftSidePanelWidth,
165
- leftSideBarWidth,
166
- openedSideBar,
167
- rightPanelVisible,
168
- rightPanelPinned,
169
- rightPanelWidth,
170
- isMobile
171
- } = props
172
- if ((!openQuickCommandBar && !pinnedQuickCommandBar) || !inActiveTerminal) {
173
- return null
201
+ // The two "move" icons are mirror images and sit in the same slot: the footer
202
+ // box offers to dock the panel into the right side panel, the docked panel
203
+ // offers to hand it back to the footer. Same glyph pair as the cmd history
204
+ // panel (cmd-history.jsx), so the two panels read the same way.
205
+ // Note VerticalLeftOutlined is the one whose bar is on the right (arrow
206
+ // pointing right); VerticalAlignBottomOutlined points at the footer.
207
+ function renderMoveIcon () {
208
+ if (inline) {
209
+ return (
210
+ <VerticalAlignBottomOutlined
211
+ className='qm-move-icon pointer'
212
+ title={e('moveToFooter')}
213
+ onClick={() => window.store.moveQuickCommandsToFooter()}
214
+ />
215
+ )
216
+ }
217
+ return (
218
+ <VerticalLeftOutlined
219
+ className='qm-move-icon pointer'
220
+ title={e('moveToRightPanel')}
221
+ onClick={handleMoveToRightPanel}
222
+ />
223
+ )
174
224
  }
175
- const all = props.currentQuickCommands
176
- // if (!all.length) {
177
- // return renderNoCmd()
178
- // }
225
+
226
+ // Docked, the right panel owns the pin and the close button (both live in its
227
+ // title bar), so the panel keeps only the actions that are about the quick
228
+ // commands themselves. A pin left over from the footer is kept in the store
229
+ // and comes back with the panel, so nothing is silently lost.
230
+ function renderActions () {
231
+ const tp = pinnedQuickCommandBar
232
+ ? 'primary'
233
+ : 'text'
234
+ return (
235
+ <div className='qm-panel-actions mg2l'>
236
+ {renderMoveIcon()}
237
+ <Space.Compact>
238
+ {
239
+ !inline && (
240
+ <Button
241
+ onClick={handleTogglePinned}
242
+ icon={<PushpinOutlined />}
243
+ type={tp}
244
+ />
245
+ )
246
+ }
247
+ <Button
248
+ onClick={window.store.handleOpenQuickCommandsSetting}
249
+ icon={<EditOutlined />}
250
+ />
251
+ {
252
+ !inline && (
253
+ <Button
254
+ onClick={handleClose}
255
+ icon={<CloseCircleOutlined />}
256
+ />
257
+ )
258
+ }
259
+ </Space.Compact>
260
+ </div>
261
+ )
262
+ }
263
+
179
264
  const keyword0 = keyword.toLowerCase()
180
265
  const filtered = filterArray(all, keyword0, label)
181
266
  const sorted = qmSortByFrequency
@@ -188,9 +273,6 @@ export default function QuickCommandsFooterBox (props) {
188
273
  className: 'qm-label-select',
189
274
  allowClear: true
190
275
  }
191
- const tp = pinnedQuickCommandBar
192
- ? 'primary'
193
- : 'text'
194
276
  const cls = classNames('qm-list-wrap')
195
277
  const type = qmSortByFrequency ? 'primary' : 'default'
196
278
  // Mirrors the footer's left offset. Mobile reserves nothing for the side
@@ -214,56 +296,71 @@ export default function QuickCommandsFooterBox (props) {
214
296
  className: 'qm-wrap-tooltip',
215
297
  style: qmStyle
216
298
  }
299
+
300
+ const content = (
301
+ <div className='pd2'>
302
+ <Flex justify='space-between' className='qm-flex'>
303
+ <Input.Search
304
+ value={keyword}
305
+ onChange={handleChange}
306
+ placeholder=''
307
+ className='qm-search-input'
308
+ />
309
+ <Flex gap='small'>
310
+ <Select
311
+ {...sprops}
312
+ >
313
+ {quickCommandTags.map(
314
+ renderTag
315
+ )}
316
+ </Select>
317
+ <Button
318
+ type={type}
319
+ onClick={window.store.handleSortByFrequency}
320
+ >
321
+ {e('sortByFrequency')}
322
+ </Button>
323
+ </Flex>
324
+ {renderActions()}
325
+ </Flex>
326
+ <div className={cls}>
327
+ {sorted.map(renderItem)}
328
+ {
329
+ !sorted.length && renderNoCmd()
330
+ }
331
+ </div>
332
+ </div>
333
+ )
334
+
335
+ // docked in the right side panel: no floating box, no pin/close of its own —
336
+ // the right panel decides when this is on screen, via store.rightPanelTab
337
+ if (inline) {
338
+ if (store.rightPanelTab !== 'quickCommands') {
339
+ return null
340
+ }
341
+ return (
342
+ <div className='qm-panel-in-right-panel'>
343
+ {content}
344
+ </div>
345
+ )
346
+ }
347
+
348
+ // Dormant while the panel is docked: the box would otherwise float over the
349
+ // terminal with nothing to show for it, and a pin left on would keep
350
+ // reserving the strip of height it occupies (see layout.jsx).
351
+ if (
352
+ quickCommandsInRightPanel ||
353
+ (!openQuickCommandBar && !pinnedQuickCommandBar) ||
354
+ !inActiveTerminal
355
+ ) {
356
+ return null
357
+ }
358
+
217
359
  return (
218
360
  <div
219
361
  {...qmProps}
220
362
  >
221
- <div className='pd2'>
222
- <Flex justify='space-between' className='qm-flex'>
223
- <Input.Search
224
- value={keyword}
225
- onChange={handleChange}
226
- placeholder=''
227
- className='qm-search-input'
228
- />
229
- <Flex gap='small'>
230
- <Select
231
- {...sprops}
232
- >
233
- {props.quickCommandTags.map(
234
- renderTag
235
- )}
236
- </Select>
237
- <Button
238
- type={type}
239
- onClick={window.store.handleSortByFrequency}
240
- >
241
- {e('sortByFrequency')}
242
- </Button>
243
- </Flex>
244
- <Space.Compact className='mg2l'>
245
- <Button
246
- onClick={handleTogglePinned}
247
- icon={<PushpinOutlined />}
248
- type={tp}
249
- />
250
- <Button
251
- onClick={window.store.handleOpenQuickCommandsSetting}
252
- icon={<EditOutlined />}
253
- />
254
- <Button
255
- onClick={handleClose}
256
- icon={<CloseCircleOutlined />}
257
- />
258
- </Space.Compact>
259
- </Flex>
260
- <div className={cls}>
261
- {sorted.map(renderItem)}
262
- {
263
- !sorted.length && renderNoCmd()
264
- }
265
- </div>
266
- </div>
363
+ {content}
267
364
  </div>
268
365
  )
269
- }
366
+ })
@@ -5,6 +5,11 @@
5
5
  * on touch (mobile) and mouse (desktop) — the old hover-to-open model was
6
6
  * unreliable on Android, where a tap synthesizes mouseleave and cancels the
7
7
  * open timer.
8
+ *
9
+ * The panel has two homes (see quick-commands-box.jsx): the footer box and the
10
+ * right side panel. While it is docked, this button is repurposed to open and
11
+ * close that panel, exactly like the info and AI icons do — and `active` (fed
12
+ * by the footer, which is already reactive) keeps it lit while it is showing.
8
13
  */
9
14
 
10
15
  import { PureComponent } from 'react'
@@ -14,15 +19,21 @@ import './qm.styl'
14
19
  export default class QuickCommandsFooter extends PureComponent {
15
20
  handleClick = () => {
16
21
  const { store } = window
22
+ if (store.quickCommandsInRightPanel) {
23
+ store.toggleQuickCommandsPanel()
24
+ return
25
+ }
17
26
  store.openQuickCommandBar = !store.openQuickCommandBar
18
27
  }
19
28
 
20
29
  render () {
30
+ const { active } = this.props
21
31
  return (
22
32
  <div className='fleft relative quick-command-trigger-wrap'>
23
33
  <Button
24
34
  size='small'
25
35
  type='text'
36
+ className={'quick-command-trigger' + (active ? ' active' : '')}
26
37
  onClick={this.handleClick}
27
38
  >
28
39
  Q
@@ -8,6 +8,7 @@ import {
8
8
  PaperClipOutlined,
9
9
  ApartmentOutlined,
10
10
  MoreOutlined,
11
+ MenuOutlined,
11
12
  ColumnWidthOutlined,
12
13
  LogoutOutlined,
13
14
  PlusOutlined,
@@ -39,6 +40,7 @@ export default function SessionControl (props) {
39
40
  keepaliveEnabled,
40
41
  broadcastInput,
41
42
  wrapDisabled,
43
+ pasteWhenContextMenu,
42
44
  onChangePane,
43
45
  toggleCheckSftpPathFollowSsh,
44
46
  onSshSftpSplitView,
@@ -47,6 +49,7 @@ export default function SessionControl (props) {
47
49
  toggleWrap,
48
50
  onFullscreen,
49
51
  onOpenSearch,
52
+ onOpenContextMenu,
50
53
  onExitGracefully,
51
54
  onZoomFontSize
52
55
  } = props
@@ -279,7 +282,35 @@ export default function SessionControl (props) {
279
282
  )
280
283
  }
281
284
 
282
- function renderTermControls () {
285
+ /**
286
+ * Touch devices have no right click, so the terminal's context menu needs a
287
+ * visible way in: this icon, next to the fullscreen icon on the desktop
288
+ * toolbar and next to the three-dot toggle in the mobile one (see below).
289
+ * Hidden when there is no menu to open — `pasteWhenContextMenu` turns both
290
+ * right click and the long press into a paste (see terminal.jsx), and it
291
+ * only ever acts on the terminal pane.
292
+ */
293
+ function renderContextMenuIcon (inMobileBar) {
294
+ if (
295
+ !isTouchDevice ||
296
+ pasteWhenContextMenu ||
297
+ tab.pane !== paneMap.terminal
298
+ ) {
299
+ return null
300
+ }
301
+ const cls = classnames(
302
+ 'iblock pointer spliter context-menu-control-icon',
303
+ inMobileBar ? 'mobile-context-menu-toggle' : 'mg1r icon-info'
304
+ )
305
+ return (
306
+ <MenuOutlined
307
+ className={cls}
308
+ onClick={onOpenContextMenu}
309
+ />
310
+ )
311
+ }
312
+
313
+ function renderTermControls (skipContextMenuIcon) {
283
314
  const { pane } = tab
284
315
  if (pane !== paneMap.terminal) {
285
316
  return null
@@ -287,6 +318,7 @@ export default function SessionControl (props) {
287
318
  return (
288
319
  <div className='fright term-controls'>
289
320
  {renderFontSizeIcons()}
321
+ {skipContextMenuIcon ? null : renderContextMenuIcon()}
290
322
  {renderFullscreenIcon()}
291
323
  {renderSearchIcon()}
292
324
  </div>
@@ -303,12 +335,13 @@ export default function SessionControl (props) {
303
335
  {renderBroadcastIcon()}
304
336
  {renderWrapIcon()}
305
337
  {renderExitGracefullyIcon()}
306
- {renderTermControls()}
338
+ {renderTermControls(true)}
307
339
  </div>
308
340
  )
309
341
  return (
310
342
  <div className='terminal-control mobile-session-control'>
311
343
  {renderPaneControl()}
344
+ {renderContextMenuIcon(true)}
312
345
  <Popover
313
346
  content={extraIcons}
314
347
  trigger='click'
@@ -14,6 +14,7 @@
14
14
  flex 1
15
15
  min-width 0
16
16
  .mobile-control-toggle
17
+ .mobile-context-menu-toggle
17
18
  font-size 16px
18
19
  line-height 32px
19
20
  color var(--text)
@@ -21,6 +22,10 @@
21
22
  &:hover
22
23
  color var(--text-light)
23
24
 
25
+ // touch-only terminal context menu icon, in the bar next to the toggle above
26
+ .mobile-context-menu-toggle
27
+ margin-right 10px
28
+
24
29
  // Popover content — icons laid out in a row, wrapping as needed
25
30
  .mobile-control-icons
26
31
  display flex
@@ -480,6 +480,17 @@ export default class SessionWrapper extends Component {
480
480
  refs.get('term-' + this.props.tab.id)?.toggleSearch()
481
481
  }
482
482
 
483
+ // touch-only: the toolbar menu icon opens the same context menu a right
484
+ // click (or a long press) does, anchored just below the icon itself
485
+ handleOpenContextMenu = (evt) => {
486
+ const term = refs.get('term-' + this.props.tab.id)
487
+ if (!term) {
488
+ return
489
+ }
490
+ const rect = evt.currentTarget.getBoundingClientRect()
491
+ term.openContextMenuAtPoint(rect.left, rect.bottom)
492
+ }
493
+
483
494
  handleZoomFontSize = (v) => {
484
495
  const term = refs.get('term-' + this.props.tab.id)
485
496
  if (!term?.term) {
@@ -625,6 +636,7 @@ export default class SessionWrapper extends Component {
625
636
  keepaliveEnabled={this.state.keepaliveEnabled}
626
637
  broadcastInput={this.state.broadcastInput}
627
638
  wrapDisabled={this.state.wrapDisabled}
639
+ pasteWhenContextMenu={!!this.props.config.pasteWhenContextMenu}
628
640
  onChangePane={(pane) => this.onChangePane(pane)}
629
641
  toggleCheckSftpPathFollowSsh={this.toggleCheckSftpPathFollowSsh}
630
642
  onSshSftpSplitView={this.handleSshSftpSplitView}
@@ -634,6 +646,7 @@ export default class SessionWrapper extends Component {
634
646
  onFullscreen={this.handleFullscreen}
635
647
  onZoomFontSize={this.handleZoomFontSize}
636
648
  onOpenSearch={this.handleOpenSearch}
649
+ onOpenContextMenu={this.handleOpenContextMenu}
637
650
  onExitGracefully={this.handleExitGracefully}
638
651
  />
639
652
  <Suspense fallback={null}>
@@ -574,7 +574,9 @@ export default class SettingCommon extends Component {
574
574
  'allowMultiInstance',
575
575
  'disableDeveloperTool',
576
576
  'switchTabOnHover',
577
+ 'doubleClickToOpenBookmark',
577
578
  'disableTabIndex',
579
+ 'onlyShowTitleInTab',
578
580
  'disableShortcutBar',
579
581
  'debug'
580
582
  ]
@@ -1,11 +1,13 @@
1
1
  import SettingCol from './col'
2
2
  import WidgetControl from '../widgets/widget-control'
3
+ import WidgetInstanceDetail from '../widgets/widget-instance-detail'
3
4
  import WidgetList from '../widgets/widgets-list'
5
+ import { auto } from 'manate/react'
4
6
  import {
5
7
  settingMap
6
8
  } from '../../common/constants'
7
9
 
8
- export default function TabWidgets (props) {
10
+ export default auto(function TabWidgets (props) {
9
11
  const {
10
12
  settingTab
11
13
  } = props
@@ -15,8 +17,14 @@ export default function TabWidgets (props) {
15
17
  const {
16
18
  settingItem,
17
19
  listProps,
18
- formProps
20
+ formProps,
21
+ store
19
22
  } = props
23
+ // The right column shows whatever the list selected: a widget definition (run
24
+ // form) or a running instance (its detail + log). A definition has `info`, an
25
+ // instance has `widgetId` — and an instance that has been stopped stays the
26
+ // selected item on purpose, so its log is still readable after it dies.
27
+ const isInstance = !!(settingItem && settingItem.widgetId)
20
28
  return (
21
29
  <div
22
30
  className='setting-tabs-profile'
@@ -25,11 +33,23 @@ export default function TabWidgets (props) {
25
33
  <WidgetList
26
34
  {...listProps}
27
35
  />
28
- <WidgetControl
29
- {...formProps}
30
- key={settingItem.id}
31
- />
36
+ {
37
+ isInstance
38
+ ? (
39
+ <WidgetInstanceDetail
40
+ key={settingItem.id}
41
+ instance={settingItem}
42
+ store={store}
43
+ />
44
+ )
45
+ : (
46
+ <WidgetControl
47
+ {...formProps}
48
+ key={settingItem.id}
49
+ />
50
+ )
51
+ }
32
52
  </SettingCol>
33
53
  </div>
34
54
  )
35
- }
55
+ })
@@ -902,12 +902,6 @@ export default class FileSection extends React.Component {
902
902
  this.transfer()
903
903
  }
904
904
 
905
- zipAndTransfer = async () => {
906
- this.transfer(transfer => {
907
- transfer.zip = true
908
- })
909
- }
910
-
911
905
  newFile = () => {
912
906
  return this.newItem(false)
913
907
  }
@@ -1098,13 +1092,6 @@ export default class FileSection extends React.Component {
1098
1092
  icon: iconType,
1099
1093
  text: transferText
1100
1094
  })
1101
- // if (isDirectory && !this.props.isFtp) {
1102
- // res.push({
1103
- // func: 'zipAndTransfer',
1104
- // icon: 'FileZipOutlined',
1105
- // text: e('compressAndTransfer')
1106
- // })
1107
- // }
1108
1095
  }
1109
1096
  if (!isDirectory && isRealFile && isLocal) {
1110
1097
  res.push({
@@ -4,7 +4,9 @@ import './right-side-panel.styl'
4
4
  import {
5
5
  CloseCircleOutlined,
6
6
  PushpinOutlined,
7
- BarChartOutlined
7
+ BarChartOutlined,
8
+ HistoryOutlined,
9
+ ThunderboltOutlined
8
10
  } from '@ant-design/icons'
9
11
  import {
10
12
  Typography,
@@ -57,9 +59,14 @@ export default memo(function RightSidePanel (
57
59
  // from a desktop session must not turn the panel into a dock here — that
58
60
  // would make it full height over the footer for no reachable reason.
59
61
  const pinned = rightPanelPinned && !isMobile
60
- const tag = rightPanelTab === 'ai'
61
- ? <Tag className='mg1r'>AI</Tag>
62
- : <BarChartOutlined className='mg1r' />
62
+ // one glyph per tab, so the panel says what it is showing; the info tab is
63
+ // the fallback, since a stale or unknown tab falls back to it everywhere else
64
+ const tagMap = {
65
+ ai: <Tag className='mg1r'>AI</Tag>,
66
+ cmdHistory: <HistoryOutlined className='mg1r' />,
67
+ quickCommands: <ThunderboltOutlined className='mg1r' />
68
+ }
69
+ const tag = tagMap[rightPanelTab] || <BarChartOutlined className='mg1r' />
63
70
 
64
71
  function onDragEnd (nw) {
65
72
  window.store.setRightSidePanelWidth(nw)