@electerm/electerm-react 2.3.103 → 2.3.114

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.
@@ -145,6 +145,7 @@ export const openedSidebarKey = 'opened-sidebar'
145
145
  export const sidebarPinnedKey = 'sidebar-pinned'
146
146
  export const leftSidebarWidthKey = 'left-sidebar-width'
147
147
  export const rightSidebarWidthKey = 'right-sidebar-width'
148
+ export const addPanelWidthLsKey = 'addPanelWidth'
148
149
  export const sftpDefaultSortSettingKey = 'sftp-default-sort'
149
150
  export const qmSortByFrequencyKey = 'qm-sort-by-frequency'
150
151
 
@@ -13,6 +13,7 @@ const vncConfig = {
13
13
  return createBaseInitValues(props, terminalVncType, {
14
14
  port: 5900,
15
15
  viewOnly: false,
16
+ clipViewport: true,
16
17
  scaleViewport: true,
17
18
  connectionHoppings: [],
18
19
  ...getAuthTypeDefault(props)
@@ -30,6 +31,7 @@ const vncConfig = {
30
31
  { type: 'input', name: 'host', label: e('host'), rules: [{ required: true, message: e('host') + ' required' }] },
31
32
  commonFields.port,
32
33
  { type: 'switch', name: 'viewOnly', label: e('viewOnly'), valuePropName: 'checked' },
34
+ { type: 'switch', name: 'clipViewport', label: e('clipViewport'), valuePropName: 'checked' },
33
35
  { type: 'switch', name: 'scaleViewport', label: e('scaleViewport'), valuePropName: 'checked' },
34
36
  { type: 'profileItem', name: '__profile__', label: '', profileFilter: d => !isEmpty(d.vnc) },
35
37
  commonFields.username,
@@ -512,7 +512,9 @@ export default class TransportAction extends Component {
512
512
 
513
513
  startTransfer = async () => {
514
514
  const { fromFile = this.fromFile, zip } = this.props.transfer
515
-
515
+ if (!fromFile) {
516
+ return
517
+ }
516
518
  if (!fromFile.isDirectory) {
517
519
  return this.transferFile()
518
520
  }
@@ -116,6 +116,7 @@ export default auto(function Layout (props) {
116
116
  'tabsHeight',
117
117
  'appPath',
118
118
  'leftSidebarWidth',
119
+ 'addPanelWidth',
119
120
  'pinned',
120
121
  'openedSideBar'
121
122
  ])
@@ -82,7 +82,7 @@ export default auto(function Index (props) {
82
82
  store.initData()
83
83
  store.checkForDbUpgrade()
84
84
  store.handleGetSerials()
85
- // window.pre.runGlobalAsync('registerDeepLink')
85
+ store.checkPendingDeepLink()
86
86
  }, [])
87
87
 
88
88
  const { store } = props
@@ -0,0 +1,138 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import { Button, message, Tooltip, Tag, Space } from 'antd'
3
+ import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'
4
+
5
+ const e = window.translate
6
+
7
+ export default function DeepLinkControl () {
8
+ const [loading, setLoading] = useState(false)
9
+ const [registrationStatus, setRegistrationStatus] = useState(null)
10
+
11
+ const checkRegistrationStatus = async () => {
12
+ try {
13
+ const status = await window.pre.runGlobalAsync('checkProtocolRegistration')
14
+ setRegistrationStatus(status)
15
+ } catch (error) {
16
+ console.error('Failed to check protocol registration:', error)
17
+ }
18
+ }
19
+
20
+ useEffect(() => {
21
+ checkRegistrationStatus()
22
+ }, [])
23
+
24
+ const handleRegister = async () => {
25
+ setLoading(true)
26
+ try {
27
+ const result = await window.pre.runGlobalAsync('registerDeepLink', true)
28
+ if (result.registered) {
29
+ message.success('Protocol handlers registered successfully')
30
+ await checkRegistrationStatus()
31
+ } else {
32
+ message.warning(e('deepLinkSkipped') || 'Registration skipped: ' + result.reason)
33
+ }
34
+ } catch (error) {
35
+ message.error('Failed to register protocol handlers')
36
+ console.error('Registration error:', error)
37
+ } finally {
38
+ setLoading(false)
39
+ }
40
+ }
41
+
42
+ const handleUnregister = async () => {
43
+ setLoading(true)
44
+ try {
45
+ await window.pre.runGlobalAsync('unregisterDeepLink')
46
+ message.success('Protocol handlers unregistered successfully')
47
+ await checkRegistrationStatus()
48
+ } catch (error) {
49
+ message.error('Failed to unregister protocol handlers')
50
+ console.error('Unregistration error:', error)
51
+ } finally {
52
+ setLoading(false)
53
+ }
54
+ }
55
+
56
+ const isAnyProtocolRegistered = () => {
57
+ if (!registrationStatus) return false
58
+ return Object.values(registrationStatus).some(status => status === true)
59
+ }
60
+
61
+ const isAllProtocolsRegistered = () => {
62
+ if (!registrationStatus) return false
63
+ return Object.values(registrationStatus).every(status => status === true)
64
+ }
65
+
66
+ const renderTooltipContent = () => {
67
+ const protocols = ['ssh', 'telnet', 'rdp', 'vnc', 'serial']
68
+
69
+ return (
70
+ <div>
71
+ <div className='pd1b'>
72
+ Register electerm to handle protocol URLs (ssh://, telnet://, rdp://, vnc://, serial://)
73
+ </div>
74
+
75
+ {registrationStatus && (
76
+ <>
77
+ <div className='pd1b'>
78
+ Protocol Status
79
+ </div>
80
+ <div className='pd1b'>
81
+ <Space size='small' wrap>
82
+ {protocols.map(protocol => {
83
+ const isRegistered = registrationStatus[protocol]
84
+ return (
85
+ <Tag
86
+ key={protocol}
87
+ icon={isRegistered ? <CheckCircleOutlined /> : <CloseCircleOutlined />}
88
+ color={isRegistered ? 'success' : 'default'}
89
+ >
90
+ {protocol}://
91
+ </Tag>
92
+ )
93
+ })}
94
+ </Space>
95
+ </div>
96
+ </>
97
+ )}
98
+ </div>
99
+ )
100
+ }
101
+
102
+ const isRegistered = isAnyProtocolRegistered()
103
+ const isAllRegistered = isAllProtocolsRegistered()
104
+
105
+ return (
106
+ <div className='pd2b'>
107
+ <Tooltip
108
+ title={renderTooltipContent()}
109
+ >
110
+ <Space>
111
+ {
112
+ !isAllRegistered && (
113
+ <Button
114
+ type='primary'
115
+ onClick={handleRegister}
116
+ loading={loading}
117
+ >
118
+ {e('registerDeepLink')}
119
+ </Button>
120
+ )
121
+ }
122
+ {
123
+ isRegistered && (
124
+ <Button
125
+ color='danger'
126
+ variant='solid'
127
+ onClick={handleUnregister}
128
+ loading={loading}
129
+ >
130
+ {e('unregisterDeepLink')}
131
+ </Button>
132
+ )
133
+ }
134
+ </Space>
135
+ </Tooltip>
136
+ </div>
137
+ )
138
+ }
@@ -31,6 +31,7 @@ import StartSession from './start-session-select'
31
31
  import HelpIcon from '../common/help-icon'
32
32
  import delay from '../../common/wait.js'
33
33
  import isColorDark from '../../common/is-color-dark'
34
+ import DeepLinkControl from './deep-link-control'
34
35
  import './setting.styl'
35
36
 
36
37
  const { Option } = Select
@@ -675,6 +676,9 @@ export default class SettingCommon extends Component {
675
676
  'debug'
676
677
  ].map(this.renderToggle)
677
678
  }
679
+ {
680
+ window.et.isWebApp ? null : <DeepLinkControl />
681
+ }
678
682
  {this.renderLoginPass()}
679
683
  {this.renderReset()}
680
684
  </div>
@@ -271,16 +271,7 @@ export default class FileSection extends React.Component {
271
271
  document.querySelectorAll('.' + onDragOverCls).forEach((d) => {
272
272
  removeClass(d, onDragOverCls)
273
273
  })
274
- if (e && e.dataTransfer) {
275
- const dt = e.dataTransfer
276
- if (dt.items) {
277
- // Use DataTransferItemList interface to remove the drag data
278
- for (let i = 0, len = dt.items.length; i < len; i++) {
279
- dt.items.remove(i)
280
- }
281
- }
282
- dt.clearData()
283
- }
274
+ e && e.dataTransfer && e.dataTransfer.clearData()
284
275
  }
285
276
 
286
277
  onDropFile = async (fromFiles, toFile, fromFileManager) => {
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Add button menu component
3
+ */
4
+
5
+ import React, { useCallback } from 'react'
6
+ import {
7
+ CodeFilled,
8
+ RightSquareFilled
9
+ } from '@ant-design/icons'
10
+ import BookmarksList from '../sidebar/bookmark-select'
11
+ import DragHandle from '../common/drag-handle'
12
+
13
+ const e = window.translate
14
+
15
+ export default function AddBtnMenu ({
16
+ menuRef,
17
+ menuPosition,
18
+ menuTop,
19
+ menuLeft,
20
+ onMenuScroll,
21
+ onTabAdd,
22
+ batch,
23
+ addPanelWidth,
24
+ setAddPanelWidth
25
+ }) {
26
+ const { onNewSsh } = window.store
27
+ const cls = 'pd2x pd1y context-item pointer'
28
+ const addTabBtn = window.store.hasNodePty
29
+ ? (
30
+ <div
31
+ className={cls}
32
+ onClick={onTabAdd}
33
+ >
34
+ <RightSquareFilled /> {e('newTab')}
35
+ </div>
36
+ )
37
+ : null
38
+
39
+ const onDragEnd = useCallback((nw) => {
40
+ if (setAddPanelWidth) {
41
+ setAddPanelWidth(nw)
42
+ }
43
+ }, [setAddPanelWidth])
44
+
45
+ const onDragMove = useCallback((nw) => {
46
+ if (menuRef.current) {
47
+ menuRef.current.style.width = nw + 'px'
48
+ }
49
+ }, [menuRef])
50
+
51
+ const dragProps = {
52
+ min: 300,
53
+ max: 600,
54
+ width: addPanelWidth || 300,
55
+ onDragEnd,
56
+ onDragMove,
57
+ left: menuPosition === 'right'
58
+ }
59
+
60
+ console.log('render add btn menu', dragProps)
61
+
62
+ return (
63
+ <div
64
+ ref={menuRef}
65
+ className={`add-menu-wrap add-menu-${menuPosition}`}
66
+ style={{
67
+ maxHeight: window.innerHeight - menuTop - 50,
68
+ top: menuTop,
69
+ left: menuLeft,
70
+ width: addPanelWidth ? addPanelWidth + 'px' : undefined
71
+ }}
72
+ onScroll={onMenuScroll}
73
+ >
74
+ <DragHandle
75
+ {...dragProps}
76
+ />
77
+ <div
78
+ className={cls}
79
+ onClick={onNewSsh}
80
+ >
81
+ <CodeFilled /> {e('newBookmark')}
82
+ </div>
83
+ {addTabBtn}
84
+ <BookmarksList
85
+ store={window.store}
86
+ />
87
+ </div>
88
+ )
89
+ }
@@ -5,11 +5,9 @@
5
5
  import React, { Component } from 'react'
6
6
  import { createPortal } from 'react-dom'
7
7
  import {
8
- CodeFilled,
9
- PlusOutlined,
10
- RightSquareFilled
8
+ PlusOutlined
11
9
  } from '@ant-design/icons'
12
- import BookmarksList from '../sidebar/bookmark-select'
10
+ import AddBtnMenu from './add-btn-menu'
13
11
  import classNames from 'classnames'
14
12
  import hasActiveInput from '../../common/has-active-input'
15
13
  import './add-btn.styl'
@@ -93,43 +91,23 @@ export default class AddBtn extends Component {
93
91
  }
94
92
 
95
93
  renderMenus = () => {
96
- const { onNewSsh } = window.store
97
- const cls = 'pd2x pd1y context-item pointer'
98
- const addTabBtn = window.store.hasNodePty
99
- ? (
100
- <div
101
- className={cls}
102
- onClick={this.handleTabAdd}
103
- >
104
- <RightSquareFilled /> {e('newTab')}
105
- </div>
106
- )
107
- : null
108
-
109
94
  const { menuPosition, menuTop, menuLeft } = this.state
95
+ const addBtnMenuProps = {
96
+ menuRef: this.menuRef,
97
+ menuPosition,
98
+ menuTop,
99
+ menuLeft,
100
+ onMenuScroll: this.handleMenuScroll,
101
+ onTabAdd: this.handleTabAdd,
102
+ batch: this.props.batch,
103
+ addPanelWidth: this.props.addPanelWidth,
104
+ setAddPanelWidth: window.store.setAddPanelWidth
105
+ }
110
106
 
111
107
  return (
112
- <div
113
- ref={this.menuRef}
114
- className={`add-menu-wrap add-menu-${menuPosition}`}
115
- style={{
116
- maxHeight: window.innerHeight - menuTop - 50,
117
- top: menuTop,
118
- left: menuLeft
119
- }}
120
- onScroll={this.handleMenuScroll}
121
- >
122
- <div
123
- className={cls}
124
- onClick={onNewSsh}
125
- >
126
- <CodeFilled /> {e('newBookmark')}
127
- </div>
128
- {addTabBtn}
129
- <BookmarksList
130
- store={window.store}
131
- />
132
- </div>
108
+ <AddBtnMenu
109
+ {...addBtnMenuProps}
110
+ />
133
111
  )
134
112
  }
135
113
 
@@ -6,8 +6,12 @@
6
6
  border 1px solid var(--main-lighter)
7
7
  border-radius 6px
8
8
  box-shadow 0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 1px var(--main-darker)
9
- min-width 160px
10
- max-width 300px
9
+ min-width 300px
10
+ max-width 600px
11
11
  overflow-y auto
12
12
  margin-top 4px
13
13
  padding 4px 15px
14
+ .drag-handle
15
+ right 3px
16
+ display block
17
+
@@ -199,6 +199,7 @@ export default class Tabs extends Component {
199
199
  className={cls}
200
200
  empty={!this.props.tabs?.length}
201
201
  batch={this.props.batch}
202
+ addPanelWidth={this.props.addPanelWidth}
202
203
  />
203
204
  )
204
205
  }
@@ -201,8 +201,6 @@
201
201
  &:hover
202
202
  color var(--text)
203
203
 
204
- .add-menu-wrap
205
- overflow-y scroll
206
204
  .window-controls
207
205
  .window-control-minimize
208
206
  .window-control-maximize
@@ -79,9 +79,11 @@ export default class VncSession extends RdpSession {
79
79
  term: terminalType,
80
80
  viewOnly = false,
81
81
  scaleViewport = true,
82
+ clipViewport = false,
82
83
  username,
83
84
  password
84
85
  } = tab
86
+ console.log('vnc tab', tab)
85
87
  const opts = clone({
86
88
  term: terminalType || config.terminalType,
87
89
  tabId: id,
@@ -113,6 +115,7 @@ export default class VncSession extends RdpSession {
113
115
  const { width, height } = this.state
114
116
  const wsUrl = `${pre}://${hs}/vnc/${pid}?token=${tokenElecterm}&width=${width}&height=${height}`
115
117
  const vncOpts = {
118
+ clipViewport,
116
119
  scaleViewport,
117
120
  viewOnly,
118
121
  style: {
@@ -9,6 +9,7 @@ import {
9
9
  modals,
10
10
  leftSidebarWidthKey,
11
11
  rightSidebarWidthKey,
12
+ addPanelWidthLsKey,
12
13
  dismissDelKeyTipLsKey,
13
14
  connectionMap,
14
15
  settingMap,
@@ -133,6 +134,11 @@ export default Store => {
133
134
  window.store.leftSidebarWidth = v
134
135
  }
135
136
 
137
+ Store.prototype.setAddPanelWidth = function (v) {
138
+ ls.setItem(addPanelWidthLsKey, v)
139
+ window.store.addPanelWidth = v
140
+ }
141
+
136
142
  Store.prototype.setRightSidePanelWidth = function (v) {
137
143
  ls.setItem(rightSidebarWidthKey, v)
138
144
  window.store.rightPanelWidth = v
@@ -16,6 +16,7 @@ import {
16
16
  localAddrBookmarkLsKey,
17
17
  leftSidebarWidthKey,
18
18
  rightSidebarWidthKey,
19
+ addPanelWidthLsKey,
19
20
  dismissDelKeyTipLsKey,
20
21
  qmSortByFrequencyKey,
21
22
  resolutionsLsKey,
@@ -151,6 +152,7 @@ export default () => {
151
152
  // sidebar
152
153
  openedSideBar: ls.getItem(openedSidebarKey),
153
154
  leftSidebarWidth: parseInt(ls.getItem(leftSidebarWidthKey), 10) || 300,
155
+ addPanelWidth: parseInt(ls.getItem(addPanelWidthLsKey), 10) || 300,
154
156
  menuOpened: false,
155
157
  pinned: ls.getItem(sidebarPinnedKey) === 'true',
156
158
 
@@ -45,7 +45,9 @@ export async function addTabFromCommandLine (store, opts) {
45
45
  options,
46
46
  argv
47
47
  } = opts
48
- store.commandLineHelp = helpInfo
48
+ if (helpInfo) {
49
+ store.commandLineHelp = helpInfo
50
+ }
49
51
  if (isHelp) {
50
52
  return store.openAbout(infoTabs.cmd)
51
53
  }
@@ -205,4 +207,10 @@ export default (Store) => {
205
207
  Store.prototype.addTabFromCommandLine = (event, opts) => {
206
208
  addTabFromCommandLine(window.store, opts)
207
209
  }
210
+ Store.prototype.checkPendingDeepLink = async function () {
211
+ const pending = await window.pre.runGlobalAsync('getPendingDeepLink')
212
+ if (pending) {
213
+ addTabFromCommandLine(window.store, pending)
214
+ }
215
+ }
208
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "2.3.103",
3
+ "version": "2.3.114",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",