@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
|
@@ -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,7 @@ export default auto(function Index (props) {
|
|
|
282
288
|
showAIConfig: store.showAIConfig,
|
|
283
289
|
rightPanelTab,
|
|
284
290
|
agentRunning: store.agentRunning,
|
|
291
|
+
aiContextInfo: store.aiContextInfo,
|
|
285
292
|
currentChatSessionId: store.currentChatSessionId,
|
|
286
293
|
showChatSessions: store.showChatSessions
|
|
287
294
|
}
|
|
@@ -358,6 +365,12 @@ export default auto(function Index (props) {
|
|
|
358
365
|
<RightSidePanel {...rightPanelProps}>
|
|
359
366
|
{!isAIDisabled() && <AIChat {...aiChatProps} />}
|
|
360
367
|
<TerminalInfo key={store.activeTabId} store={store} {...deepCopy(store.terminalInfoProps)} />
|
|
368
|
+
{/* the cmd history panel has two homes; the footer popover is the
|
|
369
|
+
other one, and both are the same component (see cmd-history.jsx) */}
|
|
370
|
+
<CmdHistory store={store} inline />
|
|
371
|
+
{/* same for the quick command panel: the footer box is the other home
|
|
372
|
+
(see quick-commands-box.jsx) */}
|
|
373
|
+
<QuickCommandsFooterBox store={store} inline />
|
|
361
374
|
</RightSidePanel>
|
|
362
375
|
<SshConfigLoadNotify {...sshConfigProps} />
|
|
363
376
|
<LoadSshConfigs
|
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
max-height calc(100vh - 100px)
|
|
3
3
|
overflow-y scroll
|
|
4
4
|
padding 20px 0 10px 0
|
|
5
|
+
|
|
6
|
+
// Docked in the right side panel the panel *is* the container, so the quick
|
|
7
|
+
// commands fill it instead of sizing themselves like the footer box. The parent
|
|
8
|
+
// (.right-side-panel-content) is an absolutely positioned box with top and
|
|
9
|
+
// bottom set, so 100% resolves to a real height and the list scrolls inside it
|
|
10
|
+
// rather than scrolling the panel.
|
|
11
|
+
.qm-panel-in-right-panel
|
|
12
|
+
display flex
|
|
13
|
+
flex-direction column
|
|
14
|
+
width 100%
|
|
15
|
+
height 100%
|
|
16
|
+
> .pd2
|
|
17
|
+
display flex
|
|
18
|
+
flex-direction column
|
|
19
|
+
height 100%
|
|
20
|
+
min-height 0
|
|
21
|
+
width 100%
|
|
22
|
+
.qm-flex
|
|
23
|
+
flex-shrink 0
|
|
24
|
+
// the footer box is as wide as the footer; the right panel is not, and the
|
|
25
|
+
// search + label + sort + actions row does not fit in one line there
|
|
26
|
+
flex-wrap wrap
|
|
27
|
+
// ...so give the search a line of its own and let it fill it, instead of
|
|
28
|
+
// sitting at its footer width with a gap beside it. `flex-basis: 100%` is what
|
|
29
|
+
// forces the wrap; the controls then share the line below.
|
|
30
|
+
.qm-search-input
|
|
31
|
+
flex 1 1 100%
|
|
32
|
+
max-width none
|
|
33
|
+
.qm-list-wrap
|
|
34
|
+
flex 1
|
|
35
|
+
min-height 0
|
|
36
|
+
max-height none
|
|
37
|
+
overflow-y auto
|
|
38
|
+
|
|
39
|
+
.qm-panel-actions
|
|
40
|
+
display flex
|
|
41
|
+
align-items center
|
|
42
|
+
gap 6px
|
|
43
|
+
|
|
44
|
+
.qm-move-icon
|
|
45
|
+
font-size 14px
|
|
46
|
+
padding 4px
|
|
47
|
+
cursor pointer
|
|
48
|
+
&:hover
|
|
49
|
+
color var(--primary)
|
|
50
|
+
|
|
51
|
+
// The footer Q stays lit while the panel is docked in the right panel, same
|
|
52
|
+
// contract as the info, AI and cmd history icons
|
|
53
|
+
.quick-command-trigger
|
|
54
|
+
&.active
|
|
55
|
+
color var(--success)
|
|
56
|
+
|
|
5
57
|
.qm-wrap-tooltip
|
|
6
58
|
background var(--main)
|
|
7
59
|
position absolute
|
|
@@ -80,6 +132,12 @@
|
|
|
80
132
|
width 100%
|
|
81
133
|
.qm-label-select
|
|
82
134
|
width 100%
|
|
135
|
+
// The docked panel's search claims a whole row with `flex-basis: 100%`, which
|
|
136
|
+
// is a width only while `.qm-flex` is a row. Here it is a column, so the same
|
|
137
|
+
// basis would be 100% of the *height* and would push the controls off the
|
|
138
|
+
// panel. Same specificity as the rule above it, declared later, so it wins.
|
|
139
|
+
.qm-panel-in-right-panel .qm-search-input
|
|
140
|
+
flex none
|
|
83
141
|
|
|
84
142
|
// On mobile, stack the quick-command row (name / delay / command / remove)
|
|
85
143
|
// vertically instead of side by side — the horizontal Space.Compact layout
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* quick commands
|
|
2
|
+
* quick commands panel
|
|
3
|
+
*
|
|
4
|
+
* The panel has two homes: the floating footer box (default) and the right side
|
|
5
|
+
* panel. Which one it is in is a stored preference
|
|
6
|
+
* (store.quickCommandsInRightPanel), so the same component renders both —
|
|
7
|
+
* `inline` drops the floating box and lets the right panel's own container wrap
|
|
8
|
+
* the panel body.
|
|
3
9
|
*/
|
|
4
10
|
|
|
5
11
|
import { useState } from 'react'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
12
|
+
import { auto } from 'manate/react'
|
|
13
|
+
import {
|
|
14
|
+
quickCommandLabelsLsKey,
|
|
15
|
+
pinnedQuickCommandBarKey
|
|
16
|
+
} from '../../common/constants'
|
|
17
|
+
import { sortBy, pick } from 'lodash-es'
|
|
8
18
|
import { Button, Input, Select, Space, Flex } from 'antd'
|
|
9
19
|
import * as ls from '../../common/safe-local-storage'
|
|
10
20
|
import CmdItem from './quick-command-item'
|
|
11
21
|
import {
|
|
12
22
|
EditOutlined,
|
|
13
23
|
CloseCircleOutlined,
|
|
14
|
-
PushpinOutlined
|
|
24
|
+
PushpinOutlined,
|
|
25
|
+
VerticalLeftOutlined,
|
|
26
|
+
VerticalAlignBottomOutlined
|
|
15
27
|
} from '@ant-design/icons'
|
|
16
28
|
import classNames from 'classnames'
|
|
17
29
|
import onDropFunc from './on-drop'
|
|
@@ -22,9 +34,42 @@ const e = window.translate
|
|
|
22
34
|
const addQuickCommands = 'addQuickCommands'
|
|
23
35
|
const { Option } = Select
|
|
24
36
|
|
|
25
|
-
export default function QuickCommandsFooterBox (props) {
|
|
37
|
+
export default auto(function QuickCommandsFooterBox (props) {
|
|
38
|
+
const { store, inline } = props
|
|
26
39
|
const [keyword, setKeyword] = useState('')
|
|
27
40
|
const [label, setLabel] = useState(ls.getItem(quickCommandLabelsLsKey, ''))
|
|
41
|
+
// read through the store so the panel re-renders on its own: it is rendered
|
|
42
|
+
// in two different places (footer box and right panel), and only one of them
|
|
43
|
+
// gets props from the layout
|
|
44
|
+
const {
|
|
45
|
+
openQuickCommandBar,
|
|
46
|
+
pinnedQuickCommandBar,
|
|
47
|
+
qmSortByFrequency,
|
|
48
|
+
quickCommandsInRightPanel,
|
|
49
|
+
inActiveTerminal,
|
|
50
|
+
leftSidePanelWidth,
|
|
51
|
+
leftSideBarWidth,
|
|
52
|
+
openedSideBar,
|
|
53
|
+
rightPanelVisible,
|
|
54
|
+
rightPanelPinned,
|
|
55
|
+
rightPanelWidth,
|
|
56
|
+
isMobile
|
|
57
|
+
} = pick(store, [
|
|
58
|
+
'openQuickCommandBar',
|
|
59
|
+
'pinnedQuickCommandBar',
|
|
60
|
+
'qmSortByFrequency',
|
|
61
|
+
'quickCommandsInRightPanel',
|
|
62
|
+
'inActiveTerminal',
|
|
63
|
+
'leftSidePanelWidth',
|
|
64
|
+
'leftSideBarWidth',
|
|
65
|
+
'openedSideBar',
|
|
66
|
+
'rightPanelVisible',
|
|
67
|
+
'rightPanelPinned',
|
|
68
|
+
'rightPanelWidth',
|
|
69
|
+
'isMobile'
|
|
70
|
+
])
|
|
71
|
+
const all = store.currentQuickCommands
|
|
72
|
+
const quickCommandTags = store.quickCommandTags
|
|
28
73
|
|
|
29
74
|
function handleTogglePinned () {
|
|
30
75
|
const current = !window.store.pinnedQuickCommandBar
|
|
@@ -58,12 +103,12 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
58
103
|
setLabel(v)
|
|
59
104
|
}
|
|
60
105
|
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
106
|
+
// Dock the panel into the right side panel. The footer box unmounts on the
|
|
107
|
+
// way out (it is gated on quickCommandsInRightPanel), so there is nothing to
|
|
108
|
+
// clean up here — unlike the cmd history popover, which has to be closed.
|
|
109
|
+
function handleMoveToRightPanel () {
|
|
110
|
+
window.store.moveQuickCommandsToRightPanel()
|
|
111
|
+
}
|
|
67
112
|
|
|
68
113
|
function onDragOver (e) {
|
|
69
114
|
e.preventDefault()
|
|
@@ -114,9 +159,6 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
114
159
|
}
|
|
115
160
|
|
|
116
161
|
function renderItem (item) {
|
|
117
|
-
const {
|
|
118
|
-
qmSortByFrequency
|
|
119
|
-
} = props
|
|
120
162
|
return (
|
|
121
163
|
<CmdItem
|
|
122
164
|
item={item}
|
|
@@ -156,26 +198,69 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
156
198
|
})
|
|
157
199
|
}
|
|
158
200
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
201
|
+
// The two "move" icons are mirror images and sit in the same slot: the footer
|
|
202
|
+
// box offers to dock the panel into the right side panel, the docked panel
|
|
203
|
+
// offers to hand it back to the footer. Same glyph pair as the cmd history
|
|
204
|
+
// panel (cmd-history.jsx), so the two panels read the same way.
|
|
205
|
+
// Note VerticalLeftOutlined is the one whose bar is on the right (arrow
|
|
206
|
+
// pointing right); VerticalAlignBottomOutlined points at the footer.
|
|
207
|
+
function renderMoveIcon () {
|
|
208
|
+
if (inline) {
|
|
209
|
+
return (
|
|
210
|
+
<VerticalAlignBottomOutlined
|
|
211
|
+
className='qm-move-icon pointer'
|
|
212
|
+
title={e('moveToFooter')}
|
|
213
|
+
onClick={() => window.store.moveQuickCommandsToFooter()}
|
|
214
|
+
/>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
return (
|
|
218
|
+
<VerticalLeftOutlined
|
|
219
|
+
className='qm-move-icon pointer'
|
|
220
|
+
title={e('moveToRightPanel')}
|
|
221
|
+
onClick={handleMoveToRightPanel}
|
|
222
|
+
/>
|
|
223
|
+
)
|
|
174
224
|
}
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
225
|
+
|
|
226
|
+
// Docked, the right panel owns the pin and the close button (both live in its
|
|
227
|
+
// title bar), so the panel keeps only the actions that are about the quick
|
|
228
|
+
// commands themselves. A pin left over from the footer is kept in the store
|
|
229
|
+
// and comes back with the panel, so nothing is silently lost.
|
|
230
|
+
function renderActions () {
|
|
231
|
+
const tp = pinnedQuickCommandBar
|
|
232
|
+
? 'primary'
|
|
233
|
+
: 'text'
|
|
234
|
+
return (
|
|
235
|
+
<div className='qm-panel-actions mg2l'>
|
|
236
|
+
{renderMoveIcon()}
|
|
237
|
+
<Space.Compact>
|
|
238
|
+
{
|
|
239
|
+
!inline && (
|
|
240
|
+
<Button
|
|
241
|
+
onClick={handleTogglePinned}
|
|
242
|
+
icon={<PushpinOutlined />}
|
|
243
|
+
type={tp}
|
|
244
|
+
/>
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
<Button
|
|
248
|
+
onClick={window.store.handleOpenQuickCommandsSetting}
|
|
249
|
+
icon={<EditOutlined />}
|
|
250
|
+
/>
|
|
251
|
+
{
|
|
252
|
+
!inline && (
|
|
253
|
+
<Button
|
|
254
|
+
onClick={handleClose}
|
|
255
|
+
icon={<CloseCircleOutlined />}
|
|
256
|
+
/>
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
</Space.Compact>
|
|
260
|
+
</div>
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
|
|
179
264
|
const keyword0 = keyword.toLowerCase()
|
|
180
265
|
const filtered = filterArray(all, keyword0, label)
|
|
181
266
|
const sorted = qmSortByFrequency
|
|
@@ -188,9 +273,6 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
188
273
|
className: 'qm-label-select',
|
|
189
274
|
allowClear: true
|
|
190
275
|
}
|
|
191
|
-
const tp = pinnedQuickCommandBar
|
|
192
|
-
? 'primary'
|
|
193
|
-
: 'text'
|
|
194
276
|
const cls = classNames('qm-list-wrap')
|
|
195
277
|
const type = qmSortByFrequency ? 'primary' : 'default'
|
|
196
278
|
// Mirrors the footer's left offset. Mobile reserves nothing for the side
|
|
@@ -214,56 +296,71 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
214
296
|
className: 'qm-wrap-tooltip',
|
|
215
297
|
style: qmStyle
|
|
216
298
|
}
|
|
299
|
+
|
|
300
|
+
const content = (
|
|
301
|
+
<div className='pd2'>
|
|
302
|
+
<Flex justify='space-between' className='qm-flex'>
|
|
303
|
+
<Input.Search
|
|
304
|
+
value={keyword}
|
|
305
|
+
onChange={handleChange}
|
|
306
|
+
placeholder=''
|
|
307
|
+
className='qm-search-input'
|
|
308
|
+
/>
|
|
309
|
+
<Flex gap='small'>
|
|
310
|
+
<Select
|
|
311
|
+
{...sprops}
|
|
312
|
+
>
|
|
313
|
+
{quickCommandTags.map(
|
|
314
|
+
renderTag
|
|
315
|
+
)}
|
|
316
|
+
</Select>
|
|
317
|
+
<Button
|
|
318
|
+
type={type}
|
|
319
|
+
onClick={window.store.handleSortByFrequency}
|
|
320
|
+
>
|
|
321
|
+
{e('sortByFrequency')}
|
|
322
|
+
</Button>
|
|
323
|
+
</Flex>
|
|
324
|
+
{renderActions()}
|
|
325
|
+
</Flex>
|
|
326
|
+
<div className={cls}>
|
|
327
|
+
{sorted.map(renderItem)}
|
|
328
|
+
{
|
|
329
|
+
!sorted.length && renderNoCmd()
|
|
330
|
+
}
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
// docked in the right side panel: no floating box, no pin/close of its own —
|
|
336
|
+
// the right panel decides when this is on screen, via store.rightPanelTab
|
|
337
|
+
if (inline) {
|
|
338
|
+
if (store.rightPanelTab !== 'quickCommands') {
|
|
339
|
+
return null
|
|
340
|
+
}
|
|
341
|
+
return (
|
|
342
|
+
<div className='qm-panel-in-right-panel'>
|
|
343
|
+
{content}
|
|
344
|
+
</div>
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Dormant while the panel is docked: the box would otherwise float over the
|
|
349
|
+
// terminal with nothing to show for it, and a pin left on would keep
|
|
350
|
+
// reserving the strip of height it occupies (see layout.jsx).
|
|
351
|
+
if (
|
|
352
|
+
quickCommandsInRightPanel ||
|
|
353
|
+
(!openQuickCommandBar && !pinnedQuickCommandBar) ||
|
|
354
|
+
!inActiveTerminal
|
|
355
|
+
) {
|
|
356
|
+
return null
|
|
357
|
+
}
|
|
358
|
+
|
|
217
359
|
return (
|
|
218
360
|
<div
|
|
219
361
|
{...qmProps}
|
|
220
362
|
>
|
|
221
|
-
|
|
222
|
-
<Flex justify='space-between' className='qm-flex'>
|
|
223
|
-
<Input.Search
|
|
224
|
-
value={keyword}
|
|
225
|
-
onChange={handleChange}
|
|
226
|
-
placeholder=''
|
|
227
|
-
className='qm-search-input'
|
|
228
|
-
/>
|
|
229
|
-
<Flex gap='small'>
|
|
230
|
-
<Select
|
|
231
|
-
{...sprops}
|
|
232
|
-
>
|
|
233
|
-
{props.quickCommandTags.map(
|
|
234
|
-
renderTag
|
|
235
|
-
)}
|
|
236
|
-
</Select>
|
|
237
|
-
<Button
|
|
238
|
-
type={type}
|
|
239
|
-
onClick={window.store.handleSortByFrequency}
|
|
240
|
-
>
|
|
241
|
-
{e('sortByFrequency')}
|
|
242
|
-
</Button>
|
|
243
|
-
</Flex>
|
|
244
|
-
<Space.Compact className='mg2l'>
|
|
245
|
-
<Button
|
|
246
|
-
onClick={handleTogglePinned}
|
|
247
|
-
icon={<PushpinOutlined />}
|
|
248
|
-
type={tp}
|
|
249
|
-
/>
|
|
250
|
-
<Button
|
|
251
|
-
onClick={window.store.handleOpenQuickCommandsSetting}
|
|
252
|
-
icon={<EditOutlined />}
|
|
253
|
-
/>
|
|
254
|
-
<Button
|
|
255
|
-
onClick={handleClose}
|
|
256
|
-
icon={<CloseCircleOutlined />}
|
|
257
|
-
/>
|
|
258
|
-
</Space.Compact>
|
|
259
|
-
</Flex>
|
|
260
|
-
<div className={cls}>
|
|
261
|
-
{sorted.map(renderItem)}
|
|
262
|
-
{
|
|
263
|
-
!sorted.length && renderNoCmd()
|
|
264
|
-
}
|
|
265
|
-
</div>
|
|
266
|
-
</div>
|
|
363
|
+
{content}
|
|
267
364
|
</div>
|
|
268
365
|
)
|
|
269
|
-
}
|
|
366
|
+
})
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* on touch (mobile) and mouse (desktop) — the old hover-to-open model was
|
|
6
6
|
* unreliable on Android, where a tap synthesizes mouseleave and cancels the
|
|
7
7
|
* open timer.
|
|
8
|
+
*
|
|
9
|
+
* The panel has two homes (see quick-commands-box.jsx): the footer box and the
|
|
10
|
+
* right side panel. While it is docked, this button is repurposed to open and
|
|
11
|
+
* close that panel, exactly like the info and AI icons do — and `active` (fed
|
|
12
|
+
* by the footer, which is already reactive) keeps it lit while it is showing.
|
|
8
13
|
*/
|
|
9
14
|
|
|
10
15
|
import { PureComponent } from 'react'
|
|
@@ -14,15 +19,21 @@ import './qm.styl'
|
|
|
14
19
|
export default class QuickCommandsFooter extends PureComponent {
|
|
15
20
|
handleClick = () => {
|
|
16
21
|
const { store } = window
|
|
22
|
+
if (store.quickCommandsInRightPanel) {
|
|
23
|
+
store.toggleQuickCommandsPanel()
|
|
24
|
+
return
|
|
25
|
+
}
|
|
17
26
|
store.openQuickCommandBar = !store.openQuickCommandBar
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
render () {
|
|
30
|
+
const { active } = this.props
|
|
21
31
|
return (
|
|
22
32
|
<div className='fleft relative quick-command-trigger-wrap'>
|
|
23
33
|
<Button
|
|
24
34
|
size='small'
|
|
25
35
|
type='text'
|
|
36
|
+
className={'quick-command-trigger' + (active ? ' active' : '')}
|
|
26
37
|
onClick={this.handleClick}
|
|
27
38
|
>
|
|
28
39
|
Q
|
|
@@ -574,7 +574,9 @@ export default class SettingCommon extends Component {
|
|
|
574
574
|
'allowMultiInstance',
|
|
575
575
|
'disableDeveloperTool',
|
|
576
576
|
'switchTabOnHover',
|
|
577
|
+
'doubleClickToOpenBookmark',
|
|
577
578
|
'disableTabIndex',
|
|
579
|
+
'onlyShowTitleInTab',
|
|
578
580
|
'disableShortcutBar',
|
|
579
581
|
'debug'
|
|
580
582
|
]
|