@electerm/electerm-react 5.5.25 → 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 +5 -5
- package/client/common/bookmark-schemas.js +3 -0
- package/client/common/constants.js +2 -0
- package/client/common/create-title.jsx +5 -1
- package/client/common/default-setting.js +2 -0
- package/client/common/parse-vv.js +119 -25
- 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 +1 -1
- package/client/components/ai/ai.styl +105 -6
- package/client/components/ai/copy-icon.jsx +31 -0
- 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/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 +14 -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/setting-panel/setting-common.jsx +2 -0
- 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/tree-list/tree-list.jsx +31 -3
- package/client/store/common.js +120 -32
- package/client/store/init-state.js +8 -0
- package/client/store/load-data.js +72 -0
- package/client/store/store.js +6 -0
- package/client/store/watch.js +3 -1
- package/package.json +1 -1
|
@@ -26,11 +26,26 @@
|
|
|
26
26
|
border-radius 3px
|
|
27
27
|
pre
|
|
28
28
|
margin-bottom 0
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
|
|
30
|
+
// copy icons stay visible all the time, never hover only
|
|
31
|
+
.copy-icon,
|
|
32
|
+
.code-action-icon
|
|
33
|
+
color var(--text-dark)
|
|
34
|
+
font-size 14px
|
|
35
|
+
&:hover
|
|
36
|
+
color var(--primary)
|
|
37
|
+
|
|
38
|
+
.code-block-actions
|
|
39
|
+
display flex
|
|
40
|
+
justify-content flex-end
|
|
41
|
+
|
|
42
|
+
// left aligned on purpose: the streaming stop/loading icon is pinned to the
|
|
43
|
+
// bottom right of .chat-history-item, so a right aligned copy icon would sit
|
|
44
|
+
// on top of it while a response is streaming
|
|
45
|
+
.ai-output-actions
|
|
46
|
+
display flex
|
|
47
|
+
justify-content flex-start
|
|
48
|
+
margin-top 6px
|
|
34
49
|
|
|
35
50
|
.ai-stream-output
|
|
36
51
|
position relative
|
|
@@ -223,9 +238,18 @@
|
|
|
223
238
|
font-size 12px
|
|
224
239
|
color var(--text-color-2)
|
|
225
240
|
|
|
241
|
+
.agent-tool-pre-wrap
|
|
242
|
+
position relative
|
|
243
|
+
|
|
244
|
+
.agent-tool-copy
|
|
245
|
+
position absolute
|
|
246
|
+
top 5px
|
|
247
|
+
right 6px
|
|
248
|
+
z-index 1
|
|
249
|
+
|
|
226
250
|
.agent-tool-pre
|
|
227
251
|
background var(--main-dark)
|
|
228
|
-
padding 6px 8px
|
|
252
|
+
padding 6px 26px 6px 8px
|
|
229
253
|
border-radius 3px
|
|
230
254
|
font-size 12px
|
|
231
255
|
max-height 200px
|
|
@@ -313,3 +337,78 @@
|
|
|
313
337
|
&:hover
|
|
314
338
|
border-color var(--primary)
|
|
315
339
|
color var(--text)
|
|
340
|
+
|
|
341
|
+
// context size indicator, right hand side of the ai chat toolbar
|
|
342
|
+
.ai-context-indicator
|
|
343
|
+
display inline-flex
|
|
344
|
+
align-items center
|
|
345
|
+
gap 4px
|
|
346
|
+
flex-shrink 0
|
|
347
|
+
font-size 11px
|
|
348
|
+
line-height 1
|
|
349
|
+
color var(--text-color-2)
|
|
350
|
+
cursor default
|
|
351
|
+
user-select none
|
|
352
|
+
|
|
353
|
+
.ai-context-bar
|
|
354
|
+
display inline-block
|
|
355
|
+
width 34px
|
|
356
|
+
height 5px
|
|
357
|
+
border-radius 3px
|
|
358
|
+
background var(--main-darker)
|
|
359
|
+
overflow hidden
|
|
360
|
+
flex-shrink 0
|
|
361
|
+
|
|
362
|
+
.ai-context-bar-fill
|
|
363
|
+
display block
|
|
364
|
+
height 100%
|
|
365
|
+
border-radius 3px
|
|
366
|
+
background var(--primary)
|
|
367
|
+
transition width 0.2s
|
|
368
|
+
|
|
369
|
+
.ai-context-count
|
|
370
|
+
color var(--text-dark)
|
|
371
|
+
font-variant-numeric tabular-nums
|
|
372
|
+
|
|
373
|
+
.ai-context-percent
|
|
374
|
+
font-variant-numeric tabular-nums
|
|
375
|
+
|
|
376
|
+
.ai-context-warn
|
|
377
|
+
.ai-context-bar-fill
|
|
378
|
+
background #faad14
|
|
379
|
+
.ai-context-percent
|
|
380
|
+
color #faad14
|
|
381
|
+
|
|
382
|
+
.ai-context-danger
|
|
383
|
+
.ai-context-bar-fill
|
|
384
|
+
background #ff4d4f
|
|
385
|
+
.ai-context-percent
|
|
386
|
+
color #ff4d4f
|
|
387
|
+
font-weight 500
|
|
388
|
+
|
|
389
|
+
.ai-context-tip
|
|
390
|
+
font-size 12px
|
|
391
|
+
min-width 210px
|
|
392
|
+
|
|
393
|
+
.ai-context-tip-title
|
|
394
|
+
font-weight 500
|
|
395
|
+
margin-bottom 4px
|
|
396
|
+
|
|
397
|
+
.ai-context-tip-row
|
|
398
|
+
display flex
|
|
399
|
+
justify-content space-between
|
|
400
|
+
gap 12px
|
|
401
|
+
|
|
402
|
+
.ai-context-tip-total
|
|
403
|
+
font-weight 500
|
|
404
|
+
border-top 1px solid rgba(128, 128, 128, 0.35)
|
|
405
|
+
margin-top 2px
|
|
406
|
+
padding-top 2px
|
|
407
|
+
|
|
408
|
+
.ai-context-tip-note
|
|
409
|
+
margin-top 4px
|
|
410
|
+
opacity 0.75
|
|
411
|
+
|
|
412
|
+
.ai-context-tip-warn
|
|
413
|
+
margin-top 4px
|
|
414
|
+
color #faad14
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CopyOutlined } from '@ant-design/icons'
|
|
2
|
+
import { copy } from '../../common/clipboard'
|
|
3
|
+
|
|
4
|
+
const e = window.translate
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Always visible copy icon, shared by AI output and agent tool call blocks
|
|
8
|
+
* @param {Object} props
|
|
9
|
+
* @param {string} props.text - text to copy
|
|
10
|
+
* @param {string} props.className - extra class for positioning
|
|
11
|
+
* @param {string} props.title - tooltip title
|
|
12
|
+
*/
|
|
13
|
+
export default function CopyIcon ({ text, className = '', title }) {
|
|
14
|
+
function handleCopy (evt) {
|
|
15
|
+
// agent tool card header toggles expanded state on click
|
|
16
|
+
evt.stopPropagation()
|
|
17
|
+
copy(text)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!text) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<CopyOutlined
|
|
26
|
+
className={`copy-icon pointer iblock${className ? ' ' + className : ''}`}
|
|
27
|
+
onClick={handleCopy}
|
|
28
|
+
title={title || e('copy')}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* bookmark form - load a .vv (virt-viewer) connection file
|
|
3
3
|
*
|
|
4
4
|
* Registered as field type 'vvFile' in ./fields.jsx and used by
|
|
5
|
-
* ../config/spice.js. Fills host/port/password/title/proxy
|
|
6
|
-
* reports anything it could not honour instead of dropping
|
|
5
|
+
* ../config/spice.js. Fills host/port/password/title/proxy/tls/ca/hostSubject
|
|
6
|
+
* from the file and reports anything it could not honour instead of dropping
|
|
7
|
+
* it silently.
|
|
7
8
|
*/
|
|
8
9
|
import { useState } from 'react'
|
|
9
10
|
import {
|
|
@@ -12,6 +12,9 @@ const spiceConfig = {
|
|
|
12
12
|
initValues: (props) => {
|
|
13
13
|
return createBaseInitValues(props, terminalSpiceType, {
|
|
14
14
|
port: 5900,
|
|
15
|
+
tls: false,
|
|
16
|
+
ca: '',
|
|
17
|
+
hostSubject: '',
|
|
15
18
|
viewOnly: false,
|
|
16
19
|
scaleViewport: true,
|
|
17
20
|
connectionHoppings: [],
|
|
@@ -29,6 +32,24 @@ const spiceConfig = {
|
|
|
29
32
|
{ type: 'vvFile', name: '__vv__', label: '' },
|
|
30
33
|
{ type: 'input', name: 'host', label: () => e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
|
|
31
34
|
commonFields.port,
|
|
35
|
+
{ type: 'switch', name: 'tls', label: 'TLS', valuePropName: 'checked' },
|
|
36
|
+
{
|
|
37
|
+
type: 'textarea',
|
|
38
|
+
name: 'ca',
|
|
39
|
+
label: 'CA certificate (PEM)',
|
|
40
|
+
props: {
|
|
41
|
+
autoSize: { minRows: 2, maxRows: 8 },
|
|
42
|
+
placeholder: '-----BEGIN CERTIFICATE-----'
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'input',
|
|
47
|
+
name: 'hostSubject',
|
|
48
|
+
label: 'Certificate subject',
|
|
49
|
+
props: {
|
|
50
|
+
placeholder: 'CN=pve1,O=Proxmox Virtual Environment'
|
|
51
|
+
}
|
|
52
|
+
},
|
|
32
53
|
{ type: 'switch', name: 'viewOnly', label: () => e('viewOnly'), valuePropName: 'checked' },
|
|
33
54
|
{ type: 'switch', name: 'scaleViewport', label: () => e('scaleViewport'), valuePropName: 'checked' },
|
|
34
55
|
{ type: 'profileItem', name: '__profile__', label: '', profileFilter: d => !isEmpty(d.spice) },
|
|
@@ -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
|
})
|