@electerm/electerm-react 1.50.21 → 1.50.40

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.
Files changed (30) hide show
  1. package/client/components/layout/layout-item.jsx +69 -1
  2. package/client/components/layout/layout.jsx +1 -1
  3. package/client/components/layout/layout.styl +4 -1
  4. package/client/components/layout/layouts.jsx +2 -1
  5. package/client/components/main/css-overwrite.jsx +0 -1
  6. package/client/components/main/custom-css.jsx +1 -1
  7. package/client/components/main/main.jsx +15 -5
  8. package/client/components/main/ui-theme.jsx +1 -1
  9. package/client/components/session/session.jsx +3 -13
  10. package/client/components/session/sessions.jsx +73 -31
  11. package/client/components/setting-sync/setting-sync-form.jsx +1 -1
  12. package/client/components/shortcuts/shortcut-handler.js +3 -0
  13. package/client/components/shortcuts/shortcuts-defaults.js +5 -5
  14. package/client/components/tabs/tab.jsx +13 -26
  15. package/client/components/tabs/tabs.styl +2 -2
  16. package/client/components/terminal/attach-addon-custom.js +1 -1
  17. package/client/components/terminal/command-tracker-addon.js +75 -0
  18. package/client/components/terminal/index.jsx +25 -26
  19. package/client/components/terminal/terminal.styl +4 -1
  20. package/client/components/web/address-bar.jsx +50 -0
  21. package/client/components/web/web-session.jsx +32 -10
  22. package/client/entry/index.jsx +5 -10
  23. package/client/store/db-upgrade.js +1 -1
  24. package/client/store/event.js +1 -0
  25. package/client/store/quick-command.js +1 -1
  26. package/client/store/setting.js +1 -1
  27. package/client/store/tab.js +27 -2
  28. package/client/store/watch.js +0 -5
  29. package/client/views/index.pug +1 -1
  30. package/package.json +1 -1
@@ -1,7 +1,13 @@
1
+ import {
2
+ tabActions
3
+ } from '../../common/constants'
4
+ import postMsg from '../../common/post-msg'
5
+
1
6
  export default function LayoutItem (props) {
2
7
  const {
3
8
  children,
4
9
  i,
10
+ batch,
5
11
  ...itemProps
6
12
  } = props
7
13
  function handleClick (e) {
@@ -12,13 +18,75 @@ export default function LayoutItem (props) {
12
18
  }
13
19
  currentElement = currentElement.parentElement
14
20
  }
21
+ window.store.currentLayoutBatch = i
22
+ }
23
+
24
+ function getDom () {
25
+ return document.querySelector(`.layout-item.v${batch + 1}`)
26
+ }
27
+
28
+ function onDrop (e) {
29
+ e.preventDefault()
30
+ const { target } = e
31
+ if (!target) {
32
+ return
33
+ }
34
+ let currentElement = target
35
+ while (currentElement) {
36
+ if (currentElement.classList && currentElement.classList.contains('tab')) {
37
+ return
38
+ }
39
+ currentElement = currentElement.parentElement
40
+ }
41
+ // debug('target drop', target)
42
+ const fromTab = JSON.parse(e.dataTransfer.getData('fromFile'))
43
+ const onDropElem = getDom
44
+ if (!onDropElem || !fromTab || fromTab.batch === batch) {
45
+ return
46
+ }
47
+ const { store } = window
48
+ const { tabs } = store
49
+ const t = tabs.find(t => t.id === fromTab.id)
50
+ if (!t) {
51
+ return
52
+ }
53
+ t.batch = batch
54
+ postMsg({
55
+ action: tabActions.changeCurrentTabId,
56
+ currentTabId: store.currentTabId
57
+ })
58
+ store.setTabs(tabs)
59
+ clearCls()
60
+ }
61
+
62
+ function clearCls () {
63
+ getDom()?.classList.remove('drag-over')
64
+ }
65
+
66
+ function addCls () {
67
+ getDom()?.classList.add('drag-over')
68
+ }
69
+
70
+ function onDragEnter () {
71
+ addCls()
72
+ }
73
+
74
+ function onDragLeave (e) {
75
+ clearCls()
76
+ }
15
77
 
16
- window.StorageEvent.currentLayoutBatch = i
78
+ function onDragEnd (e) {
79
+ clearCls()
80
+ e && e.dataTransfer && e.dataTransfer.clearData()
17
81
  }
