@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
@@ -1,9 +1,14 @@
1
1
  /**
2
- * cmd history trigger button with popover
2
+ * cmd history panel with popover
3
+ *
4
+ * The panel has two homes: the footer popover (default) and the right side
5
+ * panel. Which one it is in is a stored preference (store.cmdHistoryInRightPanel),
6
+ * so the same component renders both — `inline` drops the trigger/popover and
7
+ * lets the right panel's own container wrap the panel body.
3
8
  */
4
9
 
5
- import { useState, useEffect } from 'react'
6
- import { Button, Empty, Popover, Dropdown } from 'antd'
10
+ import { useState, useEffect, useLayoutEffect, useRef } from 'react'
11
+ import { Button, Empty, Popover } from 'antd'
7
12
  import { auto } from 'manate/react'
8
13
  import SwitchLabel from '../common/switch'
9
14
  import { copy } from '../../common/clipboard'
@@ -14,12 +19,15 @@ import {
14
19
  UnorderedListOutlined,
15
20
  MoreOutlined,
16
21
  PlusOutlined,
17
- CodeOutlined
22
+ CodeOutlined,
23
+ VerticalLeftOutlined,
24
+ VerticalAlignBottomOutlined
18
25
  } from '@ant-design/icons'
19
26
  import InputAutoFocus from '../common/input-auto-focus'
20
27
  import { getItemJSON, setItemJSON } from '../../common/safe-local-storage'
21
28
  import QuickCommandCreateModal from '../quick-commands/quick-command-create-modal'
22
29
  import MultiTabRunModal from './multi-tab-run-modal'
30
+ import { refsStatic } from '../common/ref'
23
31
  import classNames from 'classnames'
24
32
  import './cmd-history.styl'
25
33
 
@@ -27,21 +35,71 @@ const e = window.translate
27
35
  const SORT_BY_FREQ_KEY = 'electerm-cmd-history-sort-by-frequency'
28
36
 
