@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,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,22 +198,69 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
156
198
|
})
|
|
157
199
|
}
|
|
158
200
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
+
)
|
|
224
|
+
}
|
|
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
|
+
)
|
|
170
262
|
}
|
|
171
|
-
|
|
172
|
-
// if (!all.length) {
|
|
173
|
-
// return renderNoCmd()
|
|
174
|
-
// }
|
|
263
|
+
|
|
175
264
|
const keyword0 = keyword.toLowerCase()
|
|
176
265
|
const filtered = filterArray(all, keyword0, label)
|
|
177
266
|
const sorted = qmSortByFrequency
|
|
@@ -184,68 +273,94 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
184
273
|
className: 'qm-label-select',
|
|
185
274
|
allowClear: true
|
|
186
275
|
}
|
|
187
|
-
const tp = pinnedQuickCommandBar
|
|
188
|
-
? 'primary'
|
|
189
|
-
: 'text'
|
|
190
276
|
const cls = classNames('qm-list-wrap')
|
|
191
277
|
const type = qmSortByFrequency ? 'primary' : 'default'
|
|
192
|
-
|
|
278
|
+
// Mirrors the footer's left offset. Mobile reserves nothing for the side
|
|
279
|
+
// panel — it is a full-screen drawer there (store.leftSidePanelWidth returns
|
|
280
|
+
// the viewport width), so adding it would push the popup off-screen.
|
|
281
|
+
const w = openedSideBar && !isMobile
|
|
282
|
+
? leftSideBarWidth + leftSidePanelWidth
|
|
283
|
+
: leftSideBarWidth
|
|
284
|
+
// Keep the popup's horizontal extent in step with the footer (same `w`
|
|
285
|
+
// expression above). A pinned right panel is a dock that reserves its width
|
|
286
|
+
// in layout.jsx, so give that width up here too — otherwise the popup's right
|
|
287
|
+
// end slides under the dock. Unpinned is an overlay and reserves nothing, and
|
|
288
|
+
// mobile is excluded to match layout.jsx.
|
|
289
|
+
const qmStyle = {
|
|
290
|
+
left: w
|
|
291
|
+
}
|
|
292
|
+
if (rightPanelVisible && rightPanelPinned && !isMobile) {
|
|
293
|
+
qmStyle.right = rightPanelWidth
|
|
294
|
+
}
|
|
193
295
|
const qmProps = {
|
|
194
296
|
className: 'qm-wrap-tooltip',
|
|
195
|
-
style:
|
|
196
|
-
|
|
297
|
+
style: qmStyle
|
|
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
|
|
197
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
|
|
198
357
|
}
|
|
358
|
+
|
|
199
359
|
return (
|
|
200
360
|
<div
|
|
201
361
|
{...qmProps}
|
|
202
362
|
>
|
|
203
|
-
|
|
204
|
-
<Flex justify='space-between' className='qm-flex'>
|
|
205
|
-
<Input.Search
|
|
206
|
-
value={keyword}
|
|
207
|
-
onChange={handleChange}
|
|
208
|
-
placeholder=''
|
|
209
|
-
className='qm-search-input'
|
|
210
|
-
/>
|
|
211
|
-
<Flex gap='small'>
|
|
212
|
-
<Select
|
|
213
|
-
{...sprops}
|
|
214
|
-
>
|
|
215
|
-
{props.quickCommandTags.map(
|
|
216
|
-
renderTag
|
|
217
|
-
)}
|
|
218
|
-
</Select>
|
|
219
|
-
<Button
|
|
220
|
-
type={type}
|
|
221
|
-
onClick={window.store.handleSortByFrequency}
|
|
222
|
-
>
|
|
223
|
-
{e('sortByFrequency')}
|
|
224
|
-
</Button>
|
|
225
|
-
</Flex>
|
|
226
|
-
<Space.Compact className='mg2l'>
|
|
227
|
-
<Button
|
|
228
|
-
onClick={handleTogglePinned}
|
|
229
|
-
icon={<PushpinOutlined />}
|
|
230
|
-
type={tp}
|
|
231
|
-
/>
|
|
232
|
-
<Button
|
|
233
|
-
onClick={window.store.handleOpenQuickCommandsSetting}
|
|
234
|
-
icon={<EditOutlined />}
|
|
235
|
-
/>
|
|
236
|
-
<Button
|
|
237
|
-
onClick={handleClose}
|
|
238
|
-
icon={<CloseCircleOutlined />}
|
|
239
|
-
/>
|
|
240
|
-
</Space.Compact>
|
|
241
|
-
</Flex>
|
|
242
|
-
<div className={cls}>
|
|
243
|
-
{sorted.map(renderItem)}
|
|
244
|
-
{
|
|
245
|
-
!sorted.length && renderNoCmd()
|
|
246
|
-
}
|
|
247
|
-
</div>
|
|
248
|
-
</div>
|
|
363
|
+
{content}
|
|
249
364
|
</div>
|
|
250
365
|
)
|
|
251
|
-
}
|
|
366
|
+
})
|
|
@@ -4,12 +4,16 @@ import {
|
|
|
4
4
|
Select,
|
|
5
5
|
Input
|
|
6
6
|
} from 'antd'
|
|
7
|
+
import { RobotOutlined } from '@ant-design/icons'
|
|
7
8
|
import message from '../common/message'
|
|
8
9
|
import SwitchLabel from '../common/switch'
|
|
9
10
|
import { useState } from 'react'
|
|
10
11
|
import generate from '../../common/uid'
|
|
11
12
|
import InputAutoFocus from '../common/input-auto-focus'
|
|
12
13
|
import renderQm from './quick-commands-list-form'
|
|
14
|
+
import QuickCommandAiEditor from './quick-command-ai-editor'
|
|
15
|
+
import ResponsiveTabs from '../common/responsive-tabs'
|
|
16
|
+
import { isAIDisabled } from '../../common/ai-feature'
|
|
13
17
|
import ShortcutEdit from '../shortcuts/shortcut-editor'
|
|
14
18
|
import { getKeysTakenData } from '../shortcuts/shortcut-utils'
|
|
15
19
|
import deepCopy from 'json-deep-copy'
|
|
@@ -18,6 +22,8 @@ import HelpIcon from '../common/help-icon'
|
|
|
18
22
|
|
|
19
23
|
const FormItem = Form.Item
|
|
20
24
|
const { Option } = Select
|
|
25
|
+
const manualTab = 'qm-form-manual'
|
|
26
|
+
const aiTab = 'qm-form-ai'
|
|
21
27
|
const e = window.translate
|
|
22
28
|
|
|
23
29
|
export default function QuickCommandForm (props) {
|
|
@@ -25,6 +31,7 @@ export default function QuickCommandForm (props) {
|
|
|
25
31
|
const { store, formData } = props
|
|
26
32
|
const { quickCommandTags = [] } = store
|
|
27
33
|
const [shortcut, setShortcut] = useState(formData.shortcut || '')
|
|
34
|
+
const [tab, setTab] = useState(manualTab)
|
|
28
35
|
const uid = formData.id || generate()
|
|
29
36
|
const updateConfig = (name, value) => {
|
|
30
37
|
form.setFieldsValue({
|
|
@@ -86,6 +93,22 @@ export default function QuickCommandForm (props) {
|
|
|
86
93
|
message.success(e('saved'))
|
|
87
94
|
props.onSaved?.()
|
|
88
95
|
}
|
|
96
|
+
|
|
97
|
+
// AI generated quick command: write it into the same form (so the user can
|
|
98
|
+
// review/edit it) and jump back to the manual tab, where the save button is
|
|
99
|
+
function handleAiGenerated (data) {
|
|
100
|
+
const update = {
|
|
101
|
+
name: data.name,
|
|
102
|
+
commands: data.commands,
|
|
103
|
+
inputOnly: data.inputOnly
|
|
104
|
+
}
|
|
105
|
+
if (data.labels.length) {
|
|
106
|
+
update.labels = data.labels
|
|
107
|
+
}
|
|
108
|
+
form.setFieldsValue(update)
|
|
109
|
+
setTab(manualTab)
|
|
110
|
+
}
|
|
111
|
+
|
|
89
112
|
const initialValues = formData
|
|
90
113
|
if (!initialValues.labels) {
|
|
91
114
|
initialValues.labels = []
|
|
@@ -112,6 +135,111 @@ export default function QuickCommandForm (props) {
|
|
|
112
135
|
return `{{${t}}}`
|
|
113
136
|
}).join(', ')
|
|
114
137
|
const wiki = 'https://github.com/electerm/electerm/wiki/quick-command-templates'
|
|
138
|
+
const aiDisabled = isAIDisabled()
|
|
139
|
+
// renderQm() uses hooks internally, so it must run on every render
|
|
140
|
+
const qmItem = renderQm(form)
|
|
141
|
+
const manualPane = (
|
|
142
|
+
<>
|
|
143
|
+
<FormItem
|
|
144
|
+
label={e('quickCommandName')}
|
|
145
|
+
rules={[{
|
|
146
|
+
max: 60, message: '60 chars max'
|
|
147
|
+
}, {
|
|
148
|
+
required: true, message: 'Name required'
|
|
149
|
+
}]}
|
|
150
|
+
hasFeedback
|
|
151
|
+
name='name'
|
|
152
|
+
>
|
|
153
|
+
<InputAutoFocus />
|
|
154
|
+
</FormItem>
|
|
155
|
+
{qmItem}
|
|
156
|
+
<FormItem
|
|
157
|
+
name='labels'
|
|
158
|
+
label={e('label')}
|
|
159
|
+
>
|
|
160
|
+
<Select
|
|
161
|
+
mode='tags'
|
|
162
|
+
>
|
|
163
|
+
{
|
|
164
|
+
quickCommandTags.map(q => {
|
|
165
|
+
return (
|
|
166
|
+
<Option value={q} key={'qmt-' + q}>
|
|
167
|
+
{q}
|
|
168
|
+
</Option>
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
</Select>
|
|
173
|
+
</FormItem>
|
|
174
|
+
<FormItem
|
|
175
|
+
label={e('settingShortcuts')}
|
|
176
|
+
name='shortcut'
|
|
177
|
+
>
|
|
178
|
+
<div>
|
|
179
|
+
<Input className='hide' />
|
|
180
|
+
<ShortcutEdit
|
|
181
|
+
{...editorProps}
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
</FormItem>
|
|
185
|
+
<FormItem
|
|
186
|
+
label={e('inputOnly')}
|
|
187
|
+
name='inputOnly'
|
|
188
|
+
valuePropName='checked'
|
|
189
|
+
>
|
|
190
|
+
<SwitchLabel />
|
|
191
|
+
</FormItem>
|
|
192
|
+
<FormItem>
|
|
193
|
+
<p>
|
|
194
|
+
<Button
|
|
195
|
+
type='primary'
|
|
196
|
+
htmlType='submit'
|
|
197
|
+
>{e('save')}
|
|
198
|
+
</Button>
|
|
199
|
+
</p>
|
|
200
|
+
</FormItem>
|
|
201
|
+
<p>
|
|
202
|
+
<b className='mg1r'>{e('templates')}:</b>
|
|
203
|
+
<span className='mg1r'>{templatesStr}</span>
|
|
204
|
+
<HelpIcon
|
|
205
|
+
link={wiki}
|
|
206
|
+
/>
|
|
207
|
+
</p>
|
|
208
|
+
</>
|
|
209
|
+
)
|
|
210
|
+
const tabsProps = {
|
|
211
|
+
activeKey: tab,
|
|
212
|
+
onChange: setTab,
|
|
213
|
+
className: 'qm-form-tabs',
|
|
214
|
+
size: 'small',
|
|
215
|
+
items: [
|
|
216
|
+
{
|
|
217
|
+
key: manualTab,
|
|
218
|
+
label: e('quickCommand')
|
|
219
|
+
},
|
|
220
|
+
...(!aiDisabled
|
|
221
|
+
? [{
|
|
222
|
+
key: aiTab,
|
|
223
|
+
label: (
|
|
224
|
+
<span className='qm-form-tab-ai'>
|
|
225
|
+
<RobotOutlined className='mg1r' />
|
|
226
|
+
AI
|
|
227
|
+
</span>
|
|
228
|
+
)
|
|
229
|
+
}]
|
|
230
|
+
: [])
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
const aiPane = (
|
|
234
|
+
<>
|
|
235
|
+
<ResponsiveTabs {...tabsProps} />
|
|
236
|
+
{
|
|
237
|
+
tab === aiTab
|
|
238
|
+
? <QuickCommandAiEditor onChange={handleAiGenerated} />
|
|
239
|
+
: manualPane
|
|
240
|
+
}
|
|
241
|
+
</>
|
|
242
|
+
)
|
|
115
243
|
return (
|
|
116
244
|
<>
|
|
117
245
|
<Form
|
|
@@ -121,71 +249,7 @@ export default function QuickCommandForm (props) {
|
|
|
121
249
|
layout='vertical'
|
|
122
250
|
initialValues={initialValues}
|
|
123
251
|
>
|
|
124
|
-
|
|
125
|
-
label={e('quickCommandName')}
|
|
126
|
-
rules={[{
|
|
127
|
-
max: 60, message: '60 chars max'
|
|
128
|
-
}, {
|
|
129
|
-
required: true, message: 'Name required'
|
|
130
|
-
}]}
|
|
131
|
-
hasFeedback
|
|
132
|
-
name='name'
|
|
133
|
-
>
|
|
134
|
-
<InputAutoFocus />
|
|
135
|
-
</FormItem>
|
|
136
|
-
{renderQm(form)}
|
|
137
|
-
<FormItem
|
|
138
|
-
name='labels'
|
|
139
|
-
label={e('label')}
|
|
140
|
-
>
|
|
141
|
-
<Select
|
|
142
|
-
mode='tags'
|
|
143
|
-
>
|
|
144
|
-
{
|
|
145
|
-
quickCommandTags.map(q => {
|
|
146
|
-
return (
|
|
147
|
-
<Option value={q} key={'qmt-' + q}>
|
|
148
|
-
{q}
|
|
149
|
-
</Option>
|
|
150
|
-
)
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
</Select>
|
|
154
|
-
</FormItem>
|
|
155
|
-
<FormItem
|
|
156
|
-
label={e('settingShortcuts')}
|
|
157
|
-
name='shortcut'
|
|
158
|
-
>
|
|
159
|
-
<div>
|
|
160
|
-
<Input className='hide' />
|
|
161
|
-
<ShortcutEdit
|
|
162
|
-
{...editorProps}
|
|
163
|
-
/>
|
|
164
|
-
</div>
|
|
165
|
-
</FormItem>
|
|
166
|
-
<FormItem
|
|
167
|
-
label={e('inputOnly')}
|
|
168
|
-
name='inputOnly'
|
|
169
|
-
valuePropName='checked'
|
|
170
|
-
>
|
|
171
|
-
<SwitchLabel />
|
|
172
|
-
</FormItem>
|
|
173
|
-
<FormItem>
|
|
174
|
-
<p>
|
|
175
|
-
<Button
|
|
176
|
-
type='primary'
|
|
177
|
-
htmlType='submit'
|
|
178
|
-
>{e('save')}
|
|
179
|
-
</Button>
|
|
180
|
-
</p>
|
|
181
|
-
</FormItem>
|
|
182
|
-
<p>
|
|
183
|
-
<b className='mg1r'>{e('templates')}:</b>
|
|
184
|
-
<span className='mg1r'>{templatesStr}</span>
|
|
185
|
-
<HelpIcon
|
|
186
|
-
link={wiki}
|
|
187
|
-
/>
|
|
188
|
-
</p>
|
|
252
|
+
{aiDisabled ? manualPane : aiPane}
|
|
189
253
|
</Form>
|
|
190
254
|
</>
|
|
191
255
|
)
|
|
@@ -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
|
|
@@ -9,7 +9,7 @@ import createName, { createTitleTag } from '../../common/create-title'
|
|
|
9
9
|
import classnames from 'classnames'
|
|
10
10
|
import { noop } from 'lodash-es'
|
|
11
11
|
import highlight from '../common/highlight'
|
|
12
|
-
import {
|
|
12
|
+
import { staticNewItemTabs, undeletableIds } from '../../common/constants'
|
|
13
13
|
import getInitItem from '../../common/init-setting-item'
|
|
14
14
|
|
|
15
15
|
const e = window.translate
|
|
@@ -64,7 +64,7 @@ export default class ItemList extends React.PureComponent {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
renderDelBtn = item => {
|
|
67
|
-
if (!item.id ||
|
|
67
|
+
if (!item.id || undeletableIds.has(item.id)) {
|
|
68
68
|
return null
|
|
69
69
|
}
|
|
70
70
|
const { shouldConfirmDel } = this.props
|
|
@@ -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
|
]
|
|
@@ -2,13 +2,23 @@
|
|
|
2
2
|
position absolute
|
|
3
3
|
right 0
|
|
4
4
|
top 36px
|
|
5
|
-
|
|
5
|
+
// Unpinned is an overlay, so stop above the footer: the footer is app chrome
|
|
6
|
+
// and holds the very icon that toggles this panel, so a full-height overlay
|
|
7
|
+
// would bury its own trigger. 36px is footerHeight (common/constants.js).
|
|
8
|
+
// Two cases move or remove the footer, and are handled where their sibling
|
|
9
|
+
// rules already live — see shortcut-bar.styl (.shortcut-bar-on lifts the
|
|
10
|
+
// footer) and term-fullscreen.styl (rdp/vnc/spice fullscreen hides it).
|
|
11
|
+
bottom 36px
|
|
6
12
|
z-index 200
|
|
7
13
|
background var(--main)
|
|
8
14
|
color var(--text)
|
|
9
15
|
border-left 1px solid var(--main-darker)
|
|
16
|
+
// Pinned is a dock, not an overlay: it owns the whole right column, top to
|
|
17
|
+
// bottom. The layout squeezes the terminal out of the way instead
|
|
18
|
+
// (layout.jsx reserves rightPanelWidth), so covering the footer is intended.
|
|
10
19
|
&.right-side-panel-pinned
|
|
11
20
|
top 0
|
|
21
|
+
bottom 0
|
|
12
22
|
|
|
13
23
|
.drag-handle
|
|
14
24
|
left 0
|