@electerm/electerm-react 1.39.18 → 1.39.31

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 (36) hide show
  1. package/client/common/constants.js +7 -3
  2. package/client/common/create-title.jsx +12 -2
  3. package/client/common/default-setting.js +5 -0
  4. package/client/common/init-setting-item.js +6 -0
  5. package/client/components/bookmark-form/index.jsx +6 -2
  6. package/client/components/bookmark-form/rdp-form-ui.jsx +1 -1
  7. package/client/components/bookmark-form/rdp-form.jsx +1 -1
  8. package/client/components/bookmark-form/render-auth-ssh.jsx +27 -1
  9. package/client/components/bookmark-form/vnc-form-ui.jsx +179 -0
  10. package/client/components/bookmark-form/vnc-form.jsx +16 -0
  11. package/client/components/footer/footer-entry.jsx +1 -0
  12. package/client/components/main/term-fullscreen.styl +4 -1
  13. package/client/components/profile/profile-form-elem.jsx +87 -0
  14. package/client/components/profile/profile-form.jsx +33 -0
  15. package/client/components/profile/profile-list.jsx +79 -0
  16. package/client/components/profile/profile-transport-mod.jsx +5 -0
  17. package/client/components/profile/profile-transport.jsx +12 -0
  18. package/client/components/quick-commands/quick-command-transport-mod.jsx +13 -14
  19. package/client/components/rdp/rdp-session.jsx +53 -36
  20. package/client/components/session/session.jsx +14 -3
  21. package/client/components/session/session.styl +7 -2
  22. package/client/components/setting-panel/setting-modal.jsx +14 -0
  23. package/client/components/setting-panel/setting-terminal.jsx +1 -1
  24. package/client/components/setting-panel/tab-profiles.jsx +38 -0
  25. package/client/components/sftp/list-table-ui.jsx +99 -46
  26. package/client/components/sftp/sftp-entry.jsx +1 -0
  27. package/client/components/sftp/sftp.styl +4 -1
  28. package/client/components/sftp/transfer-common.js +1 -1
  29. package/client/components/terminal/index.jsx +2 -4
  30. package/client/components/vnc/vnc-form.jsx +66 -0
  31. package/client/components/vnc/vnc-session.jsx +297 -0
  32. package/client/store/common.js +21 -0
  33. package/client/store/index.js +1 -0
  34. package/client/store/init-state.js +4 -23
  35. package/client/store/load-data.js +1 -2
  36. package/package.json +1 -1
@@ -4,13 +4,14 @@ import time from '../../common/time'
4
4
  import copy from 'json-deep-copy'
5
5
 
