@electerm/electerm-react 5.5.15 → 5.5.35
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 +14 -4
- package/client/common/bookmark-schemas.js +3 -0
- package/client/common/constants.js +16 -0
- package/client/common/create-title.jsx +5 -1
- package/client/common/default-setting.js +4 -2
- package/client/common/parse-vv.js +407 -0
- package/client/common/theme-defaults.js +5 -2
- package/client/common/update-check.js +5 -4
- package/client/common/vv-to-tab.js +120 -0
- package/client/components/ai/agent-tool-call-card.jsx +19 -4
- package/client/components/ai/agent.js +33 -16
- package/client/components/ai/ai-chat-history-item.jsx +12 -48
- package/client/components/ai/ai-chat.jsx +82 -25
- package/client/components/ai/ai-config-props.js +12 -1
- package/client/components/ai/ai-config.jsx +14 -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 +4 -0
- package/client/components/ai/ai-presets.js +13 -2
- package/client/components/ai/ai.styl +105 -6
- package/client/components/ai/copy-icon.jsx +31 -0
- package/client/components/bg/css-overwrite.jsx +16 -1
- package/client/components/bookmark-form/common/fields.jsx +3 -0
- package/client/components/bookmark-form/common/vv-file-field.jsx +138 -0
- package/client/components/bookmark-form/config/spice.js +22 -0
- 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 +46 -15
- package/client/components/icons/ai-icon.styl +4 -1
- package/client/components/layout/layout.jsx +12 -13
- package/client/components/main/main.jsx +23 -3
- package/client/components/main/term-fullscreen.styl +7 -0
- package/client/components/quick-commands/qm.styl +58 -0
- package/client/components/quick-commands/quick-command-ai-editor.jsx +255 -0
- package/client/components/quick-commands/quick-command-ai-editor.styl +13 -0
- package/client/components/quick-commands/quick-commands-box.jsx +197 -82
- package/client/components/quick-commands/quick-commands-form-elem.jsx +129 -65
- package/client/components/quick-commands/quick-commands-select.jsx +11 -0
- package/client/components/setting-panel/list.jsx +2 -2
- package/client/components/setting-panel/setting-common.jsx +2 -0
- package/client/components/side-panel-r/right-side-panel.styl +11 -1
- package/client/components/side-panel-r/side-panel-r.jsx +56 -13
- package/client/components/sidebar/bookmark-select.jsx +17 -3
- package/client/components/sidebar/history-item.jsx +20 -4
- package/client/components/sidebar/history.jsx +7 -0
- package/client/components/sidebar/index.jsx +13 -4
- package/client/components/sidebar/side-panel.jsx +8 -3
- package/client/components/sidebar/sidebar-panel.jsx +20 -7
- package/client/components/terminal/shortcut-bar.styl +14 -0
- package/client/components/terminal/unix-timestamp-tooltip.jsx +27 -1
- package/client/components/terminal-info/terminal-info.styl +3 -0
- package/client/components/theme/terminal-theme-list.styl +3 -0
- package/client/components/theme/theme-ai-editor.jsx +2 -2
- package/client/components/theme/theme-form.jsx +2 -2
- package/client/components/theme/theme-list-item.jsx +59 -25
- package/client/components/tree-list/tree-list.jsx +31 -3
- package/client/css/mobile.styl +28 -0
- package/client/store/common.js +164 -32
- package/client/store/init-state.js +10 -1
- package/client/store/load-data.js +72 -0
- package/client/store/sidebar.js +5 -1
- package/client/store/store.js +15 -2
- package/client/store/watch.js +3 -1
- package/package.json +1 -1
|
@@ -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
|
|
@@ -24,7 +24,7 @@ const {
|
|
|
24
24
|
|
|
25
25
|
export default auto(function FooterEntry (props) {
|
|
26
26
|
function handleInfoPanel () {
|
|
27
|
-
window.store.
|
|
27
|
+
window.store.toggleInfoPanel()
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function batchInput (cmd, selectedTabIds) {
|
|
@@ -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
|
}
|
|
@@ -106,11 +112,19 @@ export default auto(function FooterEntry (props) {
|
|
|
106
112
|
)
|
|
107
113
|
}
|
|
108
114
|
|
|
115
|
+
function handleAIPanel () {
|
|
116
|
+
window.store.toggleAIPanel()
|
|
117
|
+
}
|
|
118
|
+
|
|
109
119
|
function renderAIIcon () {
|
|
120
|
+
const { rightPanelVisible, rightPanelTab } = props.store
|
|
121
|
+
// same contract as the info icon: lit while its panel is open
|
|
122
|
+
const active = rightPanelVisible && rightPanelTab === 'ai'
|
|
110
123
|
return (
|
|
111
124
|
<div className='terminal-footer-unit terminal-footer-ai'>
|
|
112
125
|
<AIIcon
|
|
113
|
-
onClick={
|
|
126
|
+
onClick={handleAIPanel}
|
|
127
|
+
className={active ? 'active' : ''}
|
|
114
128
|
/>
|
|
115
129
|
</div>
|
|
116
130
|
)
|
|
@@ -176,11 +190,15 @@ export default auto(function FooterEntry (props) {
|
|
|
176
190
|
if (loading) {
|
|
177
191
|
return null
|
|
178
192
|
}
|
|
193
|
+
const { rightPanelVisible, rightPanelTab } = props.store
|
|
194
|
+
// keep the icon lit while its panel is open so it reads as "click to close"
|
|
195
|
+
const active = rightPanelVisible && rightPanelTab === 'info'
|
|
179
196
|
return (
|
|
180
197
|
<div className='terminal-footer-unit terminal-footer-info'>
|
|
181
198
|
<BarChartOutlined
|
|
182
199
|
onClick={handleInfoPanel}
|
|
183
|
-
className='pointer font14 terminal-info-icon'
|
|
200
|
+
className={'pointer font14 terminal-info-icon' + (active ? ' active' : '')}
|
|
201
|
+
title={e('info')}
|
|
184
202
|
/>
|
|
185
203
|
</div>
|
|
186
204
|
)
|
|
@@ -202,7 +220,11 @@ export default auto(function FooterEntry (props) {
|
|
|
202
220
|
leftSidePanelWidth,
|
|
203
221
|
leftSideBarWidth,
|
|
204
222
|
openedSideBar,
|
|
205
|
-
inActiveTerminal
|
|
223
|
+
inActiveTerminal,
|
|
224
|
+
rightPanelVisible,
|
|
225
|
+
rightPanelPinned,
|
|
226
|
+
rightPanelWidth,
|
|
227
|
+
isMobile
|
|
206
228
|
} = props.store
|
|
207
229
|
const w = leftSideBarWidth + leftSidePanelWidth
|
|
208
230
|
// icon bar hidden: show a control on the left of the footer to bring the
|
|
@@ -217,16 +239,25 @@ export default auto(function FooterEntry (props) {
|
|
|
217
239
|
</div>
|
|
218
240
|
)
|
|
219
241
|
: null
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
242
|
+
// The footer mirrors the space layout.jsx reserves on both sides.
|
|
243
|
+
// Left: reserve while the panel is open OR pinned. A pinned left panel is
|
|
244
|
+
// always open, so this is a superset of the pin state — and it is what keeps
|
|
245
|
+
// the footer from painting over the panel, which spans bottom 0 in both
|
|
246
|
+
// states. Right: reserve only for a pinned panel, because the unpinned right
|
|
247
|
+
// panel is an overlay that stops above the footer and so never needs the
|
|
248
|
+
// space. Mobile reserves nothing on either side, matching layout.jsx, and the
|
|
249
|
+
// right panel has no pin control there at all.
|
|
250
|
+
const footerStyle = {}
|
|
251
|
+
if (openedSideBar && !isMobile) {
|
|
252
|
+
footerStyle.left = `${w}px`
|
|
253
|
+
}
|
|
254
|
+
if (rightPanelVisible && rightPanelPinned && !isMobile) {
|
|
255
|
+
footerStyle.right = `${rightPanelWidth}px`
|
|
256
|
+
}
|
|
257
|
+
const sideProps = {
|
|
258
|
+
className: 'main-footer',
|
|
259
|
+
style: footerStyle
|
|
260
|
+
}
|
|
230
261
|
if (
|
|
231
262
|
!inActiveTerminal
|
|
232
263
|
) {
|