@electerm/electerm-react 1.51.21 → 1.60.6

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.
@@ -26,37 +26,23 @@ export default class BatchInput extends Component {
26
26
  super(props)
27
27
  this.state = {
28
28
  cmd: '',
29
- selectedTabIds: [props.activeTabId],
30
29
  open: false,
31
30
  enter: false
32
31
  }
33
32
  }
34
33
 
35
- componentDidUpdate (prevProps) {
36
- if (prevProps.activeTabId !== this.props.activeTabId) {
37
- this.setState(prevState => {
38
- const newSelectedTabIds = prevState.selectedTabIds.filter(
39
- id => id !== this.props.activeTabId
40
- )
41
- newSelectedTabIds.unshift(this.props.activeTabId)
42
- return {
43
- selectedTabIds: newSelectedTabIds
44
- }
45
- })
46
- }
47
- }
48
-
49
34
  componentWillUnmount () {
50
35
  clearTimeout(this.timer)
51
36
  }
52
37
 
53
38
  handleEnter = (e) => {
54
- const { cmd, selectedTabIds } = this.state
39
+ const { batchInputSelectedTabIds } = window.store
40
+ const { cmd } = this.state
55
41
  if (!cmd.trim()) {
56
42
  return
57
43
  }
58
44
  window.store.addBatchInput(cmd)
59
- this.props.input(cmd, selectedTabIds)
45
+ this.props.input(cmd, Array.from(batchInputSelectedTabIds))
60
46
  this.setState({
61
47
  cmd: '',
62
48
  open: false
@@ -64,43 +50,6 @@ export default class BatchInput extends Component {
64
50
  e.stopPropagation()
65
51
  }
66
52
 
67
- onSelectAll = () => {
68
- this.setState({
69
- selectedTabIds: this.getTabs().map(tab => tab.id)
70
- })
71
- }
72
-
73
- onSelectNone = () => {
74
- this.setState({
75
- selectedTabIds: [this.props.activeTabId]
76
- })
77
- }
78
-
79
- filterValidTabIds = (tabIds) => {
80
- return tabIds.filter(id => {
81
- return this.props.tabs.some(tab => tab.id === id)
82
- })
83
- }
84
-
85
- onSelect = (id) => {
86
- this.setState(prevState => {
87
- const selectedTabIds = prevState.selectedTabIds.includes(id)
88
- ? prevState.selectedTabIds.filter(tabId => tabId !== id)
89
- : [...prevState.selectedTabIds, id]
90
-
91
- // Ensure at least the current tab is selected
92
- if (selectedTabIds.length === 0) {
93
- return {
94
- selectedTabIds: [this.props.activeTabId]
95
- }
96
- }
97
-
98
- return {
99
- selectedTabIds
100
- }
101
- })
102
- }
103
-
104
53
  handleChange = (v = '') => {
105
54
  let vv = v.replace(/^\d+:/, '').replace(/\n$/, '')
106
55
  if (vv === batchInputLsKey) {
@@ -118,15 +67,9 @@ export default class BatchInput extends Component {
118
67
 
119
68
  handleClick = () => {
120
69
  this.setState({
121
- open: true,
122
- selectedTabIds: this.filterValidTabIds(this.state.selectedTabIds)
123
- })
124
- }
125
-
126
- handleChangeAll = toAll => {
127
- this.setState({
128
- toAll
70
+ open: true
129
71
  })
72
+ window.store.filterBatchInputSelectedTabIds()
130
73
  }
131
74
 
132
75
  handleBlur = () => {
@@ -192,7 +135,10 @@ export default class BatchInput extends Component {
192
135
  }
193
136
 
194
137
  render () {
195
- const { cmd, open, selectedTabIds, enter } = this.state
138
+ const { cmd, open, enter } = this.state
139
+ const {
140
+ batchInputSelectedTabIds
141
+ } = this.props
196
142
  const opts = {
197
143
  options: this.buildOptions(),
198
144
  placeholder: e('batchInput'),
@@ -217,10 +163,10 @@ export default class BatchInput extends Component {
217
163
  const tabSelectProps = {
218
164
  activeTabId: this.props.activeTabId,
219
165
  tabs: this.getTabs(),
220
- selectedTabIds,
221
- onSelectAll: this.onSelectAll,
222
- onSelectNone: this.onSelectNone,
223
- onSelect: this.onSelect
166
+ selectedTabIds: batchInputSelectedTabIds,
167
+ onSelectAll: window.store.selectAllBatchInputTabs,
168
+ onSelectNone: window.store.selectNoneBatchInputTabs,
169
+ onSelect: window.store.onSelectBatchInputSelectedTabId
224
170
  }
225
171
  return (
226
172
  <span
@@ -49,11 +49,13 @@ export default auto(function FooterEntry (props) {
49
49
  }
50
50
 
51
51
  function renderBatchInputs () {
52
+ const { store } = props
52
53
  const batchProps = {
53
54
  input: batchInput,
54
- batchInputs: props.store.batchInputs,
55
- tabs: props.store.tabs,
56
- activeTabId: props.store.activeTabId
55
+ tabs: store.tabs,
56
+ batchInputs: store.batchInputs,
57
+ batchInputSelectedTabIds: store.batchInputSelectedTabIds,
58
+ activeTabId: store.activeTabId
57
59
  }
58
60
  return (
59
61
  <div className='terminal-footer-unit terminal-footer-center'>
@@ -72,6 +74,19 @@ export default auto(function FooterEntry (props) {
72
74
  )
73
75
  }
74
76
 
77
+ function renderAIIcon () {
78
+ return (
79
+ <div className='terminal-footer-unit terminal-footer-ai'>
80
+ <span
81
+ className='ai-icon'
82
+ onClick={window.store.handleOpenAIPanel}
83
+ >
84
+ AI
85
+ </span>
86
+ </div>
87
+ )
88
+ }
89
+
75
90
  function renderEncodingInfo () {
76
91
  const selectProps = {
77
92
  style: {
@@ -148,6 +163,7 @@ export default auto(function FooterEntry (props) {
148
163
  return (
149
164
  <div {...sideProps}>
150
165
  <div className='terminal-footer-flex'>
166
+ {renderAIIcon()}
151
167
  {renderQuickCommands()}
152
168
  {renderBatchInputs()}
153
169
  {renderEncodingInfo()}
@@ -14,6 +14,10 @@
14
14
  .qm-wrap-tooltip
15
15
  left 343px
16
16
 
17
+ .ai-icon
18
+ display inline-block
19
+ padding 0 6px
20
+ cursor pointer
17
21
 
18
22
  .terminal-footer-flex
19
23
  display flex
@@ -14,7 +14,7 @@ export default function TabSelect (props) {
14
14
  const itemProps = {
15
15
  tab,
16
16
  selected,
17
- onSelect: props.onSelect,
17
+ onSelect: window.store.onSelectBatchInputSelectedTabId,
18
18
  id: tab.id,
19
19
  isCurrent: tab.id === activeTabId
20
20
  }
@@ -26,18 +26,24 @@ export default function TabSelect (props) {
26
26
  )
27
27
  })
28
28
  }
29
+ function onSelectAll () {
30
+ window.store.selectAllBatchInputTabs()
31
+ }
32
+ function onSelectNone () {
33
+ window.store.selectNoneBatchInputTabs()
34
+ }
29
35
  function renderBtns () {
30
36
  return (
31
37
  <div className='pd1t pd2b font12'>
32
38
  <span
33
39
  className='mg1r pointer'
34
- onClick={props.onSelectAll}
40
+ onClick={onSelectAll}
35
41
  >
36
42
  All
37
43
  </span>
38
44
  <span
39
45
  className='pointer'
40
- onClick={props.onSelectNone}
46
+ onClick={onSelectNone}
41
47
  >
42
48
  None
43
49
  </span>
@@ -61,12 +61,13 @@ export default auto(function Layout (props) {
61
61
  width,
62
62
  pinnedQuickCommandBar,
63
63
  leftSidebarWidth,
64
- infoPanelPinned,
65
- pinned,
66
- rightSidebarWidth
64
+ rightPanelVisible,
65
+ rightPanelPinned,
66
+ rightPanelWidth,
67
+ pinned
67
68
  } = props.store
68
69
  const l = pinned ? leftSidebarWidth : 0
69
- const r = infoPanelPinned ? rightSidebarWidth : 0
70
+ const r = rightPanelPinned && rightPanelVisible ? rightPanelWidth : 0
70
71
  const w = width - l - r - 42
71
72
  const h = height - footerHeight - (pinnedQuickCommandBar ? quickCommandBoxHeight : 0)
72
73
  return layoutAlg(layout, w, h)
@@ -29,6 +29,7 @@ import RightSidePanel from '../side-panel-r/side-panel-r'
29
29
  import ConnectionHoppingWarning from './connection-hopping-warnning'
30
30
  import SshConfigLoadNotify from '../ssh-config/ssh-config-load-notify'
31
31
  import LoadSshConfigs from '../ssh-config/load-ssh-configs'
32
+ import AIChat from '../ai/ai-chat'
32
33
  import { pick } from 'lodash-es'
33
34
  import deepCopy from 'json-deep-copy'
34
35
  import './wrapper.styl'
@@ -81,7 +82,7 @@ export default auto(function Index (props) {
81
82
  store.isSecondInstance = window.pre.runSync('isSecondInstance')
82
83
  store.initData()
83
84
  store.checkForDbUpgrade()
84
- window.pre.runGlobalAsync('registerDeepLink')
85
+ // window.pre.runGlobalAsync('registerDeepLink')
85
86
  }, [])
86
87
 
87
88
  const { store } = props
@@ -213,6 +214,17 @@ export default auto(function Index (props) {
213
214
  hasOldConnectionHoppingBookmark: store.hasOldConnectionHoppingBookmark,
214
215
  configLoaded
215
216
  }
217
+ const aiChatProps = {
218
+ aiChatHistory: store.aiChatHistory,
219
+ config,
220
+ selectedTabIds: store.batchInputSelectedTabIds,
221
+ tabs: store.getTabs(),
222
+ activeTabId: store.activeTabId,
223
+ showAIConfig: store.showAIConfig
224
+ }
225
+ const rightPanelContent = store.rightPanelTab === 'ai'
226
+ ? <AIChat {...aiChatProps} />
227
+ : <TerminalInfo {...terminalInfoProps} />
216
228
  return (
217
229
  <ConfigProvider
218
230
  theme={uiThemeConfig}
@@ -269,7 +281,7 @@ export default auto(function Index (props) {
269
281
  <Resolutions {...resProps} />
270
282
  <InfoModal {...infoModalProps} />
271
283
  <RightSidePanel {...rightPanelProps}>
272
- <TerminalInfo {...terminalInfoProps} />
284
+ {rightPanelContent}
273
285
  </RightSidePanel>
274
286
  <SshConfigLoadNotify {...sshConfigProps} />
275
287
  <LoadSshConfigs
@@ -9,8 +9,10 @@ import { throttle } from 'lodash-es'
9
9
 
10
10
  class ShortcutControl extends React.PureComponent {
11
11
  componentDidMount () {
12
- window.addEventListener('keydown', this.handleKeyboardEvent.bind(this))
13
- window.addEventListener('mousewheel', this.handleKeyboardEvent.bind(this))
12
+ const onEvent = this.handleKeyboardEvent.bind(this)
13
+ window.addEventListener('keydown', onEvent)
14
+ window.addEventListener('mousedown', onEvent)
15
+ window.addEventListener('mousewheel', onEvent)
14
16
  }
15
17
 
16
18
  closeCurrentTabShortcut = throttle((e) => {
@@ -21,6 +23,19 @@ class ShortcutControl extends React.PureComponent {
21
23
  }
22
24
  }, 500)
23
25
 
26
+ mouseWheelDownCloseTabShortcut = throttle((e) => {
27
+ e.stopPropagation()
28
+
29
+ // Check if the event target is within a .tab element
30
+ const tabElement = e.target.closest('.tab')
31
+ if (tabElement) {
32
+ const tabId = tabElement.getAttribute('data-id')
33
+ if (tabId) {
34
+ window.store.delTab(tabId)
35
+ }
36
+ }
37
+ }, 500)
38
+
24
39
  reloadCurrentTabShortcut = throttle((e) => {
25
40
  e.stopPropagation()
26
41
  window.store.reloadTab()
@@ -53,12 +53,14 @@ export function shortcutExtend (Cls) {
53
53
  metaKey,
54
54
  altKey,
55
55
  wheelDeltaY,
56
+ button,
56
57
  type,
57
58
  key
58
59
  } = event
59
60
  if (this.cmdAddon) {
60
61
  this.cmdAddon.handleKey(event)
61
62
  }
63
+
62
64
  if (
63
65
  this.term &&
64
66
  key === 'Backspace' &&
@@ -66,6 +68,7 @@ export function shortcutExtend (Cls) {
66
68
  !altKey &&
67
69
  !ctrlKey
68
70
  ) {
71
+ console.log('handleKeyboardEvent: Handling Backspace key')
69
72
  this.props.onDelKeyPressed()
70
73
  const delKey = this.props.config.backspaceMode === '^?' ? 8 : 127
71
74
  const altDelDelKey = delKey === 8 ? 127 : 8
@@ -78,6 +81,7 @@ export function shortcutExtend (Cls) {
78
81
  ) {
79
82
  return true
80
83
  }
84
+
81
85
  if (
82
86
  this.term &&
83
87
  key === 'c' &&
@@ -89,31 +93,42 @@ export function shortcutExtend (Cls) {
89
93
  ) {
90
94
  this.onZmodemEnd()
91
95
  }
92
- const codeName = event instanceof window.WheelEvent
93
- ? (wheelDeltaY > 0 ? 'mouseWheelUp' : 'mouseWheelDown')
94
- : code
96
+
97
+ let codeName
98
+ if (type === 'mousedown' && button === 1) {
99
+ codeName = 'mouseWheel'
100
+ } else {
101
+ codeName = event instanceof window.WheelEvent
102
+ ? (wheelDeltaY > 0 ? 'mouseWheelUp' : 'mouseWheelDown')
103
+ : code
104
+ }
105
+
95
106
  const codeK = getKeyCharacter(codeName)
96
- const noControlKey = !ctrlKey && !shiftKey && !metaKey && !altKey
107
+
108
+ const noControlKey = type !== 'mousedown' && !ctrlKey && !shiftKey && !metaKey && !altKey
97
109
  if (noControlKey) {
98
110
  return
99
111
  }
100
- const r = (ctrlKey ? 'ctrl+' : '') +
112
+ const r = codeName === 'mouseWheel'
113
+ ? 'mouseWheel'
114
+ : (ctrlKey ? 'ctrl+' : '') +
101
115
  (metaKey ? 'meta+' : '') +
102
116
  (shiftKey ? 'shift+' : '') +
103
117
  (altKey ? 'alt+' : '') +
104
118
  codeK.toLowerCase()
119
+
105
120
  const shortcutsConfig = buildConfig(this.props.config, d => !d.hidden)
106
121
  const keys = Object.keys(shortcutsConfig)
107
122
  const len = keys.length
123
+
108
124
  if (this.term) {
109
125
  const qmMatch = window.store.quickCommands.find(d => d.shortcut === r)
110
126
  if (qmMatch) {
111
- window.store.runQuickCommandItem(
112
- qmMatch.id
113
- )
127
+ window.store.runQuickCommandItem(qmMatch.id)
114
128
  return false
115
129
  }
116
130
  }
131
+
117
132
  for (let i = 0; i < len; i++) {
118
133
  const k = keys[i]
119
134
  const conf = shortcutsConfig[k]
@@ -128,6 +143,7 @@ export function shortcutExtend (Cls) {
128
143
  }
129
144
  }
130
145
  }
146
+
131
147
  return !!this.term
132
148
  }
133
149
  return Cls
@@ -5,6 +5,12 @@ export default () => {
5
5
  shortcut: 'alt+w',
6
6
  shortcutMac: 'alt+w'
7
7
  },
8
+ {
9
+ name: 'app_mouseWheelDownCloseTab',
10
+ shortcut: 'mouseWheel',
11
+ shortcutMac: 'mouseWheel',
12
+ readonly: true
13
+ },
8
14
  {
9
15
  name: 'app_reloadCurrentTab',
10
16
  shortcut: 'alt+r',
@@ -0,0 +1,35 @@
1
+ import { useState, useEffect } from 'react'
2
+ import {
3
+ ClockCircleOutlined
4
+ } from '@ant-design/icons'
5
+ import dayjs from 'dayjs'
6
+ import duration from 'dayjs/plugin/duration'
7
+
8
+ dayjs.extend(duration)
9
+
10
+ const e = window.translate
11
+
12
+ export default function RunningTime () {
13
+ const [runningTime, setRunningTime] = useState(0)
14
+
15
+ useEffect(() => {
16
+ const startTime = window.pre.runSync('getInitTime')
17
+
18
+ const timer = setInterval(() => {
19
+ const currentTime = Date.now()
20
+ const diff = Math.floor((currentTime - startTime) / 1000)
21
+ setRunningTime(diff)
22
+ }, 1000)
23
+
24
+ return () => clearInterval(timer)
25
+ }, [])
26
+ const formatRunningTime = (seconds) => {
27
+ return dayjs.duration(seconds, 'seconds').format('HH:mm:ss')
28
+ }
29
+ return (
30
+ <p className='mg2b'>
31
+ <ClockCircleOutlined /> <b>{e('runningTime')} ➾</b>
32
+ <span className='mg1l'>{formatRunningTime(runningTime)}</span>
33
+ </p>
34
+ )
35
+ }
@@ -15,6 +15,7 @@ import {
15
15
  import { Modal, Tabs, Button } from 'antd'
16
16
  import Link from '../common/external-link'
17
17
  import LogoElem from '../common/logo-elem'
18
+ import RunningTime from './app-running-time'
18
19
 
19
20
  import {
20
21
  packInfo,
@@ -121,6 +122,7 @@ export default memo(function InfoModal (props) {
121
122
  <div>
122
123
  <LogoElem />
123
124
  <p className='mg2b'>{e('desc')}</p>
125
+ <RunningTime />
124
126
  <p className='mg1b'>
125
127
  <UserOutlined /> <b className='mg1r'>{e('author')} ➾</b>
126
128
  <Link to={authorUrl} className='mg1l'>
@@ -18,7 +18,7 @@ export default function AppDrag (props) {
18
18
  }
19
19
 
20
20
  function onMouseDown (e) {
21
- e.stopPropagation()
21
+ // e.stopPropagation()
22
22
  if (canOperate(e)) {
23
23
  window.pre.runSync('windowMove', true)
24
24
  }
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React from 'react'
6
6
  import runIdle from '../../common/run-idle'
7
- import { debounce } from 'lodash-es'
7
+ import { throttle } from 'lodash-es'
8
8
  import TabTitle from './tab-title'
9
9
  import {
10
10
  CodeFilled,
@@ -49,6 +49,7 @@ export default class Tabs extends React.Component {
49
49
  constructor (props) {
50
50
  super(props)
51
51
  this.tabsRef = React.createRef()
52
+ this.domRef = React.createRef()
52
53
  this.state = {
53
54
  overflow: false,
54
55
  receiveDataTabId: '',
@@ -60,13 +61,13 @@ export default class Tabs extends React.Component {
60
61
  }
61
62
 
62
63
  componentDidMount () {
63
- this.domRef = React.createRef()
64
64
  const {
65
65
  tabsRef
66
66
  } = this
67
67
  window.addEventListener('message', this.onEvent)
68
- tabsRef.current.addEventListener('mousedown', this.handleClickEvent)
69
- tabsRef.current.addEventListener('mousewheel', this.handleWheelEvent)
68
+ tabsRef.current.addEventListener('wheel', this.handleWheelEvent, {
69
+ passive: false
70
+ })
70
71
  }
71
72
 
72
73
  componentDidUpdate (prevProps) {
@@ -198,16 +199,6 @@ export default class Tabs extends React.Component {
198
199
  return inner ? inner.clientWidth : 0
199
200
  }
200
201
 
201
- handleClickEvent = (e) => {
202
- if (e.button === 1) {
203
- const p = findParentBySel(e.target, '.tab')
204
- if (p) {
205
- const id = p.dataset.id
206
- this.props.delTab(id)
207
- }
208
- }
209
- }
210
-
211
202
  handleAdd = e => {
212
203
  if (!e.target.className.includes('tabs-wrapper')) {
213
204
  return
@@ -230,7 +221,7 @@ export default class Tabs extends React.Component {
230
221
  const index = tabs.findIndex(t => t.id === currentBatchTabId)
231
222
  const tabsDomWith = Array.from(
232
223
  document.querySelectorAll(`.v${batch + 1} .tab`)
233
- ).slice(0, index + 1).reduce((prev, c) => {
224
+ ).slice(0, index + 2).reduce((prev, c) => {
234
225
  return prev + c.clientWidth
235
226
  }, 0)
236
227
  const w = (index + 1) * tabMargin + tabsDomWith
@@ -262,7 +253,7 @@ export default class Tabs extends React.Component {
262
253
  this.domRef.current.scrollTo({ left: scrollLeft, behavior: 'smooth' })
263
254
  }
264
255
 
265
- handleWheelEvent = debounce((e) => {
256
+ handleWheelEvent = throttle((e) => {
266
257
  if (this.isOverflow()) {
267
258
  if (e.deltaY < 0) {
268
259
  this.handleScrollLeft()
@@ -270,7 +261,7 @@ export default class Tabs extends React.Component {
270
261
  this.handleScrollRight()
271
262
  }
272
263
  }
273
- }, 100)
264
+ }, 100, { leading: true, trailing: true })
274
265
 
275
266
  handleClickMenu = ({ key }) => {
276
267
  const id = key.split('##')[1]
@@ -16,12 +16,14 @@ import {
16
16
  terminalActions
17
17
  } from '../common/constants'
18
18
  import * as ls from '../common/safe-local-storage'
19
+ import { action } from 'manate'
19
20
 
20
21
  const e = window.translate
22
+ const { assign } = Object
21
23
 
22
24
  export default Store => {
23
25
  Store.prototype.storeAssign = function (updates) {
24
- Object.assign(this, updates)
26
+ assign(window.store, updates)
25
27
  }
26
28
 
27
29
  Store.prototype.onError = function (e) {
@@ -47,13 +49,23 @@ export default Store => {
47
49
  })
48
50
  }
49
51
 
50
- Store.prototype.openInfoPanel = function () {
52
+ Store.prototype.openInfoPanel = action(function () {
51
53
  const { store } = window
52
54
  store.rightPanelVisible = true
55
+ store.rightPanelTab = 'info'
56
+ store.openInfoPanelAction()
57
+ })
58
+
59
+ Store.prototype.openInfoPanelAction = action(function () {
60
+ const { store } = window
53
61
  postMessage({
54
62
  action: terminalActions.showInfoPanel,
55
63
  activeTabId: store.activeTabId
56
64
  })
65
+ })
66
+
67
+ Store.prototype.toggleAIConfig = function () {
68
+ window.store.showAIConfig = !window.store.showAIConfig
57
69
  }
58
70
 
59
71
  Store.prototype.onResize = debounce(async function () {
@@ -262,4 +274,27 @@ export default Store => {
262
274
  }
263
275
  return window.store.applyProfile(tab)
264
276
  }
277
+
278
+ Store.prototype.handleOpenAIPanel = function () {
279
+ const { store } = window
280
+ store.rightPanelVisible = true
281
+ store.rightPanelTab = 'ai'
282
+ }
283
+
284
+ Store.prototype.runCommandInTerminal = function (cmd) {
285
+ postMessage({
286
+ action: terminalActions.quickCommand,
287
+ cmd,
288
+ selectedTabIds: window.store.batchInputSelectedTabIds
289
+ })
290
+ }
291
+
292
+ Store.prototype.removeAiHistory = function (id) {
293
+ const { store } = window
294
+ const index = store.aiChatHistory.findIndex(d => d.id === id)
295
+ if (index === -1) {
296
+ return
297
+ }
298
+ window.store.aiChatHistory.splice(index, 1)
299
+ }
265
300
  }