18
82
  return (
19
83
  <div
20
84
  {...itemProps}
21
85
  onClick={handleClick}
86
+ onDragEnter={onDragEnter}
87
+ onDragLeave={onDragLeave}
88
+ onDragEnd={onDragEnd}
89
+ onDrop={onDrop}
22
90
  >
23
91
  {children}
24
92
  </div>
@@ -84,7 +84,7 @@ export default auto(function Layout (props) {
84
84
  batch: i,
85
85
  layout,
86
86
  ...v,
87
- tabs: tabsBatch[i],
87
+ tabs: tabsBatch[i] || [],
88
88
  ...pick(store, [
89
89
  'isMaximized',
90
90
  'config',
@@ -2,4 +2,7 @@
2
2
  .layout-wrap
3
3
  .layout-item
4
4
  position absolute
5
- background main
5
+ background main
6
+ overflow hidden
7
+ &.drag-over
8
+ border 2px solid #08c
@@ -37,7 +37,8 @@ export default memo(function LayoutWrap (props) {
37
37
  const itemProps = {
38
38
  i,
39
39
  style: pixed(wrapStyles[i]),
40
- className: 'layout-item v' + (i + 1)
40
+ className: 'layout-item v' + (i + 1),
41
+ batch: i
41
42
  }
42
43
  return (
43
44
  <LayoutItem
@@ -48,7 +48,6 @@ export default class CssOverwrite extends PureComponent {
48
48
 
49
49
  const styles = [
50
50
  `background-image: ${st}`,
51
- 'background-size: cover',
52
51
  'background-position: center'
53
52
  ]
54
53
 
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { useEffect } from 'react'
6
- import { useDelta, useConditionalEffect } from 'react-delta'
6
+ import { useDelta, useConditionalEffect } from 'react-delta-hooks'
7
7
  import eq from 'fast-deep-equal'
8
8
 
9
9
  const themeDomId = 'custom-css'
@@ -23,20 +23,30 @@ import { isMac, isWin } from '../../common/constants'
23
23
  import TermFullscreenControl from './term-fullscreen-control'
24
24
  import TerminalInfo from '../terminal-info/terminal-info'
25
25
  import { LoadingUI } from './loading'
26
- import { ConfigProvider, notification } from 'antd'
26
+ import { ConfigProvider, notification, message } from 'antd'
27
27
  import InfoModal from '../sidebar/info-modal.jsx'
28
28
  import RightSidePanel from '../side-panel-r/side-panel-r'
29
29
  import { pick } from 'lodash-es'
30
30
  import './wrapper.styl'
31
31
 
32
+ function setupGlobalMessageDismiss () {
33
+ document.addEventListener('click', (event) => {
34
+ const messageElement = event.target.closest('.ant-message-notice')
35
+ if (messageElement) {
36
+ message.destroy()
37
+ }
38
+ })
39
+ }
40
+
32
41
  export default auto(function Index (props) {
33
42
  useEffect(() => {
34
43
  notification.config({
35
44
  placement: 'bottomRight'
36
45
  })
46
+ setupGlobalMessageDismiss()
37
47
  const { store } = props
38
48
  window.addEventListener('resize', store.onResize)
39
- store.onResize()
49
+ setTimeout(store.triggerResize, 200)
40
50
  store.initStoreEvents()
41
51
  const { ipcOnEvent } = window.pre
42
52
  ipcOnEvent('checkupdate', store.onCheckUpdate)
@@ -64,7 +74,7 @@ export default auto(function Index (props) {
64
74
  if (window.et.isWebApp) {
65
75
  window.onbeforeunload = store.beforeExit
66
76
  }
67
- store.isSencondInstance = window.pre.runSync('isSencondInstance')
77
+ store.isSecondInstance = window.pre.runSync('isSecondInstance')
68
78
  store.initData()
69
79
  store.checkForDbUpgrade()
70
80
  window.pre.runGlobalAsync('registerDeepLink')
@@ -76,7 +86,7 @@ export default auto(function Index (props) {
76
86
  config,
77
87
  terminalFullScreen,
78
88
  pinned,
79
- isSencondInstance,
89
+ isSecondInstance,
80
90
  pinnedQuickCommandBar,
81
91
  wsInited,
82
92
  upgradeInfo,
@@ -98,7 +108,7 @@ export default auto(function Index (props) {
98
108
  pinned,
99
109
  'qm-pinned': pinnedQuickCommandBar,
100
110
  'term-fullscreen': terminalFullScreen,
101
- 'is-main': !isSencondInstance
111
+ 'is-main': !isSecondInstance
102
112
  })
103
113
  const ext1 = {
104
114
  className: cls
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { useEffect } from 'react'
6
- import { useDelta, useConditionalEffect } from 'react-delta'
6
+ import { useDelta, useConditionalEffect } from 'react-delta-hooks'
7
7
  import eq from 'fast-deep-equal'
8
8
 
9
9
  const themeDomId = 'theme-css'
@@ -20,7 +20,6 @@ import generate from '../../common/uid'
20
20
  import copy from 'json-deep-copy'
21
21
  import classnames from 'classnames'
22
22
  import {
23
- termControlHeight,
24
23
  paneMap,
25
24
  terminalActions,
26
25
  connectionMap,
@@ -89,15 +88,6 @@ export default class SessionWrapper extends Component {
89
88
  })
90
89
  }
91
90
 
92
- computeHeight = () => {
93
- const {
94
- tabsHeight
95
- } = this.props
96
- return this.props.height -
97
- tabsHeight -
98
- termControlHeight
99
- }
100
-
101
91
  editTab = (up) => {
102
92
  const {
103
93
  tab,
@@ -131,7 +121,7 @@ export default class SessionWrapper extends Component {
131
121
 
132
122
  computePosition = (index) => {
133
123
  const windowWidth = this.getWidth()
134
- const heightAll = this.computeHeight()
124
+ const heightAll = this.props.computeHeight()
135
125
  return {
136
126
  height: heightAll,
137
127
  width: windowWidth,
@@ -196,7 +186,7 @@ export default class SessionWrapper extends Component {
196
186
  const cls = pane === paneMap.terminal
197
187
  ? 'terms-box'
198
188
  : 'terms-box hide'
199
- const height = this.computeHeight()
189
+ const height = this.props.computeHeight()
200
190
  const { tab } = this.props
201
191
  const width = this.getWidth()
202
192
  const themeConfig = copy(window.store.getThemeConfig())
@@ -246,7 +236,7 @@ export default class SessionWrapper extends Component {
246
236
  if (type === terminalRdpType) {
247
237
  return null
248
238
  }
249
- const height = this.computeHeight(pane)
239
+ const height = this.props.computeHeight(pane)
250
240
  const cls = pane === paneMap.terminal
251
241
  ? 'hide'
252
242
  : ''
@@ -8,15 +8,13 @@ import copy from 'json-deep-copy'
8
8
  import wait from '../../common/wait.js'
9
9
  import Tabs from '../tabs/index.jsx'
10
10
  import {
11
- commonActions,
12
11
  tabActions,
13
12
  paneMap,
14
13
  statusMap,
15
- terminalWebType
14
+ terminalWebType,
15
+ termControlHeight
16
16
  } from '../../common/constants.js'
17
17
  import newTerm, { updateCount } from '../../common/new-terminal.js'
18
- import postMsg from '../../common/post-msg.js'
19
-
20
18
  import LogoElem from '../common/logo-elem.jsx'
21
19
  import { Button } from 'antd'
22
20
  import toSimpleObj from '../../common/to-simple-obj.js'
@@ -45,9 +43,7 @@ class Sessions extends Component {
45
43
  this.props.tabs &&
46
44
  !deepEqual(prevProps.tabs, this.props.tabs)
47
45
  ) {
48
- this.setState({
49
- tabs: copy(this.props.tabs)
50
- })
46
+ this.updateTabs(this.props.tabs)
51
47
  }
52
48
  }
53
49
 
@@ -58,11 +54,29 @@ class Sessions extends Component {
58
54
  this.timer = null
59
55
  }
60
56
 
57
+ updateTabs = (propTabs) => {
58
+ const update = {
59
+ tabs: copy(propTabs)
60
+ }
61
+ const currentTab = propTabs.find(t => t.id === this.state.currentTabId)
62
+ if (!currentTab) {
63
+ update.currentTabId = propTabs[0]?.id
64
+ }
65
+ this.setState(update)
66
+ }
67
+
61
68
  initShortcuts () {
62
69
  window.addEventListener('keydown', this.bindHandleKeyboardEvent)
63
70
  }
64
71
 
72
+ notCurrentTab = (tab) => {
73
+ return this.state.currentTabId !== window.store.currentTabId
74
+ }
75
+
65
76
  closeCurrentTabShortcut = (e) => {
77
+ if (this.notCurrentTab()) {
78
+ return
79
+ }
66
80
  e.stopPropagation()
67
81
  this.delTab(
68
82
  this.state.currentTabId
@@ -70,12 +84,23 @@ class Sessions extends Component {
70
84
  }
71
85
 
72
86
  reloadCurrentTabShortcut = (e) => {
87
+ if (this.notCurrentTab()) {
88
+ return
89
+ }
73
90
  e.stopPropagation()
74
91
  this.reloadTab(
75
92
  this.getCurrentTab()
76
93
  )
77
94
  }
78
95
 
96
+ cloneToNextLayoutShortcut = (e) => {
97
+ if (this.notCurrentTab()) {
98
+ return
99
+ }
100
+ e.stopPropagation()
101
+ window.store.cloneToNextLayout()
102
+ }
103
+
79
104
  watch = () => {
80
105
  window.addEventListener('message', this.onEvent)
81
106
  }
@@ -94,18 +119,8 @@ class Sessions extends Component {
94
119
  currentTabId: id
95
120
  })
96
121
  } else {
97
- window.store.focus()
122
+ document.querySelector('.tab.active')?.click()
98
123
  }
99
- postMsg({
100
- action: commonActions.updateStore,
101
- value: id,
102
- prop: 'currentTabId'
103
- })
104
- postMsg({
105
- action: commonActions.updateStore,
106
- value: id,
107
- prop: 'currentTabId' + this.props.batch
108
- })
109
124
  }
110
125
 
111
126
  getCurrentTab = () => {
@@ -123,17 +138,15 @@ class Sessions extends Component {
123
138
  if (tab) {
124
139
  Object.assign(tab, update)
125
140
  }
126
- this.updateStoreTabs(tabs)
127
141
  return {
128
142
  tabs
129
143
  }
144
+ }, () => {
145
+ this.updateStoreTabs(this.state.tabs)
130
146
  })
131
147
  }
132
148
 
133
- addTab = (
134
- _tab,
135
- _index
136
- ) => {
149
+ addTab = (_tab, _index) => {
137
150
  this.setState((oldState) => {
138
151
  const tabs = copy(oldState.tabs)
139
152
  const index = typeof _index === 'undefined'
@@ -147,15 +160,17 @@ class Sessions extends Component {
147
160
  }
148
161
  tab.batch = this.props.batch
149
162
  tabs.splice(index, 0, tab)
150
- this.updateStoreTabs(tabs)
151
- this.updateStoreCurrentTabId(tab.id)
152
163
  return {
153
164
  currentTabId: tab.id,
154
165
  tabs
155
166
  }
167
+ }, () => {
168
+ this.updateStoreTabs(this.state.tabs)
169
+ this.updateStoreCurrentTabId(this.state.currentTabId)
156
170
  })
157
171
  }
158
172
 
173
+ // After
159
174
  delTab = (id) => {
160
175
  this.setState((oldState) => {
161
176
  const tabs = copy(oldState.tabs)
@@ -168,13 +183,16 @@ class Sessions extends Component {
168
183
  i = i ? i - 1 : i + 1
169
184
  const next = tabs[i] || {}
170
185
  up.currentTabId = next.id || ''
171
- this.updateStoreCurrentTabId(next.id)
172
186
  }
173
187
  up.tabs = tabs.filter(t => {
174
188
  return t.id !== id
175
189
  })
176
- this.updateStoreTabs(up.tabs)
177
190
  return up
191
+ }, () => {
192
+ this.updateStoreTabs(this.state.tabs)
193
+ if (this.state.currentTabId !== id) {
194
+ this.updateStoreCurrentTabId(this.state.currentTabId)
195
+ }
178
196
  })
179
197
  }
180
198
 
@@ -185,6 +203,10 @@ class Sessions extends Component {
185
203
  this.addTab(tab)
186
204
  }
187
205
 
206
+ handleClick = () => {
207
+ window.store.currentTabId = this.state.currentTabId
208
+ }
209
+
188
210
  reloadTab = async (tabToReload) => {
189
211
  this.setState(async oldState => {
190
212
  const tab = copy(
@@ -228,11 +250,15 @@ class Sessions extends Component {
228
250
  if (!matchedTab) {
229
251
  return
230
252
  }
231
- this.timer = setTimeout(window.store.triggerResize, 500)
232
- this.updateStoreCurrentTabId(id)
253
+
254
+ // Batch the updates
233
255
  this.setState({
234
256
  currentTabId: id
235
- }, this.postChange)
257
+ }, () => {
258
+ this.updateStoreCurrentTabId(id)
259
+ this.timer = setTimeout(window.store.triggerResize, 500)
260
+ this.postChange()
261
+ })
236
262
  }
237
263
 
238
264
  setTabs = tabs => {
@@ -347,6 +373,15 @@ class Sessions extends Component {
347
373
  )
348
374
  }
349
375
 
376
+ computeHeight = () => {
377
+ const {
378
+ tabsHeight
379
+ } = this.props
380
+ return this.props.height -
381
+ tabsHeight -
382
+ termControlHeight
383
+ }
384
+
350
385
  renderSessions () {
351
386
  const {
352
387
  config, width, height
@@ -389,6 +424,7 @@ class Sessions extends Component {
389
424
  'onChangeTabId',
390
425
  'onDuplicateTab',
391
426
  'reloadTab',
427
+ 'computeHeight',
392
428
  'delTab',
393
429
  'addTab',
394
430
  'editTab'
@@ -396,7 +432,12 @@ class Sessions extends Component {
396
432
  }
397
433
  if (type === terminalWebType) {
398
434
  const webProps = {
399
- tab
435
+ tab,
436
+ width,
437
+ height: this.computeHeight(),
438
+ ...pick(this, [
439
+ 'reloadTab'
440
+ ])
400
441
  }
401
442
  return (
402
443
  <div className={cls} key={id}>
@@ -461,6 +502,7 @@ class Sessions extends Component {
461
502
  <div
462
503
  className='sessions'
463
504
  key='main-sess'
505
+ onClick={this.handleClick}
464
506
  >
465
507
  {this.renderSessions()}
466
508
  </div>
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * bookmark form
7
7
  */
8
- import { useDelta, useConditionalEffect } from 'react-delta'
8
+ import { useDelta, useConditionalEffect } from 'react-delta-hooks'
9
9
  import { ArrowDownOutlined, ArrowUpOutlined, SaveOutlined, ClearOutlined } from '@ant-design/icons'
10
10
  import { Button, Input, notification, Form } from 'antd'
11
11
  import Link from '../common/external-link'
@@ -56,6 +56,9 @@ export function shortcutExtend (Cls) {
56
56
  type,
57
57
  key
58
58
  } = event
59
+ if (this.cmdAddon) {
60
+ this.cmdAddon.handleKey(event)
61
+ }
59
62
  if (
60
63
  this.term &&
61
64
  key === 'Backspace' &&
@@ -10,6 +10,11 @@ export default () => {
10
10
  shortcut: 'alt+r',
11
11
  shortcutMac: 'alt+r'
12
12
  },
13
+ {
14
+ name: 'app_cloneToNextLayout',
15
+ shortcut: 'ctrl+/',
16
+ shortcutMac: 'meta+/'
17
+ },
13
18
  {
14
19
  name: 'app_newBookmark',
15
20
  shortcut: 'ctrl+n',
@@ -40,11 +45,6 @@ export default () => {
40
45
  shortcut: 'ctrl+tab',
41
46
  shortcutMac: 'ctrl+tab'
42
47
  },
43
- {
44
- name: 'terminal_split',
45
- shortcut: 'ctrl+/',
46
- shortcutMac: 'meta+/'
47
- },
48
48
  {
49
49
  name: 'terminal_clear',
50
50
  shortcut: 'ctrl+l,ctrl+shift+l',
@@ -9,7 +9,6 @@ import {
9
9
  Loading3QuartersOutlined,
10
10
  BorderlessTableOutlined
11
11
  } from '@ant-design/icons'
12
- import generate from '../../common/id-with-stamp'
13
12
  import { Tooltip, message } from 'antd'
14
13
  import classnames from 'classnames'
15
14
  import copy from 'json-deep-copy'
@@ -18,10 +17,7 @@ import Input from '../common/input-auto-focus'
18
17
  import createName from '../../common/create-title'
19
18
  import { addClass, removeClass } from '../../common/class'
20
19
  import {
21
- terminalSshConfigType,
22
- splitConfig,
23
- paneMap,
24
- statusMap
20
+ terminalSshConfigType
25
21
  } from '../../common/constants'
26
22
  import { shortcutDescExtend } from '../shortcuts/shortcut-handler.js'
27
23
 
@@ -203,18 +199,7 @@ class Tab extends Component {
203
199
  }
204
200
 
205
201
  cloneToNextLayout = () => {
206
- const defaultStatus = statusMap.processing
207
- const { batch, layout } = this.props
208
- const ntb = copy(this.state.tab)
209
- Object.assign(ntb, {
210
- id: generate(),
211
- status: defaultStatus,
212
- isTransporting: undefined,
213
- pane: paneMap.terminal
214
- })
215
- const maxBatch = splitConfig[layout].children
216
- ntb.batch = (batch + 1) % maxBatch
217
- window.store.addTab(ntb)
202
+ window.store.cloneToNextLayout()
218
203
  }
219
204
 
220
205
  newTab = () => {
@@ -280,17 +265,20 @@ class Tab extends Component {
280
265
  }
281
266
 
282
267
  renderContext = () => {
283
- const { tabs, tab, layout } = this.props
268
+ const { tabs, tab } = this.props
284
269
  const len = tabs.length
285
270
  const index = findIndex(tabs, t => t.id === tab.id)
286
271
  const noRight = index >= len - 1
287
272
  const isSshConfig = tab.type === terminalSshConfigType
288
273
  const res = []
289
274
  const reloadShortcut = this.getShortcut('app_reloadCurrentTab')
275
+ const closeShortcut = this.getShortcut('app_closeCurrentTab')
276
+ const cloneToNextShortcut = this.getShortcut('app_cloneToNextLayout')
290
277
  res.push({
291
278
  func: 'handleClose',
292
279
  icon: 'CloseOutlined',
293
- text: e('close')
280
+ text: e('close'),
281
+ subText: closeShortcut
294
282
  })
295
283
  res.push({
296
284
  func: 'closeOther',
@@ -314,13 +302,12 @@ class Tab extends Component {
314
302
  icon: 'CopyOutlined',
315
303
  text: e('duplicate')
316
304
  })
317
- if (layout !== 'c1') {
318
- res.push({
319
- func: 'cloneToNextLayout',
320
- icon: 'CopyOutlined',
321
- text: e('cloneToNextLayout')
322
- })
323
- }
305
+ res.push({
306
+ func: 'cloneToNextLayout',
307
+ icon: 'CopyOutlined',
308
+ text: e('cloneToNextLayout'),
309
+ subText: cloneToNextShortcut
310
+ })
324
311
  res.push({
325
312
  disabled: isSshConfig,
326
313
  func: 'doRename',
@@ -7,6 +7,8 @@
7
7
  ::-webkit-scrollbar
8
8
  width 0
9
9
  display none
10
+ .not-system-ui.is-mac .layout-item.v1 .tabs-inner
11
+ margin-left 42px
10
12
  .not-system-ui.is-mac.not-webapp .layout-item.v1
11
13
  .tabs-inner
12
14
  margin-left 72px
@@ -25,8 +27,6 @@
25
27
  height 36px
26
28
  overflow-x scroll
27
29
  overflow-y hidden
28
- .layout-item.v1 .tabs-inner
29
- margin-left 42px
30
30
 
31
31
  .tabs-wrapper
32
32
  z-index 3
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * customize AttachAddon
3
3
  */
4
- import { AttachAddon } from 'xterm-addon-attach'
4
+ import { AttachAddon } from '@xterm/addon-attach'
5
5
  import regEscape from 'escape-string-regexp'
6
6
 
7
7
  export default class AttachAddonCustom extends AttachAddon {
@@ -0,0 +1,75 @@
1
+ export class CommandTrackerAddon {
2
+ constructor () {
3
+ this.terminal = undefined
4
+ this.activeCommand = ''
5
+ this.currentCommand = ''
6
+ this.cursorPosition = 0
7
+ this.timeout = null
8
+ this.handleKey = this.debounce(this._handleKey, 200) // 10ms debounce
9
+ }
10
+
11
+ debounce = (func, wait) => {
12
+ return (...args) => {
13
+ const later = () => {
14
+ clearTimeout(this.timeout)
15
+ func.apply(this, args)
16
+ }
17
+
18
+ clearTimeout(this.timeout)
19
+ this.timeout = setTimeout(later, wait)
20
+ }
21
+ }
22
+
23
+ activate (terminal) {
24
+ this.terminal = terminal
25
+ }
26
+
27
+ dispose () {
28
+ this.term = null
29
+ if (this._disposables) {
30
+ this._disposables.forEach(d => d.dispose())
31
+ this._disposables.length = 0
32
+ }
33
+ if (this.timeout) {
34
+ clearTimeout(this.timeout)
35
+ this.timeout = null
36
+ }
37
+ }
38
+
39
+ handleData = (data) => {
40
+ // Handle regular input
41
+ this.activeCommand = this.activeCommand.slice(0, this.cursorPosition) + data + this.activeCommand.slice(this.cursorPosition)
42
+ this.cursorPosition += data.length
43
+ }
44
+
45
+ // This is now our internal handler
46
+ _handleKey = (e) => {
47
+ const { key } = e
48
+ if (key === 'Enter') {
49
+ // Command executed, reset
50
+ this.currentCommand = this.activeCommand
51
+ this.activeCommand = ''
52
+ this.cursorPosition = 0
53
+ } else if (key === 'Backspace') {
54
+ // Handle backspace
55
+ if (this.cursorPosition > 0) {
56
+ this.activeCommand = this.activeCommand.slice(0, this.cursorPosition - 1) + this.activeCommand.slice(this.cursorPosition)
57
+ this.cursorPosition--
58
+ }
59
+ } else if (key === 'ArrowLeft') {
60
+ // Move cursor left
61
+ if (this.cursorPosition > 0) {
62
+ this.cursorPosition--
63
+ }
64
+ } else if (key === 'ArrowRight') {
65
+ // Move cursor right
66
+ if (this.cursorPosition < this.activeCommand.length) {
67
+ this.cursorPosition++
68
+ }
69
+ }
70
+ }
71
+
72
+ getCurrentCommand () {
73
+ return this.activeCommand || this.currentCommand
74
+ }
75
+ }
@@ -31,25 +31,24 @@ import {
31
31
  } from '../../common/constants'
32
32
  import deepCopy from 'json-deep-copy'
33
33
  import { readClipboardAsync, copy } from '../../common/clipboard'
34
- import { FitAddon } from 'xterm-addon-fit'
34
+ import { FitAddon } from '@xterm/addon-fit'
35
35
  import AttachAddon from './attach-addon-custom'
36
- import { SearchAddon } from 'xterm-addon-search'
37
- import { WebLinksAddon } from 'xterm-addon-web-links'
38
- import { CanvasAddon } from 'xterm-addon-canvas'
39
- import { WebglAddon } from 'xterm-addon-webgl'
40
- import { LigaturesAddon } from 'xterm-addon-ligatures'
36
+ import { SearchAddon } from '@xterm/addon-search'
37
+ import { WebLinksAddon } from '@xterm/addon-web-links'
38
+ import { CanvasAddon } from '@xterm/addon-canvas'
39
+ import { WebglAddon } from '@xterm/addon-webgl'
40
+ import { LigaturesAddon } from '@xterm/addon-ligatures'
41
41
  import getProxy from '../../common/get-proxy'
42
42
  import { AddonZmodem } from './xterm-zmodem'
43
- import { Unicode11Addon } from 'xterm-addon-unicode11'
43
+ import { Unicode11Addon } from '@xterm/addon-unicode11'
44
44
  import keyControlPressed from '../../common/key-control-pressed'
45
- import { Terminal } from 'xterm'
45
+ import { Terminal } from '@xterm/xterm'
46
46
  import NormalBuffer from './normal-buffer'
47
47
  import { createTerm, resizeTerm } from './terminal-apis'
48
48
  import { shortcutExtend, shortcutDescExtend } from '../shortcuts/shortcut-handler.js'
49
49
  import { KeywordHighlighterAddon } from './highlight-addon.js'
50
50
  import { getLocalFileInfo } from '../sftp/file-read.js'
51
- import { SerializeAddon } from 'xterm-addon-serialize'
52
- import strip from '@electerm/strip-ansi'
51
+ import { CommandTrackerAddon } from './command-tracker-addon.js'
53
52
  import { formatBytes } from '../../common/byte-format.js'
54
53
  import * as fs from './fs.js'
55
54
 
@@ -184,8 +183,13 @@ clear\r`
184
183
  this.fitAddon = null
185
184
  this.zmodemAddon = null
186
185
  this.searchAddon = null
187
- this.serializeAddon = null
188
186
  this.fitAddon = null
187
+ this.cmdAddon = null
188
+ // Clear the notification if it exists
189
+ if (this.socketCloseWarning) {
190
+ notification.destroy(this.socketCloseWarning.key)
191
+ this.socketCloseWarning = null
192
+ }
189
193
  }
190
194
 
191
195
  terminalConfigProps = [
@@ -928,17 +932,8 @@ clear\r`
928
932
  return result
929
933
  }
930
934
 
931
- // onKey = ({ key }) => {
932
- // if (key === '\r') {
933
- // this.getCmd()
934
- // }
935
- // }
936
-
937
935
  getCmd = () => {
938
- const str = this.serializeAddon.serialize()
939
- const arr = strip(str).split(/ +/)
940
- const len = arr.length
941
- return arr[len - 1]
936
+ return this.cmdAddon.getCurrentCommand()
942
937
  }
943
938
 
944
939
  getCwd = () => {
@@ -963,13 +958,15 @@ clear\r`
963
958
  }
964
959
 
965
960
  onData = (d) => {
966
- // runIdle(this.notifyOnData)
961
+ if (this.cmdAddon) {
962
+ this.cmdAddon.handleData(d)
963
+ }
967
964
  if (!d.includes('\r')) {
968
965
  delete this.userTypeExit
969
966
  } else {
970
- const data = this.getCmd()
967
+ const data = this.getCmd().trim()
971
968
  if (this.term.buffer.active.type !== 'alternate') {
972
- setTimeout(this.getCwd, 200)
969
+ this.timers.getCwd = setTimeout(this.getCwd, 200)
973
970
  }
974
971
  const exitCmds = [
975
972
  'exit',
@@ -1028,18 +1025,18 @@ clear\r`
1028
1025
 
1029
1026
  // term.on('keydown', this.handleEvent)
1030
1027
  this.fitAddon = new FitAddon()
1028
+ this.cmdAddon = new CommandTrackerAddon()
1031
1029
  this.searchAddon = new SearchAddon()
1032
1030
  const ligtureAddon = new LigaturesAddon()
1033
1031
  this.searchAddon.onDidChangeResults(this.onSearchResultsChange)
1034
1032
  const unicode11Addon = new Unicode11Addon()
1035
1033
  term.loadAddon(unicode11Addon)
1036
- this.serializeAddon = new SerializeAddon()
1037
- term.loadAddon(this.serializeAddon)
1038
1034
  term.loadAddon(ligtureAddon)
1039
1035
  // activate the new version
1040
1036
  term.unicode.activeVersion = '11'
1041
1037
  term.loadAddon(this.fitAddon)
1042
1038
  term.loadAddon(this.searchAddon)
1039
+ term.loadAddon(this.cmdAddon)
1043
1040
  term.onData(this.onData)
1044
1041
  this.term = term
1045
1042
  term.attachCustomKeyEventHandler(this.handleKeyboardEvent.bind(this))
@@ -1056,6 +1053,7 @@ clear\r`
1056
1053
  }
1057
1054
 
1058
1055
  runInitScript = () => {
1056
+ window.store.triggerResize()
1059
1057
  const {
1060
1058
  type,
1061
1059
  title,
@@ -1237,6 +1235,7 @@ clear\r`
1237
1235
  new KeywordHighlighterAddon(keywords)
1238
1236
  )
1239
1237
  window.store.triggerResize()
1238
+ window.store.focus()
1240
1239
  }
1241
1240
 
1242
1241
  onResize = throttle(() => {
@@ -34,7 +34,10 @@
34
34
  #container
35
35
  .xterm-viewport
36
36
  background-repeat no-repeat
37
- background-position 50% 50%
37
+ background-position center
38
+ background-color: transparent !important
39
+ z-index 1
40
+
38
41
 
39
42
  .terminal-not-active .xterm-text-layer
40
43
  opacity .74
@@ -0,0 +1,50 @@
1
+ import {
2
+ Input,
3
+ Tooltip
4
+ } from 'antd'
5
+ import { copy } from '../../common/clipboard'
6
+ import {
7
+ ReloadOutlined,
8
+ GlobalOutlined
9
+ } from '@ant-design/icons'
10
+
11
+ export default function AddressBar (props) {
12
+ const {
13
+ url,
14
+ onReload,
15
+ onOpen,
16
+ title,
17
+ description
18
+ } = props
19
+ const content = (
20
+ <div>
21
+ <h1>{title}</h1>
22
+ <p>{description}</p>
23
+ </div>
24
+ )
25
+ function handleClick () {
26
+ copy(url)
27
+ }
28
+ return (
29
+ <div className='web-address-bar pd1'>
30
+ <Tooltip
31
+ title={content}
32
+ >
33
+ <Input
34
+ value={url}
35
+ onClick={handleClick}
36
+ addonBefore={
37
+ <ReloadOutlined
38
+ onClick={onReload}
39
+ />
40
+ }
41
+ addonAfter={
42
+ <GlobalOutlined
43
+ onClick={onOpen}
44
+ />
45
+ }
46
+ />
47
+ </Tooltip>
48
+ </div>
49
+ )
50
+ }
@@ -1,19 +1,41 @@
1
- import { useEffect } from 'react'
2
- import Link from '../common/external-link'
1
+ import AddressBar from './address-bar'
3
2
 
4
3
  export default function WebSession (props) {
5
4
  const {
6
- tab
5
+ tab,
6
+ width,
7
+ height,
8
+ reloadTab
7
9
  } = props
8
- useEffect(() => {
9
- tab.url && window.openLink(tab.url)
10
- }, [])
10
+ const addrProps = {
11
+ url: tab.url,
12
+ title: tab.title,
13
+ description: tab.description,
14
+ onOpen: () => {
15
+ window.openLink(tab.url)
16
+ },
17
+ onReload: () => {
18
+ reloadTab(
19
+ tab
20
+ )
21
+ }
22
+ }
23
+ const viewProps = {
24
+ src: tab.url,
25
+ style: {
26
+ width: (width - 10) + 'px',
27
+ height: (height - 12) + 'px'
28
+ },
29
+ disableblinkfeatures: 'true',
30
+ disablewebsecurity: 'true'
31
+ }
11
32
  return (
12
33
  <div className='web-session-wrap'>
13
- <div className='pd3 aligncenter'>
14
- <h1>{tab.title}</h1>
15
- <p>{tab.description}</p>
16
- <Link to={tab.url}>{tab.url}</Link>
34
+ <AddressBar {...addrProps} />
35
+ <div className='pd1'>
36
+ <webview
37
+ {...viewProps}
38
+ />
17
39
  </div>
18
40
  </div>
19
41
  )
@@ -1,16 +1,11 @@
1
- import { render } from 'react-dom'
1
+ import { createRoot } from 'react-dom/client'
2
2
  import 'antd/dist/reset.css'
3
- import 'xterm/css/xterm.css'
3
+ import '@xterm/xterm/css/xterm.css'
4
4
  import '../common/trzsz'
5
5
  import 'firacode/distr/fira_code.css'
6
6
  import Main from '../components/main/index.jsx'
7
- import { notification } from 'antd'
8
- notification.config({
9
- placement: 'bottomRight'
10
- })
11
7
 
12
- const rootElement = document.getElementById('container')
13
- render(
14
- <Main />,
15
- rootElement
8
+ const rootElement = createRoot(document.getElementById('container'))
9
+ rootElement.render(
10
+ <Main />
16
11
  )
@@ -9,7 +9,7 @@ import initWatch from './watch'
9
9
  export default (Store) => {
10
10
  Store.prototype.checkForDbUpgrade = async function () {
11
11
  const { store } = window
12
- if (store.isSencondInstance) {
12
+ if (store.isSecondInstance) {
13
13
  return false
14
14
  }
15
15
  const shouldUpgrade = await window.pre.runGlobalAsync('checkDbUpgrade')
@@ -63,6 +63,7 @@ export default Store => {
63
63
 
64
64
  Store.prototype.triggerResize = function () {
65
65
  window.store.resizeTrigger = window.store.resizeTrigger ? 0 : 1
66
+ window.dispatchEvent(new Event('resize'))
66
67
  }
67
68
 
68
69
  Store.prototype.toggleTermFullscreen = function (terminalFullScreen) {
@@ -73,7 +73,7 @@ export default Store => {
73
73
  clickCount: ((qm.clickCount || 0) + 1)
74
74
  })
75
75
  }
76
- }, 100)
76
+ }, 200)
77
77
 
78
78
  Store.prototype.setQmSortByFrequency = function (v) {
79
79
  window.store.qmSortByFrequency = v
@@ -190,7 +190,7 @@ export default Store => {
190
190
 
191
191
  Store.prototype.openSettingModal = function () {
192
192
  const { store } = window
193
- if (store.isSencondInstance) {
193
+ if (store.isSecondInstance) {
194
194
  return message.warning(
195
195
  e('sencondInstanceTip')
196
196
  )
@@ -5,11 +5,14 @@
5
5
  import { uniq, debounce, findIndex } from 'lodash-es'
6
6
  import {
7
7
  tabActions,
8
- splitConfig
8
+ splitConfig,
9
+ statusMap,
10
+ paneMap
9
11
  } from '../common/constants'
10
12
  import postMsg from '../common/post-msg'
11
13
  import * as ls from '../common/safe-local-storage'
12
14
  import deepCopy from 'json-deep-copy'
15
+ import generate from '../common/id-with-stamp'
13
16
 
14
17
  export default Store => {
15
18
  Store.prototype.updateTabsStatus = function () {
@@ -63,7 +66,7 @@ export default Store => {
63
66
  postMsg({
64
67
  action: tabActions.addTab,
65
68
  tab,
66
- batch: window.openTabBatch ?? window.store.currentLayoutBatch,
69
+ batch: tab?.batch ?? window.openTabBatch ?? window.store.currentLayoutBatch,
67
70
  index
68
71
  })
69
72
  }
@@ -96,6 +99,28 @@ export default Store => {
96
99
  }
97
100
  }
98
101
 
102
+ Store.prototype.cloneToNextLayout = function () {
103
+ const { store } = window
104
+ const defaultStatus = statusMap.processing
105
+ const { currentTab, layout, currentLayoutBatch } = store
106
+ const ntb = deepCopy(currentTab)
107
+ Object.assign(ntb, {
108
+ id: generate(),
109
+ status: defaultStatus,
110
+ isTransporting: undefined,
111
+ pane: paneMap.terminal
112
+ })
113
+ let maxBatch = splitConfig[layout].children
114
+ if (maxBatch < 2) {
115
+ maxBatch = 2
116
+ }
117
+ ntb.batch = (currentLayoutBatch + 1) % maxBatch
118
+ store.addTab(ntb)
119
+ if (layout === 'c1') {
120
+ store.setLayout('c2')
121
+ }
122
+ }
123
+
99
124
  Store.prototype.setLayout = function (layout) {
100
125
  const {
101
126
  store
@@ -99,11 +99,6 @@ export default store => {
99
99
  return store._checkedKeys
100
100
  }).start()
101
101
 
102
- // autoRun(store, () => {
103
- // window.store.onLayoutChange()
104
- // return store.layout
105
- // }).start()
106
-
107
102
  autoRun(store, () => {
108
103
  const tabs = store.getTabs()
109
104
  const { currentTabId } = store
@@ -27,7 +27,7 @@ html
27
27
  }
28
28
  - if (!isDev)
29
29
  link(rel='stylesheet', href='css/' + version + '-basic.css')
30
- link(rel='stylesheet', href='css/' + version + '-index.css')
30
+ link(rel='stylesheet', href='css/' + version + '-electerm.css')
31
31
  style(id='theme-css').
32
32
  style(id='custom-css').
33
33
  body
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "1.50.21",
3
+ "version": "1.50.40",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",