@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.
- package/README.md +5 -5
- package/client/common/bookmark-schemas.js +4 -0
- package/client/common/constants.js +3 -0
- package/client/common/create-title.jsx +5 -1
- package/client/common/default-setting.js +2 -0
- package/client/common/parse-quick-connect.js +1 -0
- package/client/common/parse-vv.js +119 -25
- package/client/common/parse-widget-log.js +57 -0
- package/client/common/vv-to-tab.js +120 -0
- package/client/components/ai/agent-tool-call-card.jsx +26 -7
- package/client/components/ai/agent.js +205 -45
- package/client/components/ai/ai-auto-compress.js +119 -0
- package/client/components/ai/ai-chat-history-item.jsx +133 -84
- package/client/components/ai/ai-chat.jsx +135 -30
- package/client/components/ai/ai-compress.js +95 -0
- package/client/components/ai/ai-config-props.js +12 -1
- package/client/components/ai/ai-config.jsx +43 -16
- package/client/components/ai/ai-config.styl +20 -0
- package/client/components/ai/ai-context-indicator.jsx +83 -0
- package/client/components/ai/ai-context.js +272 -0
- package/client/components/ai/ai-output.jsx +5 -6
- package/client/components/ai/ai-presets.js +1 -16
- package/client/components/ai/ai.styl +105 -6
- package/client/components/ai/copy-icon.jsx +31 -0
- package/client/components/batch-op/batch-op-runner.jsx +1 -0
- package/client/components/bookmark-form/bookmark-schema.js +1 -0
- package/client/components/bookmark-form/common/ssh-agent.jsx +31 -21
- package/client/components/bookmark-form/common/vv-file-field.jsx +3 -2
- package/client/components/bookmark-form/config/spice.js +21 -0
- package/client/components/bookmark-form/config/ssh.js +1 -0
- package/client/components/bookmark-form/fix-bookmark-default.js +1 -0
- package/client/components/file-transfer/transfer.jsx +3 -119
- package/client/components/footer/cmd-history.jsx +279 -65
- package/client/components/footer/cmd-history.styl +68 -1
- package/client/components/footer/footer-entry.jsx +7 -1
- package/client/components/layout/layout.jsx +12 -19
- package/client/components/main/main.jsx +15 -1
- package/client/components/quick-commands/qm.styl +58 -0
- package/client/components/quick-commands/quick-commands-box.jsx +180 -83
- package/client/components/quick-commands/quick-commands-select.jsx +11 -0
- package/client/components/session/session-control.jsx +35 -2
- package/client/components/session/session-control.styl +5 -0
- package/client/components/session/session.jsx +13 -0
- package/client/components/setting-panel/setting-common.jsx +2 -0
- package/client/components/setting-panel/tab-widgets.jsx +27 -7
- package/client/components/sftp/file-item.jsx +0 -13
- package/client/components/side-panel-r/side-panel-r.jsx +11 -4
- package/client/components/sidebar/bookmark-select.jsx +14 -2
- package/client/components/sidebar/history-item.jsx +20 -4
- package/client/components/sidebar/history.jsx +7 -0
- package/client/components/terminal/mixins/term-touch.js +18 -0
- package/client/components/tree-list/tree-list.jsx +31 -3
- package/client/components/widgets/widget-control.jsx +4 -2
- package/client/components/widgets/widget-instance-detail.jsx +370 -0
- package/client/components/widgets/widget-instance.jsx +30 -13
- package/client/components/widgets/widget-instances.jsx +3 -1
- package/client/components/widgets/widgets-list.jsx +8 -0
- package/client/components/widgets/widgets.styl +100 -0
- package/client/store/common.js +200 -54
- package/client/store/init-state.js +15 -0
- package/client/store/load-data.js +73 -0
- package/client/store/store.js +6 -0
- package/client/store/watch.js +3 -1
- package/client/store/widgets.js +23 -5
- package/package.json +1 -1
- package/client/components/file-transfer/zip.js +0 -42
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cmd history
|
|
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
|
|
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
|
-
//
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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':
|
|
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
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|