29
37
  export default auto(function CmdHistory (props) {
38
+ const { store, inline } = props
30
39
  const [keyword, setKeyword] = useState('')
31
40
  const [sortByFrequency, setSortByFrequency] = useState(() => {
32
41
  return getItemJSON(SORT_BY_FREQ_KEY, false)
33
42
  })
34
- // cmd of the item whose action menu is open / being acted on
35
- const [menuOpenCmd, setMenuOpenCmd] = useState('')
43
+ // The item action menu is a part of this panel, not of the row it acts on:
44
+ // the row's ⋯ icon only reports which command was picked and where that row
45
+ // sits ({ cmd, top, rowTop }), and the panel renders the single menu for it.
46
+ // A menu per row cannot be closed with the panel — the popover content stops
47
+ // being re-rendered the moment the popover closes, so a per-row menu portaled
48
+ // to the body is left floating on its own.
49
+ const [menu, setMenu] = useState(null)
50
+ const panelRef = useRef(null)
51
+ const menuRef = useRef(null)
36
52
  const [quickCommandCmd, setQuickCommandCmd] = useState('')
37
53
  const [multiTabCmd, setMultiTabCmd] = useState('')
38
- const { terminalCommandHistory } = props.store
54
+ // Footer mode only. The popover is controlled so that the trigger can be
55
+ // repurposed (open the right panel instead, when the history lives there) and
56
+ // so that "move back to footer" can open it again from the store.
57
+ const [open, setOpen] = useState(false)
58
+ const { terminalCommandHistory } = store
39
59
 
40
60
  useEffect(() => {
41
61
  setItemJSON(SORT_BY_FREQ_KEY, sortByFrequency)
42
62
  }, [sortByFrequency])
43
63
 
64
+ // "move back to footer" happens in the right panel, which cannot reach this
65
+ // component through props — it goes through the store, which needs a handle
66
+ useEffect(() => {
67
+ if (inline) {
68
+ return
69
+ }
70
+ refsStatic.add('CmdHistory', {
71
+ openPopover: () => setOpen(true)
72
+ })
73
+ return () => refsStatic.remove('CmdHistory')
74
+ }, [inline])
75
+
76
+ // The menu hangs below its row, which for the last rows would put it past the
77
+ // bottom of the window — the panel itself is not clipped, so lift the menu
78
+ // above its row instead. The layout effect runs before paint, so the
79
+ // correction is never visible.
80
+ useLayoutEffect(() => {
81
+ const el = menuRef.current
82
+ if (!el || !menu) {
83
+ return
84
+ }
85
+ const rect = el.getBoundingClientRect()
86
+ if (rect.bottom > window.innerHeight - 4) {
87
+ const above = menu.rowTop - rect.height
88
+ if (above !== menu.top) {
89
+ setMenu({
90
+ ...menu,
91
+ top: above
92
+ })
93
+ }
94
+ }
95
+ }, [menu])
96
+
97
+ function closeMenu () {
98
+ setMenu(null)
99
+ }
100
+
44
101
  function handleRunCommand (cmd) {
102
+ closeMenu()
45
103
  window.store.runCmdFromHistory(cmd)
46
104
  }
47
105
 
@@ -55,6 +113,7 @@ export default auto(function CmdHistory (props) {
55
113
  }
56
114
 
57
115
  function handleMenuAction (key, cmd) {
116
+ closeMenu()
58
117
  if (key === 'delete') {
59
118
  window.store.deleteCmdHistory(cmd)
60
119
  } else if (key === 'quickCommand') {
@@ -65,33 +124,49 @@ export default auto(function CmdHistory (props) {
65
124
  }
66
125
  }
67
126
 
68
- function getMenuProps (cmd) {
69
- return {
70
- items: [
71
- {
72
- key: 'delete',
73
- icon: <DeleteOutlined />,
74
- label: e('del'),
75
- danger: true
76
- },
77
- {
78
- key: 'quickCommand',
79
- icon: <PlusOutlined />,
80
- label: e('addQuickCommands')
81
- },
82
- {
83
- key: 'multiTab',
84
- icon: <CodeOutlined />,
85
- label: e('runInAllTerminals')
86
- }
87
- ],
88
- onClick: ({ key, domEvent }) => {
89
- // the menu renders in a portal but React events still bubble through
90
- // the row, which would run the command
91
- domEvent.stopPropagation()
92
- handleMenuAction(key, cmd)
127
+ function getMenuItems () {
128
+ return [
129
+ {
130
+ key: 'delete',
131
+ icon: <DeleteOutlined />,
132
+ label: e('del'),
133
+ danger: true
134
+ },
135
+ {
136
+ key: 'quickCommand',
137
+ icon: <PlusOutlined />,
138
+ label: e('addQuickCommands')
139
+ },
140
+ {
141
+ key: 'multiTab',
142
+ icon: <CodeOutlined />,
143
+ label: e('runInAllTerminals')
93
144
  }
145
+ ]
146
+ }
147
+
148
+ // the ⋯ icon only reports the picked command and its position; the panel owns
149
+ // the menu itself
150
+ function handleToggleMenu (ev, cmd) {
151
+ // the row would otherwise run the command
152
+ ev.stopPropagation()
153
+ if (menu && menu.cmd === cmd) {
154
+ closeMenu()
155
+ return
156
+ }
157
+ const row = ev.currentTarget.closest('.cmd-history-item')
158
+ const panel = panelRef.current
159
+ if (!row || !panel) {
160
+ return
94
161
  }
162
+ const rowRect = row.getBoundingClientRect()
163
+ const panelTop = panel.getBoundingClientRect().top
164
+ setMenu({
165
+ cmd,
166
+ // below the row by default, above it when there is no room left
167
+ top: rowRect.bottom - panelTop,
168
+ rowTop: rowRect.top - panelTop
169
+ })
95
170
  }
96
171
 
97
172
  function filterArray (array, keyword) {
@@ -105,6 +180,45 @@ export default auto(function CmdHistory (props) {
105
180
  setKeyword(e.target.value)
106
181
  }
107
182
 
183
+ // The menu is anchored to its row, and rc-trigger re-aligns it on every
184
+ // scroll of the trigger's scrollable ancestors. The rows live in a 190px-tall
185
+ // scrolling list, so a wheel over the list drags an open menu along with the
186
+ // row — out of the popup and eventually off the screen, while the row it
187
+ // belongs to is long gone. Close it instead: the menu is only meaningful next
188
+ // to the row it was opened on.
189
+ function handleListScroll () {
190
+ if (menu) {
191
+ closeMenu()
192
+ }
193
+ }
194
+
195
+ // closing the popover hides the menu with it; dropping the selection too
196
+ // keeps it from coming back on the next open
197
+ function handlePopoverOpenChange (nextOpen) {
198
+ if (!nextOpen) {
199
+ closeMenu()
200
+ setOpen(false)
201
+ return
202
+ }
203
+ // While the history lives in the right panel this trigger never opens the
204
+ // popover: it opens and closes the right panel, exactly like the info and
205
+ // AI triggers do (store.toggleCmdHistoryPanel).
206
+ if (store.cmdHistoryInRightPanel) {
207
+ window.store.toggleCmdHistoryPanel()
208
+ return
209
+ }
210
+ setOpen(true)
211
+ }
212
+
213
+ // Dock the panel into the right side panel. The popover has to be closed on
214
+ // the way out, or it would be left behind pointing at a panel that is no
215
+ // longer in the footer.
216
+ function handleMoveToRightPanel () {
217
+ closeMenu()
218
+ setOpen(false)
219
+ window.store.moveCmdHistoryToRightPanel()
220
+ }
221
+
108
222
  const historyArray = (terminalCommandHistory || []).slice().reverse()
109
223
 
110
224
  let filtered = filterArray(historyArray, keyword)
@@ -129,7 +243,7 @@ export default auto(function CmdHistory (props) {
129
243
  return filtered.map((item, index) => {
130
244
  const cls = classNames(
131
245
  'cmd-history-item',
132
- { 'menu-open': menuOpenCmd === item.cmd }
246
+ { 'menu-open': menu && menu.cmd === item.cmd }
133
247
  )
134
248
  return (
135
249
  <div
@@ -149,25 +263,80 @@ export default auto(function CmdHistory (props) {
149
263
  className='cmd-history-item-copy'
150
264
  onClick={(ev) => handleCopyCommand(item.cmd, ev)}
151
265
  />
152
- <Dropdown
153
- menu={getMenuProps(item.cmd)}
154
- trigger={['click']}
155
- onOpenChange={(open) => setMenuOpenCmd(open ? item.cmd : '')}
156
- >
157
- <Button
158
- type='text'
159
- size='small'
160
- icon={<MoreOutlined />}
161
- className='cmd-history-item-more'
162
- onClick={(ev) => ev.stopPropagation()}
163
- />
164
- </Dropdown>
266
+ <Button
267
+ type='text'
268
+ size='small'
269
+ icon={<MoreOutlined />}
270
+ className={classNames(
271
+ 'cmd-history-item-more',
272
+ { active: menu && menu.cmd === item.cmd }
273
+ )}
274
+ onClick={(ev) => handleToggleMenu(ev, item.cmd)}
275
+ />
165
276
  </div>
166
277
  </div>
167
278
  )
168
279
  })
169
280
  }
170
281
 
282
+ // the panel's own action menu: it renders inside the popover content, so it
283
+ // is hidden and revealed with the panel instead of outliving it
284
+ function renderItemMenu () {
285
+ if (!menu) {
286
+ return null
287
+ }
288
+ return (
289
+ <div
290
+ ref={menuRef}
291
+ className='cmd-history-item-menu'
292
+ style={{ top: `${menu.top}px` }}
293
+ onClick={ev => ev.stopPropagation()}
294
+ >
295
+ {
296
+ getMenuItems().map(item => (
297
+ <div
298
+ key={item.key}
299
+ className={classNames(
300
+ 'cmd-history-menu-item',
301
+ { danger: item.danger }
302
+ )}
303
+ onClick={() => handleMenuAction(item.key, menu.cmd)}
304
+ >
305
+ <span className='cmd-history-menu-icon'>{item.icon}</span>
306
+ <span className='cmd-history-menu-label'>{item.label}</span>
307
+ </div>
308
+ ))
309
+ }
310
+ </div>
311
+ )
312
+ }
313
+
314
+ // The two "move" icons are mirror images and sit in the same slot: the
315
+ // popover offers to dock the panel into the right side panel, the docked
316
+ // panel offers to hand it back to the footer popover. The glyphs are antd's
317
+ // "align" pair — an arrow into a bar on the right, an arrow into a bar at the
318
+ // bottom — so each points at the edge the panel is going to.
319
+ // Note VerticalLeftOutlined is the one whose bar is on the right (arrow
320
+ // pointing right); VerticalRightOutlined is its mirror and points left.
321
+ function renderMoveIcon () {
322
+ if (inline) {
323
+ return (
324
+ <VerticalAlignBottomOutlined
325
+ className='cmd-history-move-icon pointer'
326
+ title={e('moveToFooter')}
327
+ onClick={() => window.store.moveCmdHistoryToFooter()}
328
+ />
329
+ )
330
+ }
331
+ return (
332
+ <VerticalLeftOutlined
333
+ className='cmd-history-move-icon pointer'
334
+ title={e('moveToRightPanel')}
335
+ onClick={handleMoveToRightPanel}
336
+ />
337
+ )
338
+ }
339
+
171
340
  function renderHeader () {
172
341
  if (!historyArray.length) {
173
342
  return null
@@ -182,17 +351,29 @@ export default auto(function CmdHistory (props) {
182
351
  label={e('sortByFrequency')}
183
352
  />
184
353
  </div>
185
- <UnorderedListOutlined
186
- className='cmd-history-clear-icon pointer clear-ai-icon icon-hover'
187
- title={e('clear')}
188
- onClick={handleClearAll}
189
- />
354
+ <div className='cmd-history-header-actions'>
355
+ {renderMoveIcon()}
356
+ <UnorderedListOutlined
357
+ className='cmd-history-clear-icon pointer clear-ai-icon icon-hover'
358
+ title={e('clear')}
359
+ onClick={handleClearAll}
360
+ />
361
+ </div>
190
362
  </div>
191
363
  )
192
364
  }
193
365
 
194
366
  const content = (
195
- <div className='cmd-history-popover-content pd2'>
367
+ <div
368
+ ref={panelRef}
369
+ className={classNames(
370
+ 'cmd-history-popover-content',
371
+ inline
372
+ // docked: fill the right panel instead of sizing itself like a popup
373
+ ? 'cmd-history-in-panel'
374
+ : 'pd2'
375
+ )}
376
+ >
196
377
  <div className='cmd-history-search pd2b'>
197
378
  <InputAutoFocus
198
379
  value={keyword}
@@ -203,25 +384,18 @@ export default auto(function CmdHistory (props) {
203
384
  />
204
385
  </div>
205
386
  {renderHeader()}
206
- <div className='cmd-history-list'>
387
+ <div className='cmd-history-list' onScroll={handleListScroll}>
207
388
  {renderList()}
208
389
  </div>
390
+ {renderItemMenu()}
209
391
  </div>
210
392
  )
211
393
 
212
- return (
394
+ // The modals are siblings of the panel, never children of the popover: the
395
+ // content of a closed popover is no longer re-rendered, so a modal opened
396
+ // from inside one could not be closed once the popover went away.
397
+ const modals = (
213
398
  <>
214
- <Popover
215
- content={content}
216
- trigger='click'
217
- placement='topLeft'
218
- >
219
- <Button
220
- size='small'
221
- type='text'
222
- icon={<HistoryOutlined />}
223
- />
224
- </Popover>
225
399
  {
226
400
  quickCommandCmd
227
401
  ? (
@@ -247,4 +421,44 @@ export default auto(function CmdHistory (props) {
247
421
  }
248
422
  </>
249
423
  )
424
+
425
+ // docked in the right side panel: no trigger, no popover — the right panel
426
+ // decides when this is on screen, via store.rightPanelTab
427
+ if (inline) {
428
+ if (store.rightPanelTab !== 'cmdHistory') {
429
+ return null
430
+ }
431
+ return (
432
+ <>
433
+ {content}
434
+ {modals}
435
+ </>
436
+ )
437
+ }
438
+
439
+ const rightPanelActive = store.rightPanelVisible &&
440
+ store.rightPanelTab === 'cmdHistory'
441
+
442
+ return (
443
+ <>
444
+ <Popover
445
+ content={content}
446
+ trigger='click'
447
+ placement='topLeft'
448
+ open={open}
449
+ onOpenChange={handlePopoverOpenChange}
450
+ >
451
+ <Button
452
+ size='small'
453
+ type='text'
454
+ className={classNames(
455
+ 'cmd-history-trigger',
456
+ { active: rightPanelActive }
457
+ )}
458
+ icon={<HistoryOutlined />}
459
+ />
460
+ </Popover>
461
+ {modals}
462
+ </>
463
+ )
250
464
  })
@@ -1,9 +1,20 @@
1
1
  .cmd-history-popover-content
2
+ position relative
2
3
  max-height 300px
3
4
  width 300px
4
- overflow hidden
5
+ // deliberately not clipped: the item action menu is a child of the panel, but
6
+ // it hangs below its row and may reach past the panel's edge
5
7
  display flex
6
8
  flex-direction column
9
+ // Docked in the right side panel the panel *is* the container, so the history
10
+ // fills it instead of sizing itself like a popup. The parent
11
+ // (.right-side-panel-content) is an absolutely positioned box with top and
12
+ // bottom set, so 100% resolves to a real height and the list scrolls inside
13
+ // it rather than scrolling the panel.
14
+ &.cmd-history-in-panel
15
+ width 100%
16
+ max-height none
17
+ height 100%
7
18
  .cmd-history-search
8
19
  flex-shrink 0
9
20
  position sticky
@@ -16,6 +27,19 @@
16
27
  justify-content space-between
17
28
  padding-bottom 8px
18
29
 
30
+ .cmd-history-header-actions
31
+ display flex
32
+ align-items center
33
+ gap 6px
34
+
35
+ // The footer trigger stays lit while the history is docked in the right panel,
36
+ // same contract as the info and AI icons
37
+ .cmd-history-trigger
38
+ &.active
39
+ color var(--success)
40
+ .anticon
41
+ color var(--success)
42
+
19
43
  .cmd-history-sort
20
44
  display flex
21
45
  align-items center
@@ -24,6 +48,13 @@
24
48
  .cmd-history-sort-label
25
49
  user-select none
26
50
 
51
+ .cmd-history-move-icon
52
+ font-size 14px
53
+ padding 4px
54
+ cursor pointer
55
+ &:hover
56
+ color var(--primary)
57
+
27
58
  .cmd-history-clear-icon
28
59
  font-size 14px
29
60
  padding 4px
@@ -77,6 +108,8 @@
77
108
  .cmd-history-item-more
78
109
  cursor pointer
79
110
  padding 2px 4px
111
+ &.active
112
+ color var(--primary)
80
113
 
81
114
  .cmd-history-item-copy
82
115
  cursor pointer
@@ -84,6 +117,40 @@
84
117
  &:hover
85
118
  color blue
86
119
 
120
+ // The item action menu is part of this panel: it is rendered inside
121
+ // `.cmd-history-popover-content` (not in a body-level portal) so that it can
122
+ // never outlive the popover. It hangs below the row whose ⋯ icon was clicked —
123
+ // the component lifts it above that row when it would fall off the window.
124
+ .cmd-history-item-menu
125
+ position absolute
126
+ right 8px
127
+ z-index 10
128
+ min-width 156px
129
+ padding 4px 0
130
+ border-radius 4px
131
+ background var(--main)
132
+ border 1px solid var(--main-darker)
133
+ box-shadow 0 2px 8px rgba(0, 0, 0, 0.35)
134
+
135
+ .cmd-history-menu-item
136
+ display flex
137
+ align-items center
138
+ gap 8px
139
+ padding 5px 10px
140
+ cursor pointer
141
+ white-space nowrap
142
+ &:hover
143
+ background var(--main-lighter)
144
+ &.danger
145
+ color var(--error)
146
+
147
+ .cmd-history-menu-icon
148
+ font-size 13px
149
+ line-height 1
150
+
151
+ .cmd-history-menu-label
152
+ flex 1
153
+
87
154
  .multi-tab-run-cmd
88
155
  font-size 12px
89
156
  padding 6px 8px
@@ -72,9 +72,15 @@ export default auto(function FooterEntry (props) {
72
72
  }
73
73
 
74
74
  function renderQuickCommands () {
75
+ const { rightPanelVisible, rightPanelTab, quickCommandsInRightPanel } = props.store
76
+ // the Q button only owns the right panel while the panel lives there; in
77
+ // the footer it toggles the floating box instead, which shows its own state
78
+ const active = quickCommandsInRightPanel &&
79
+ rightPanelVisible &&
80
+ rightPanelTab === 'quickCommands'
75
81
  return (
76
82
  <div className='terminal-footer-unit terminal-footer-qm'>
77
- <Qm />
83
+ <Qm active={active} />
78
84
  </div>
79
85
  )
80
86
  }
@@ -37,6 +37,7 @@ export default auto(function Layout (props) {
37
37
  height,
38
38
  isMobile,
39
39
  pinnedQuickCommandBar,
40
+ quickCommandsInRightPanel,
40
41
  leftSidePanelWidth,
41
42
  leftSideBarWidth,
42
43
  pinned,
@@ -49,7 +50,14 @@ export default auto(function Layout (props) {
49
50
  shortcutBarKbOffset
50
51
  } = props.store
51
52
  const monitorHeight = isRemoteMonitorBarVisible(props.store) ? remoteMonitorBarHeight : 0
52
- const h = height - footerHeight - monitorHeight - (inActiveTerminal && pinnedQuickCommandBar ? quickCommandBoxHeight : 0) - (shortcutBarVisible ? shortcutBarHeight + shortcutBarKbOffset : 0) + resizeTrigger
53
+ // a pin left on while the panel is docked in the right side panel reserves
54
+ // nothing: the footer box is not rendered then (quick-commands-box.jsx)
55
+ const qmPinnedHeight = inActiveTerminal &&
56
+ pinnedQuickCommandBar &&
57
+ !quickCommandsInRightPanel
58
+ ? quickCommandBoxHeight
59
+ : 0
60
+ const h = height - footerHeight - monitorHeight - qmPinnedHeight - (shortcutBarVisible ? shortcutBarHeight + shortcutBarKbOffset : 0) + resizeTrigger
53
61
  const l = pinned && !isMobile ? leftSideBarWidth + leftSidePanelWidth : leftSideBarWidth
54
62
  const r = rightPanelVisible && rightPanelPinned && !isMobile ? rightPanelWidth : 0
55
63
  return {
@@ -141,24 +149,9 @@ export default auto(function Layout (props) {
141
149
  const footerProps = {
142
150
  store
143
151
  }
144
- const qmProps = pick(store, [
145
- 'quickCommandTags',
146
- 'qmSortByFrequency',
147
- 'openQuickCommandBar',
148
- 'pinnedQuickCommandBar',
149
- 'qmSortByFrequency',
150
- 'inActiveTerminal',
151
- 'leftSidePanelWidth',
152
- 'leftSideBarWidth',
153
- 'openedSideBar',
154
- 'currentQuickCommands',
155
- // the quick-command popup mirrors the footer's horizontal extent, so it
156
- // needs the same right-panel state the footer uses
157
- 'rightPanelVisible',
158
- 'rightPanelPinned',
159
- 'rightPanelWidth',
160
- 'isMobile'
161
- ])
152
+ const qmProps = {
153
+ store
154
+ }
162
155
  const sessionsProps = {
163
156
  styles: styles.wrapStyles,
164
157
  sizes,
@@ -29,6 +29,8 @@ import { isAIDisabled } from '../../common/ai-feature'
29
29
  import { ConfigProvider } from 'antd'
30
30
  import { NotificationContainer } from '../common/notification'
31
31
  import RightSidePanel from '../side-panel-r/side-panel-r'
32
+ import CmdHistory from '../footer/cmd-history'
33
+ import QuickCommandsFooterBox from '../quick-commands/quick-commands-box'
32
34
  import ConnectionHoppingWarning from './connection-hopping-warnning'
33
35
  import SshConfigLoadNotify from '../ssh-config/ssh-config-load-notify'
34
36
  import LoadSshConfigs from '../ssh-config/load-ssh-configs'
@@ -94,6 +96,7 @@ export default auto(function Index (props) {
94
96
  store.checkForDbUpgrade()
95
97
  store.handleGetSerials()
96
98
  store.checkPendingDeepLink()
99
+ store.checkPendingVvFile()
97
100
  }, [])
98
101
 
99
102
  // Track the actual input modality rather than a static capability probe:
@@ -128,6 +131,7 @@ export default auto(function Index (props) {
128
131
  pinned,
129
132
  isSecondInstance,
130
133
  pinnedQuickCommandBar,
134
+ quickCommandsInRightPanel,
131
135
  installSrc,
132
136
  fileTransfers,
133
137
  uiThemeConfig,
@@ -155,7 +159,9 @@ export default auto(function Index (props) {
155
159
  // would restyle the mobile layout.
156
160
  pinned: pinned && !store.isMobile,
157
161
  'not-win': !isWin,
158
- 'qm-pinned': pinnedQuickCommandBar,
162
+ // the pinned quick-command box only shapes the layout while it is actually
163
+ // in the footer — docked in the right panel it renders there instead
164
+ 'qm-pinned': pinnedQuickCommandBar && !quickCommandsInRightPanel,
159
165
  fullscreen,
160
166
  // terminal fullscreen keeps the footer visible (rdp/vnc/spice fullscreen
161
167
  // does not — the footer would be an empty bar there)
@@ -282,6 +288,8 @@ export default auto(function Index (props) {
282
288
  showAIConfig: store.showAIConfig,
283
289
  rightPanelTab,
284
290
  agentRunning: store.agentRunning,
291
+ aiContextInfo: store.aiContextInfo,
292
+ aiAutoCompress: store.aiAutoCompress,
285
293
  currentChatSessionId: store.currentChatSessionId,
286
294
  showChatSessions: store.showChatSessions
287
295
  }
@@ -358,6 +366,12 @@ export default auto(function Index (props) {
358
366
  <RightSidePanel {...rightPanelProps}>
359
367
  {!isAIDisabled() && <AIChat {...aiChatProps} />}
360
368
  <TerminalInfo key={store.activeTabId} store={store} {...deepCopy(store.terminalInfoProps)} />
369
+ {/* the cmd history panel has two homes; the footer popover is the
370
+ other one, and both are the same component (see cmd-history.jsx) */}
371
+ <CmdHistory store={store} inline />
372
+ {/* same for the quick command panel: the footer box is the other home
373
+ (see quick-commands-box.jsx) */}
374
+ <QuickCommandsFooterBox store={store} inline />
361
375
  </RightSidePanel>
362
376
  <SshConfigLoadNotify {...sshConfigProps} />
363
377
  <LoadSshConfigs