6
6
  export default class QmTransport extends BookmarkTransport {
7
+ name = 'quickCommands'
7
8
  beforeUpload = async (file) => {
8
9
  const { store } = this.props
9
10
  const txt = await window.fs.readFile(file.path)
10
11
  try {
11
- const quickCommands = JSON.parse(txt)
12
- const quickCommandsOld = copy(store.quickCommands)
13
- const bmTreeOld = quickCommandsOld.reduce((p, v) => {
12
+ const arr = JSON.parse(txt)
13
+ const arrOld = copy(store[this.name])
14
+ const bmTreeOld = arrOld.reduce((p, v) => {
14
15
  return {
15
16
  ...p,
16
17
  [v.id]: v
@@ -18,19 +19,19 @@ export default class QmTransport extends BookmarkTransport {
18
19
  }, {})
19
20
  const add = []
20
21
  const dbAdd = []
21
- quickCommands.forEach(bg => {
22
+ arr.forEach(bg => {
22
23
  if (!bmTreeOld[bg.id]) {
23
- quickCommandsOld.push(bg)
24
+ arrOld.push(bg)
24
25
  add.push(bg)
25
26
  dbAdd.push({
26
- db: 'quickCommands',
27
+ db: this.name,
27
28
  obj: bg
28
29
  })
29
30
  }
30
31
  })
31
- store.storeAssign({
32
- quickCommands: quickCommandsOld
33
- })
32
+ store.setState(
33
+ this.name, arrOld
34
+ )
34
35
  store.batchDbAdd(dbAdd)
35
36
  } catch (e) {
36
37
  store.onError(e)
@@ -44,11 +45,9 @@ export default class QmTransport extends BookmarkTransport {
44
45
 
45
46
  handleDownload = () => {
46
47
  const { store } = this.props
47
- const {
48
- quickCommands
49
- } = store
50
- const txt = JSON.stringify(quickCommands, null, 2)
48
+ const arr = store[this.name]
49
+ const txt = JSON.stringify(arr, null, 2)
51
50
  const stamp = time(undefined, 'YYYY-MM-DD-HH-mm-ss')
52
- download('quickCommands-' + stamp + '.json', txt)
51
+ download('electerm-' + this.name + '-' + stamp + '.json', txt)
53
52
  }
54
53
  }
@@ -70,7 +70,7 @@ export default class RdpSession extends Component {
70
70
  openedSideBar
71
71
  } = this.props
72
72
  return {
73
- width: width - (pinned && openedSideBar ? leftSidebarWidth : 0),
73
+ width: width - (pinned && openedSideBar ? leftSidebarWidth : 0) - 42,
74
74
  height: height - tabsHeight
75
75
  }
76
76
  }
@@ -235,7 +235,7 @@ export default class RdpSession extends Component {
235
235
  const isHorizontal = false
236
236
  const delta = isHorizontal ? e.deltaX : e.deltaY
237
237
  const step = Math.round(Math.abs(delta) * 15 / 8)
238
- console.log(x, y, step, delta, isHorizontal)
238
+ // console.log(x, y, step, delta, isHorizontal)
239
239
  params = [x, y, step, delta > 0, isHorizontal]
240
240
  } else if (type === 'keydown' || type === 'keyup') {
241
241
  params = [keyCode, pressed]
@@ -349,6 +349,14 @@ export default class RdpSession extends Component {
349
349
  this.setState(res, this.handleReInit)
350
350
  }
351
351
 
352
+ renderHelp = () => {
353
+ return (
354
+ <HelpIcon
355
+ link={rdpHelpLink}
356
+ />
357
+ )
358
+ }
359
+
352
360
  renderControl = () => {
353
361
  const {
354
362
  id
@@ -358,45 +366,54 @@ export default class RdpSession extends Component {
358
366
  onChange: this.handleResChange,
359
367
  popupMatchSelectWidth: false
360
368
  }
369
+ return (
370
+ <div className='pd1 fix session-v-info'>
371
+ <div className='fleft'>
372
+ <ReloadOutlined
373
+ onClick={this.handleReInit}
374
+ className='mg2r mg1l pointer'
375
+ />
376
+ <Select
377
+ {...sleProps}
378
+ >
379
+ {
380
+ this.getAllRes().map(d => {
381
+ const v = d.id
382
+ return (
383
+ <Option
384
+ key={v}
385
+ value={v}
386
+ >
387
+ {d.width}x{d.height}
388
+ </Option>
389
+ )
390
+ })
391
+ }
392
+ </Select>
393
+ <EditOutlined
394
+ onClick={this.handleEditResolutions}
395
+ className='mg2r mg1l pointer'
396
+ />
397
+ {this.renderInfo()}
398
+ {this.renderHelp()}
399
+ </div>
400
+ <div className='fright'>
401
+ {this.props.fullscreenIcon()}
402
+ </div>
403
+ </div>
404
+ )
405
+ }
406
+
407
+ renderInfo () {
361
408
  const {
362
409
  host,
363
410
  port,
364
411
  username
365
412
  } = this.props.tab
366
413
  return (
367
- <div className='pd1 fix'>
368
- <ReloadOutlined
369
- onClick={this.handleReInit}
370
- className='mg2r mg1l pointer'
371
- />
372
- <Select
373
- {...sleProps}
374
- >
375
- {
376
- this.getAllRes().map(d => {
377
- const v = d.id
378
- return (
379
- <Option
380
- key={v}
381
- value={v}
382
- >
383
- {d.width}x{d.height}
384
- </Option>
385
- )
386
- })
387
- }
388
- </Select>
389
- <EditOutlined
390
- onClick={this.handleEditResolutions}
391
- className='mg2r mg1l pointer'
392
- />
393
- <span className='mg2l mg2r'>
394
- {username}@{host}:{port}
395
- </span>
396
- <HelpIcon
397
- link={rdpHelpLink}
398
- />
399
- </div>
414
+ <span className='mg2l mg2r'>
415
+ {username}@{host}:{port}
416
+ </span>
400
417
  )
401
418
  }
402
419
 
@@ -425,7 +442,7 @@ export default class RdpSession extends Component {
425
442
  <Spin spinning={loading}>
426
443
  <div
427
444
  {...rdpProps}
428
- className='rdp-session-wrap pd1'
445
+ className='rdp-session-wrap session-v-wrap'
429
446
  >
430
447
  {this.renderControl()}
431
448
  <canvas
@@ -5,6 +5,7 @@ import { Component } from 'react'
5
5
  import Term from '../terminal'
6
6
  import Sftp from '../sftp/sftp-entry'
7
7
  import RdpSession from '../rdp/rdp-session'
8
+ import VncSession from '../vnc/vnc-session'
8
9
  import {
9
10
  BorderVerticleOutlined,
10
11
  BorderHorizontalOutlined,
@@ -29,7 +30,8 @@ import {
29
30
  footerHeight,
30
31
  terminalActions,
31
32
  connectionMap,
32
- terminalRdpType
33
+ terminalRdpType,
34
+ terminalVncType
33
35
  } from '../../common/constants'
34
36
  import ResizeWrap from '../common/resize-wrap'
35
37
  import safeName from '../../common/safe-name'
@@ -325,7 +327,7 @@ export default class SessionWrapper extends Component {
325
327
  const {
326
328
  pane, type
327
329
  } = this.props.tab
328
- if (type === terminalRdpType) {
330
+ if (type === terminalRdpType || type === terminalVncType) {
329
331
  const rdpProps = {
330
332
  tab: this.props.tab,
331
333
  sessionId,
@@ -339,14 +341,23 @@ export default class SessionWrapper extends Component {
339
341
  'openedSideBar',
340
342
  'delTab',
341
343
  'config',
344
+ 'reloadTab',
342
345
  'editTab'
343
346
  ]),
344
347
  ...pick(
345
348
  this,
346
349
  [
350
+ 'fullscreenIcon',
347
351
  'setSessionState'
348
352
  ])
349
353
  }
354
+ if (type === terminalVncType) {
355
+ return (
356
+ <VncSession
357
+ {...rdpProps}
358
+ />
359
+ )
360
+ }
350
361
  return (
351
362
  <RdpSession
352
363
  {...rdpProps}
@@ -506,7 +517,7 @@ export default class SessionWrapper extends Component {
506
517
  const { splitDirection, terminals, sftpPathFollowSsh } = this.state
507
518
  const { props } = this
508
519
  const { pane, enableSsh, type } = props.tab
509
- if (type === terminalRdpType) {
520
+ if (type === terminalRdpType || type === terminalVncType) {
510
521
  return null
511
522
  }
512
523
  const termType = props.tab?.type
@@ -59,6 +59,11 @@
59
59
  .web-session-wrap
60
60
  height 100vh
61
61
  background main
62
- .rdp-session-wrap
62
+ .session-v-wrap
63
63
  background main
64
- overflow scroll
64
+ overflow scroll
65
+ z-index 99
66
+ .vnc-session-wrap > div
67
+ display block !important
68
+ width auto !important
69
+ height auto !important
@@ -15,11 +15,13 @@ import TabHistory from './tab-history'
15
15
  import TabQuickCommands from './tab-quick-commands'
16
16
  import TabSettings from './tab-settings'
17
17
  import TabThemes from './tab-themes'
18
+ import TabProfiles from './tab-profiles'
18
19
 
19
20
  const { prefix } = window
20
21
  const m = prefix('common')
21
22
  const t = prefix('terminalThemes')
22
23
  const q = prefix('quickCommands')
24
+ const f = prefix('profiles')
23
25
 
24
26
  export default class SettingModalWrap extends Component {
25
27
  selectItem = (item) => {
@@ -92,6 +94,11 @@ export default class SettingModalWrap extends Component {
92
94
  key: settingMap.quickCommands,
93
95
  label: q(settingMap.quickCommands),
94
96
  children: null
97
+ },
98
+ {
99
+ key: settingMap.profiles,
100
+ label: f(settingMap.profiles),
101
+ children: null
95
102
  }
96
103
  ]
97
104
  const tabsProps = {
@@ -139,6 +146,13 @@ export default class SettingModalWrap extends Component {
139
146
  store={store}
140
147
  settingTab={settingTab}
141
148
  />
149
+ <TabProfiles
150
+ listProps={props0}
151
+ settingItem={settingItem}
152
+ formProps={formProps}
153
+ store={store}
154
+ settingTab={settingTab}
155
+ />
142
156
  </div>
143
157
  )
144
158
  }
@@ -71,7 +71,7 @@ export default class SettingTerminal extends Component {
71
71
  }
72
72
 
73
73
  handleChangeDelMode = v => this.onChangeValue(v, 'backspaceMode')
74
- handleChangeRenderType = v => this.onChangeValue(v, 'renderType')
74
+ handleChangeRenderType = v => this.onChangeValue(v, 'rendererType')
75
75
 
76
76
  handleChangeFont = (values) => {
77
77
  this.onChangeValue(
@@ -0,0 +1,38 @@
1
+ import SettingCol from './col'
2
+ import ProfileForm from '../profile/profile-form'
3
+ import ProfileList from '../profile/profile-list'
4
+ import {
5
+ settingMap
6
+ } from '../../common/constants'
7
+
8
+ export default function TabProfiles (props) {
9
+ const {
10
+ settingTab
11
+ } = props
12
+ if (settingTab !== settingMap.profiles) {
13
+ return null
14
+ }
15
+ const {
16
+ settingItem,
17
+ listProps,
18
+ formProps,
19
+ store
20
+ } = props
21
+ return (
22
+ <div
23
+ className='setting-tabs-profile'
24
+ >
25
+ <SettingCol>
26
+ <ProfileList
27
+ {...listProps}
28
+ quickCommandId={store.quickCommandId}
29
+ />
30
+ <ProfileForm
31
+ {...formProps}
32
+ quickCommandTags={store.quickCommandTags}
33
+ key={settingItem.id}
34
+ />
35
+ </SettingCol>
36
+ </div>
37
+ )
38
+ }
@@ -6,7 +6,7 @@
6
6
  * - click header to sort
7
7
  */
8
8
 
9
- import React from 'react'
9
+ import { Component } from '../common/react-subx'
10
10
  import classnames from 'classnames'
11
11
  import { isEqual, pick, find, isNull, isArray, isUndefined } from 'lodash-es'
12
12
  import generate from '../../common/uid'
@@ -17,24 +17,32 @@ import {
17
17
  maxDragMove,
18
18
  sftpControlHeight,
19
19
  eventTypes,
20
- paneMap,
21
- commonActions
20
+ paneMap
22
21
  } from '../../common/constants'
23
22
  import copy from 'json-deep-copy'
24
23
  import FileSection from './file-item'
25
24
  import PagedList from './paged-list'
26
25
  import {
27
26
  DownOutlined,
28
- UpOutlined
27
+ UpOutlined,
28
+ CheckOutlined
29
29
  } from '@ant-design/icons'
30
+ import IconHolder from '../context-menu/icon-holder'
30
31
 
31
32
  const { prefix } = window
32
33
  const e = prefix('sftp')
33
34
 
34
- export default class FileListTable extends React.Component {
35
+ export default class FileListTable extends Component {
35
36
  constructor (props) {
36
37
  super(props)
37
- this.state = this.initFromProps()
38
+ this.state = {
39
+ ...this.initFromProps(),
40
+ showContextMenu: false,
41
+ contextMenuPos: {
42
+ left: 0,
43
+ top: 0
44
+ }
45
+ }
38
46
  }
39
47
 
40
48
  componentDidMount () {
@@ -61,10 +69,37 @@ export default class FileListTable extends React.Component {
61
69
  }
62
70
 
63
71
  componentWillUnmount () {
64
- window.removeEventListener('message', this.onContextAction)
65
72
  window.removeEventListener('message', this.onMsg)
66
73
  }
67
74
 
75
+ setOnCloseEvent = () => {
76
+ const dom = document
77
+ .querySelector('.ant-drawer')
78
+ if (dom) {
79
+ dom.addEventListener('click', this.onTriggerClose)
80
+ }
81
+ document
82
+ .getElementById('outside-context')
83
+ .addEventListener('click', this.onTriggerClose)
84
+ }
85
+
86
+ onTriggerClose = (e) => {
87
+ if (e.target.closest('.context-menu')) {
88
+ return null
89
+ }
90
+ this.setState({
91
+ showContextMenu: false
92
+ })
93
+ const dom = document
94
+ .querySelector('.ant-drawer')
95
+ if (dom) {
96
+ dom.removeEventListener('click', this.onTriggerClose)
97
+ }
98
+ document
99
+ .getElementById('outside-context')
100
+ .removeEventListener('click', this.onTriggerClose)
101
+ }
102
+
68
103
  toVisible = (prevProps, props) => {
69
104
  return (
70
105
  prevProps.pane === paneMap.ssh ||
@@ -128,7 +163,7 @@ export default class FileListTable extends React.Component {
128
163
  }
129
164
 
130
165
  getPropsDefault = () => {
131
- return [
166
+ return this.props.store.config.filePropsEnabled || [
132
167
  'name',
133
168
  'size',
134
169
  'modifyTime'
@@ -229,10 +264,7 @@ export default class FileListTable extends React.Component {
229
264
  }
230
265
 
231
266
  computePos = (e, height) => {
232
- return {
233
- left: e.clientX,
234
- top: e.clientY
235
- }
267
+ return e.target.getBoundingClientRect()
236
268
  }
237
269
 
238
270
  onToggleProp = name => {
@@ -244,41 +276,22 @@ export default class FileListTable extends React.Component {
244
276
  : [...names, name]
245
277
  const props = all.filter(g => newProps.includes(g))
246
278
  const update = this.initFromProps(props)
279
+ this.props.store.setConfig({
280
+ filePropsEnabled: props
281
+ })
247
282
  this.setState(update)
248
283
  }
249
284
 
250
- onContextAction = e => {
251
- const {
252
- action,
253
- id,
254
- args = [],
255
- func
256
- } = e.data || {}
257
- if (
258
- action !== commonActions.clickContextMenu ||
259
- id !== this.uid ||
260
- !this[func]
261
- ) {
262
- return false
263
- }
264
- window.removeEventListener('message', this.onContextAction)
265
- this[func](...args)
266
- }
267
-
268
285
  handleContextMenu = e => {
269
286
  e && e.preventDefault()
270
- const items = this.renderContext()
271
287
  const pos = e
272
288
  ? this.computePos(e)
273
289
  : this.pos
274
- this.pos = pos
275
- this.uid = generate()
276
- window.store.openContextMenu({
277
- id: this.uid,
278
- items,
279
- pos
290
+ this.setState({
291
+ contextMenuPos: pos,
292
+ showContextMenu: true
280
293
  })
281
- window.addEventListener('message', this.onContextAction)
294
+ this.setOnCloseEvent()
282
295
  }
283
296
 
284
297
  onClickName = (e) => {
@@ -318,18 +331,23 @@ export default class FileListTable extends React.Component {
318
331
  const selected = selectedNames.includes(p)
319
332
  const disabled = !i
320
333
  const cls = classnames(
334
+ 'context-item',
321
335
  { selected },
322
336
  { unselected: !selected }
323
337
  )
324
- return {
325
- func: 'onToggleProp',
326
- icon: disabled || selected ? 'CheckOutlined' : 'IconHolder',
327
- text: e(p),
328
- disabled,
329
- args: [p],
330
- noCloseMenu: true,
331
- className: cls
338
+ const icon = disabled || selected ? <CheckOutlined /> : <IconHolder />
339
+ const obj = {
340
+ className: cls,
341
+ onClick: () => this.onToggleProp(p)
332
342
  }
343
+ return (
344
+ <div
345
+ {...obj}
346
+ key={p}
347
+ >
348
+ {icon} {e(p)}
349
+ </div>
350
+ )
333
351
  })
334
352
  }
335
353
 
@@ -519,6 +537,40 @@ export default class FileListTable extends React.Component {
519
537
  )
520
538
  }
521
539
 
540
+ renderContextMenu = () => {
541
+ const {
542
+ showContextMenu
543
+ } = this.state
544
+ if (!showContextMenu) {
545
+ return null
546
+ }
547
+ const {
548
+ left,
549
+ top
550
+ } = this.state.contextMenuPos
551
+ const outerProps = {
552
+ className: 'context-menu file-header-context-menu',
553
+ style: {
554
+ left: left + 'px',
555
+ top: top + 'px'
556
+ }
557
+ }
558
+ const innerProps = {
559
+ className: 'context-menu-inner'
560
+ }
561
+ return (
562
+ <div
563
+ {...outerProps}
564
+ >
565
+ <div
566
+ {...innerProps}
567
+ >
568
+ {this.renderContext()}
569
+ </div>
570
+ </div>
571
+ )
572
+ }
573
+
522
574
  render () {
523
575
  const { fileList, height, type } = this.props
524
576
  const tableHeaderHeight = 30
@@ -539,6 +591,7 @@ export default class FileListTable extends React.Component {
539
591
  return (
540
592
  <div className={cls}>
541
593
  {this.renderTableHeader()}
594
+ {this.renderContextMenu()}
542
595
  <div
543
596
  {...props}
544
597
  >
@@ -990,6 +990,7 @@ export default class Sftp extends Component {
990
990
  const loading = this.state[`${type}Loading`]
991
991
  const { host, username } = this.props.tab
992
992
  const listProps = {
993
+ store: window.store,
993
994
  id,
994
995
  type,
995
996
  ...this.props,
@@ -188,4 +188,7 @@
188
188
 
189
189
  .pager-wrap
190
190
  z-index 4
191
- position relative
191
+ position relative
192
+ .file-header-context-menu
193
+ position fixed
194
+ z-index 999
@@ -2,7 +2,7 @@ import createTitle from '../../common/create-title'
2
2
 
3
3
  export function createTransferProps (props) {
4
4
  return {
5
- title: createTitle(props.tab),
5
+ title: createTitle(props.tab, false),
6
6
  tabId: props.tab.id,
7
7
  sessionId: props.sessionId
8
8
  }
@@ -1027,7 +1027,7 @@ class Term extends Component {
1027
1027
  }
1028
1028
  const startFolder = startDirectory || window.initFolder
1029
1029
  if (startFolder) {
1030
- const cmd = `cd ${startFolder}\r`
1030
+ const cmd = `cd "${startFolder}"\r`
1031
1031
  this.attachAddon._sendData(cmd)
1032
1032
  }
1033
1033
  if (runScripts && runScripts.length) {
@@ -1049,8 +1049,6 @@ class Term extends Component {
1049
1049
  }
1050
1050
  }
1051
1051
 
1052
- count = 0
1053
-
1054
1052
  setStatus = status => {
1055
1053
  const id = this.props.tab?.id
1056
1054
  this.props.editTab(id, {
@@ -1094,7 +1092,7 @@ class Term extends Component {
1094
1092
  server = ''
1095
1093
  } = config
1096
1094
  const { sessionId, terminalIndex, id, logName } = this.props
1097
- const tab = deepCopy(this.props.tab || {})
1095
+ const tab = window.store.applyProfile(deepCopy(this.props.tab || {}))
1098
1096
  const {
1099
1097
  srcId, from = 'bookmarks',
1100
1098
  type,