@electerm/electerm-react 5.0.6 → 5.0.7

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/common/constants.js +2 -1
  2. package/client/common/fetch-from-server.js +12 -2
  3. package/client/components/common/drawer.styl +1 -1
  4. package/client/components/footer/footer-entry.jsx +24 -4
  5. package/client/components/footer/footer.styl +1 -1
  6. package/client/components/layout/layout.jsx +13 -8
  7. package/client/components/main/main.jsx +6 -2
  8. package/client/components/quick-commands/qm.styl +1 -1
  9. package/client/components/quick-commands/quick-commands-box.jsx +3 -2
  10. package/client/components/session/session.jsx +1 -1
  11. package/client/components/session/sessions.jsx +1 -1
  12. package/client/components/setting-panel/setting-modal.jsx +1 -1
  13. package/client/components/setting-panel/setting-wrap.jsx +1 -2
  14. package/client/components/setting-panel/setting-wrap.styl +1 -1
  15. package/client/components/setting-panel/setting.styl +1 -1
  16. package/client/components/sidebar/bookmark-select.jsx +2 -2
  17. package/client/components/sidebar/index.jsx +23 -7
  18. package/client/components/sidebar/side-panel.jsx +3 -3
  19. package/client/components/sidebar/sidebar.styl +6 -1
  20. package/client/components/terminal/shell.js +10 -2
  21. package/client/components/terminal/terminal-apis.js +4 -4
  22. package/client/components/terminal/terminal-command-dropdown.jsx +1 -0
  23. package/client/components/terminal/terminal.jsx +50 -17
  24. package/client/components/tree-list/tree-list-row.jsx +2 -2
  25. package/client/components/tree-list/tree-list.jsx +1 -1
  26. package/client/css/mobile.styl +2 -1
  27. package/client/store/common.js +15 -3
  28. package/client/store/init-state.js +9 -2
  29. package/client/store/store.js +12 -3
  30. package/package.json +1 -1
@@ -147,8 +147,9 @@ export const terminalFtpType = 'ftp'
147
147
  export const terminalSpiceType = 'spice'
148
148
  export const openedSidebarKey = 'opened-sidebar'
149
149
  export const sidebarPinnedKey = 'sidebar-pinned'
150
+ export const leftSideBarOpenKey = 'left-side-bar-open'
150
151
  export const pinnedQuickCommandBarKey = 'pinned-quick-command-bar'
151
- export const leftSidebarWidthKey = 'left-sidebar-width'
152
+ export const leftSidePanelWidthKey = 'left-sidebar-width'
152
153
  export const rightSidebarWidthKey = 'right-sidebar-width'
153
154
  export const addPanelWidthLsKey = 'addPanelWidth'
154
155
  export const sftpDefaultSortSettingKey = 'sftp-default-sort'
@@ -27,7 +27,8 @@ export const initWsCommon = async () => {
27
27
 
28
28
  window.pre.ipcOnEvent('power-resume', initWsCommon)
29
29
 
30
- const wsFetch = async (data) => {
30
+ const wsFetch = async (data, options = {}) => {
31
+ const { silent = false } = options
31
32
  if (!window.et.wsOpened) {
32
33
  await initWsCommon()
33
34
  }
@@ -35,7 +36,16 @@ const wsFetch = async (data) => {
35
36
  return new NewPromise((resolve, reject) => {
36
37
  window.et.commonWs.once((arg) => {
37
38
  if (arg.error) {
38
- console.error('fetch error', arg.error)
39
+ // Rejections are propagated to the caller via the returned promise, so
40
+ // callers decide severity. Many fetches (shell detection, owner lookup,
41
+ // folder size) are best-effort and catch gracefully; logging every one
42
+ // at error level with a stack dump surfaces scary-but-harmless failures
43
+ // (e.g. an SSH server rejecting an auxiliary exec channel). Default to a
44
+ // low-key warning carrying only the message; best-effort probes pass
45
+ // { silent: true } to suppress this and log their own clearer message.
46
+ if (!silent) {
47
+ console.warn('fetch error:', arg.error.message || arg.error)
48
+ }
39
49
  return reject(new Error(arg.error.message))
40
50
  }
41
51
  resolve(arg.data)
@@ -1,7 +1,7 @@
1
1
  .custom-drawer
2
2
  position fixed
3
3
  top 0
4
- left 43px
4
+ left var(--left-side-bar-width, 43px)
5
5
  right 0
6
6
  bottom 0
7
7
 
@@ -3,7 +3,7 @@ import {
3
3
  Select,
4
4
  Dropdown
5
5
  } from 'antd'
6
- import { InfoCircleOutlined, TranslationOutlined } from '@ant-design/icons'
6
+ import { InfoCircleOutlined, TranslationOutlined, DoubleRightOutlined } from '@ant-design/icons'
7
7
  import './footer.styl'
8
8
  import { statusMap } from '../../common/constants'
9
9
  import BatchInput from './batch-input'
@@ -164,12 +164,29 @@ export default auto(function FooterEntry (props) {
164
164
  )
165
165
  }
166
166
 
167
+ function handleShowSidebar () {
168
+ window.store.toggleLeftSideBar()
169
+ }
170
+
167
171
  const {
168
- leftSidebarWidth,
172
+ leftSidePanelWidth,
173
+ leftSideBarWidth,
169
174
  openedSideBar,
170
175
  inActiveTerminal
171
176
  } = props.store
172
- const w = 43 + leftSidebarWidth
177
+ const w = leftSideBarWidth + leftSidePanelWidth
178
+ // icon bar hidden: show a control on the left of the footer to bring the
179
+ // sidebar back
180
+ const showSidebarIcon = leftSideBarWidth === 0
181
+ ? (
182
+ <div className='terminal-footer-unit terminal-footer-show-sidebar'>
183
+ <DoubleRightOutlined
184
+ className='pointer font18 show-sidebar-icon'
185
+ onClick={handleShowSidebar}
186
+ />
187
+ </div>
188
+ )
189
+ : null
173
190
  const sideProps = openedSideBar
174
191
  ? {
175
192
  className: 'main-footer',
@@ -184,12 +201,15 @@ export default auto(function FooterEntry (props) {
184
201
  !inActiveTerminal
185
202
  ) {
186
203
  return (
187
- <div className='main-footer' {...sideProps} />
204
+ <div className='main-footer' {...sideProps}>
205
+ {showSidebarIcon}
206
+ </div>
188
207
  )
189
208
  }
190
209
  return (
191
210
  <div {...sideProps}>
192
211
  <div className='terminal-footer-flex'>
212
+ {showSidebarIcon}
193
213
  {!isAIDisabled() && renderAIIcon()}
194
214
  {renderCmdHistory()}
195
215
  {renderQuickCommands()}
@@ -2,7 +2,7 @@
2
2
  background var(--main)
3
3
  height 36px
4
4
  position absolute
5
- left 44px
5
+ left calc(var(--left-side-bar-width, 43px) + 1px)
6
6
  right 0
7
7
  bottom 0
8
8
  z-index 200
@@ -33,7 +33,8 @@ export default auto(function Layout (props) {
33
33
  height,
34
34
  pinnedQuickCommandBar,
35
35
  // tabsHeight,
36
- leftSidebarWidth,
36
+ leftSidePanelWidth,
37
+ leftSideBarWidth,
37
38
  // infoPanelPinned,
38
39
  pinned,
39
40
  rightPanelVisible,
@@ -43,7 +44,7 @@ export default auto(function Layout (props) {
43
44
  inActiveTerminal
44
45
  } = props.store
45
46
  const h = height - footerHeight - (inActiveTerminal && pinnedQuickCommandBar ? quickCommandBoxHeight : 0) + resizeTrigger
46
- const l = pinned ? 43 + leftSidebarWidth : 43
47
+ const l = pinned ? leftSideBarWidth + leftSidePanelWidth : leftSideBarWidth
47
48
  const r = rightPanelVisible && rightPanelPinned ? rightPanelWidth : 0
48
49
  return {
49
50
  height: h,
@@ -59,15 +60,18 @@ export default auto(function Layout (props) {
59
60
  height,
60
61
  width,
61
62
  pinnedQuickCommandBar,
62
- leftSidebarWidth,
63
+ leftSidePanelWidth,
64
+ leftSideBarWidth,
63
65
  rightPanelVisible,
64
66
  rightPanelPinned,
65
67
  rightPanelWidth,
66
68
  pinned
67
69
  } = props.store
68
- const l = pinned ? leftSidebarWidth : 0
70
+ const l = pinned ? leftSidePanelWidth : 0
69
71
  const r = rightPanelPinned && rightPanelVisible ? rightPanelWidth : 0
70
- const w = width - l - r - 42
72
+ // account for the far-left icon bar (sidebarWidth - 1px border on desktop;
73
+ // 0 when the bar is hidden on mobile)
74
+ const w = width - l - r - (leftSideBarWidth > 0 ? leftSideBarWidth - 1 : 0)
71
75
  const h = height - footerHeight - (pinnedQuickCommandBar ? quickCommandBoxHeight : 0)
72
76
  return layoutAlg(layout, w, h)
73
77
  }
@@ -115,7 +119,7 @@ export default auto(function Layout (props) {
115
119
  'pinnedQuickCommandBar',
116
120
  'tabsHeight',
117
121
  'appPath',
118
- 'leftSidebarWidth',
122
+ 'leftSidePanelWidth',
119
123
  'addPanelWidth',
120
124
  'pinned',
121
125
  'openedSideBar',
@@ -153,7 +157,8 @@ export default auto(function Layout (props) {
153
157
  'pinnedQuickCommandBar',
154
158
  'qmSortByFrequency',
155
159
  'inActiveTerminal',
156
- 'leftSidebarWidth',
160
+ 'leftSidePanelWidth',
161
+ 'leftSideBarWidth',
157
162
  'openedSideBar',
158
163
  'currentQuickCommands'
159
164
  ])
@@ -177,7 +182,7 @@ export default auto(function Layout (props) {
177
182
  'pinnedQuickCommandBar',
178
183
  'tabsHeight',
179
184
  'appPath',
180
- 'leftSidebarWidth',
185
+ 'leftSidePanelWidth',
181
186
  'pinned',
182
187
  'openedSideBar',
183
188
  'config',
@@ -119,7 +119,10 @@ export default auto(function Index (props) {
119
119
  'is-desktop': !store.isMobile
120
120
  })
121
121
  const ext1 = {
122
- className: cls
122
+ className: cls,
123
+ style: {
124
+ '--left-side-bar-width': store.leftSideBarWidth + 'px'
125
+ }
123
126
  }
124
127
  // Get active tab IDs
125
128
  const activeTabIds = [
@@ -168,7 +171,8 @@ export default auto(function Index (props) {
168
171
  'settingTab',
169
172
  'settingItem',
170
173
  'isSyncingSetting',
171
- 'leftSidebarWidth',
174
+ 'leftSidePanelWidth',
175
+ 'leftSideBarWidth',
172
176
  'transferTab',
173
177
  'sidebarPanelTab',
174
178
  'openWidgetsModal'
@@ -5,7 +5,7 @@
5
5
  .qm-wrap-tooltip
6
6
  background var(--main)
7
7
  position absolute
8
- left 43px
8
+ left var(--left-side-bar-width, 43px)
9
9
  right 0
10
10
  z-index 89
11
11
  height auto
@@ -176,7 +176,8 @@ export default function QuickCommandsFooterBox (props) {
176
176
  pinnedQuickCommandBar,
177
177
  qmSortByFrequency,
178
178
  inActiveTerminal,
179
- leftSidebarWidth,
179
+ leftSidePanelWidth,
180
+ leftSideBarWidth,
180
181
  openedSideBar
181
182
  } = props
182
183
  if ((!openQuickCommandBar && !pinnedQuickCommandBar) || !inActiveTerminal) {
@@ -203,7 +204,7 @@ export default function QuickCommandsFooterBox (props) {
203
204
  : 'text'
204
205
  const cls = classNames('qm-list-wrap')
205
206
  const type = qmSortByFrequency ? 'primary' : 'default'
206
- const w = openedSideBar ? 43 + leftSidebarWidth : 43
207
+ const w = openedSideBar ? leftSideBarWidth + leftSidePanelWidth : leftSideBarWidth
207
208
  const qmProps = {
208
209
  className: 'qm-wrap-tooltip',
209
210
  style: {
@@ -278,7 +278,7 @@ export default class SessionWrapper extends Component {
278
278
  'height',
279
279
  'width',
280
280
  'tabsHeight',
281
- 'leftSidebarWidth',
281
+ 'leftSidePanelWidth',
282
282
  'pinned',
283
283
  'openedSideBar',
284
284
  'delTab',
@@ -74,7 +74,7 @@ export default class Sessions extends Component {
74
74
  'pinnedQuickCommandBar',
75
75
  'tabsHeight',
76
76
  'appPath',
77
- 'leftSidebarWidth',
77
+ 'leftSidePanelWidth',
78
78
  'pinned',
79
79
  'openedSideBar',
80
80
  'fullscreen'
@@ -70,7 +70,7 @@ export default auto(function SettingModalWrap (props) {
70
70
  'config',
71
71
  'checkedKeys',
72
72
  'expandedKeys',
73
- 'leftSidebarWidth',
73
+ 'leftSidePanelWidth',
74
74
  'initLoadingData'
75
75
  ])
76
76
  }
@@ -5,7 +5,6 @@
5
5
  import { Component } from 'react'
6
6
  import Drawer from '../common/drawer'
7
7
  import { CloseCircleOutlined } from '@ant-design/icons'
8
- import { sidebarWidth } from '../../common/constants'
9
8
  import AppDrag from '../tabs/app-drag'
10
9
  import './setting-wrap.styl'
11
10
 
@@ -33,7 +32,7 @@ export default class SettingWrap extends Component {
33
32
  open: this.props.visible,
34
33
  onClose: this.props.onCancel,
35
34
  className: 'setting-wrap',
36
- size: this.props.innerWidth - sidebarWidth,
35
+ size: this.props.innerWidth - window.store.leftSideBarWidth,
37
36
  zIndex: 888,
38
37
  placement: 'left'
39
38
  }
@@ -72,7 +72,7 @@
72
72
  position fixed
73
73
  top 128px
74
74
  bottom 0
75
- left 43px
75
+ left var(--left-side-bar-width, 43px)
76
76
  right 0
77
77
  overflow-y scroll
78
78
  .setting-row
@@ -7,7 +7,7 @@
7
7
  .setting-wrap .app-drag
8
8
  position fixed
9
9
  top 0
10
- left 43px
10
+ left var(--left-side-bar-width, 43px)
11
11
  height 60px
12
12
  right 0
13
13
  z-index 888
@@ -10,7 +10,7 @@ export default auto(function BookmarkSelect (props) {
10
10
  const {
11
11
  listStyle,
12
12
  openedSideBar,
13
- leftSidebarWidth,
13
+ leftSidePanelWidth,
14
14
  expandedKeys,
15
15
  bookmarks,
16
16
  bookmarksMap,
@@ -38,7 +38,7 @@ export default auto(function BookmarkSelect (props) {
38
38
  bookmarksMap,
39
39
  bookmarkGroups: store.getBookmarkGroupsTotal(),
40
40
  expandedKeys,
41
- leftSidebarWidth,
41
+ leftSidePanelWidth,
42
42
  bookmarkGroupTree: store.bookmarkGroupTree,
43
43
  autoFocus,
44
44
  initLoadingData
@@ -8,7 +8,8 @@ import {
8
8
  UpCircleOutlined,
9
9
  AppstoreOutlined,
10
10
  ThunderboltOutlined,
11
- AimOutlined
11
+ AimOutlined,
12
+ MenuFoldOutlined
12
13
  } from '@ant-design/icons'
13
14
  import { Tooltip, Popover } from 'antd'
14
15
  import SideBarPanel from './sidebar-panel'
@@ -16,7 +17,6 @@ import TransferList from './transfer-list'
16
17
  import MenuBtn from '../sys-menu/menu-btn'
17
18
  import QuickConnect from '../tabs/quick-connect'
18
19
  import {
19
- sidebarWidth,
20
20
  settingMap,
21
21
  modals
22
22
  } from '../../common/constants'
@@ -34,7 +34,10 @@ export default function Sidebar (props) {
34
34
  settingTab,
35
35
  settingItem,
36
36
  isSyncingSetting,
37
- leftSidebarWidth,
37
+ // expandable bookmarks/history panel
38
+ leftSidePanelWidth,
39
+ // far-left icon bar (43px; 0 when hidden)
40
+ leftSideBarWidth,
38
41
  pinned,
39
42
  fileTransfers,
40
43
  openedSideBar,
@@ -91,6 +94,10 @@ export default function Sidebar (props) {
91
94
  store.onZoomReset()
92
95
  }
93
96
 
97
+ const handleToggleSidebar = () => {
98
+ store.toggleLeftSideBar()
99
+ }
100
+
94
101
  const {
95
102
  onNewSsh,
96
103
  openSetting,
@@ -115,16 +122,16 @@ export default function Sidebar (props) {
115
122
  ? {
116
123
  className: 'sidebar-list',
117
124
  style: {
118
- width: `${leftSidebarWidth}px`
125
+ width: `${leftSidePanelWidth}px`
119
126
  }
120
127
  }
121
128
  : {
122
129
  className: 'sidebar-list'
123
130
  }
124
131
  const sidebarProps = {
125
- className: `sidebar type-${openedSideBar}`,
132
+ className: `sidebar type-${openedSideBar}${leftSideBarWidth === 0 ? ' collapsed' : ''}`,
126
133
  style: {
127
- width: sidebarWidth,
134
+ width: leftSideBarWidth,
128
135
  height
129
136
  }
130
137
  }
@@ -209,6 +216,14 @@ export default function Sidebar (props) {
209
216
  onClick={openAbout}
210
217
  />
211
218
  </SideIcon>
219
+ <SideIcon
220
+ title={e('hide')}
221
+ >
222
+ <MenuFoldOutlined
223
+ className='iblock font16 control-icon hide-sidebar-icon'
224
+ onClick={handleToggleSidebar}
225
+ />
226
+ </SideIcon>
212
227
  {
213
228
  Math.round((zoom ?? 1) * 100) !== 100
214
229
  ? (
@@ -246,7 +261,8 @@ export default function Sidebar (props) {
246
261
  <SidePanel
247
262
  sideProps={sideProps}
248
263
  setLeftSidePanelWidth={setLeftSidePanelWidth}
249
- leftSidebarWidth={leftSidebarWidth}
264
+ leftSidePanelWidth={leftSidePanelWidth}
265
+ leftSideBarWidth={leftSideBarWidth}
250
266
  >
251
267
  <SideBarPanel
252
268
  pinned={pinned}
@@ -15,13 +15,13 @@ export default function SidePanel (props) {
15
15
  }
16
16
  const el1 = document.querySelector('.sessions')
17
17
  if (el1) {
18
- el1.style.left = (nw + 43) + 'px'
18
+ el1.style.left = (nw + props.leftSideBarWidth) + 'px'
19
19
  }
20
- }, [props.leftSidebarWidth])
20
+ }, [props.leftSidePanelWidth, props.leftSideBarWidth])
21
21
  const dragProps = {
22
22
  min: 343,
23
23
  max: 600,
24
- width: props.leftSidebarWidth,
24
+ width: props.leftSidePanelWidth,
25
25
  onDragEnd,
26
26
  onDragMove,
27
27
  left: true
@@ -93,12 +93,17 @@
93
93
  color var(--text-light)
94
94
  .sidebar-list
95
95
  position absolute
96
- left 43px
96
+ left var(--left-side-bar-width, 43px)
97
97
  top 0
98
98
  bottom 0
99
99
  z-index 200
100
100
  width 0
101
101
 
102
+ // mobile: icon bar hidden — hide its buttons
103
+ .sidebar.collapsed
104
+ .sidebar-bar
105
+ display none
106
+
102
107
  .pinned
103
108
  .sidebar-list
104
109
  top 0
@@ -147,9 +147,17 @@ export async function detectRemoteShell (pid) {
147
147
  // instead of probing for any shell binary installed on the host.
148
148
  const cmd = 'printf "%s\n" "$SHELL"'
149
149
 
150
- const r = await runCmd(pid, cmd)
150
+ // { silent: true } so this best-effort probe does not emit a transport-level
151
+ // fetch warning; a single, clearer warning is logged below if it fails.
152
+ const r = await runCmd(pid, cmd, { silent: true })
151
153
  .catch((err) => {
152
- console.error('detectRemoteShell error', err)
154
+ // Non-fatal: the interactive shell already opened, so the terminal keeps
155
+ // working. We just can't inject OSC 633 shell integration for command
156
+ // tracking. This commonly happens when the server limits concurrent
157
+ // sessions (sshd MaxSessions) or the account uses a forced/restricted
158
+ // command, which rejects the auxiliary exec channel with
159
+ // "(SSH) Channel open failure: open failed".
160
+ console.warn('detectRemoteShell: exec channel rejected by server, shell integration disabled, falling back to sh —', err?.message || err)
153
161
  return 'sh'
154
162
  })
155
163
 
@@ -11,21 +11,21 @@ export function createTerm (body) {
11
11
  })
12
12
  }
13
13
 
14
- export function runCmd (pid, cmd) {
14
+ export function runCmd (pid, cmd, options) {
15
15
  return fetch({
16
16
  pid,
17
17
  cmd,
18
18
  action: 'run-cmd'
19
- })
19
+ }, options)
20
20
  }
21
21
 
22
- export function execCmd (pid, cmd, timeoutMs) {
22
+ export function execCmd (pid, cmd, timeoutMs, options) {
23
23
  return fetch({
24
24
  pid,
25
25
  cmd,
26
26
  timeoutMs,
27
27
  action: 'exec-cmd'
28
- })
28
+ }, options)
29
29
  }
30
30
 
31
31
  export function resizeTerm (pid, cols, rows) {
@@ -73,6 +73,7 @@ export default class TerminalCmdSuggestions extends Component {
73
73
  config.baseURLAI,
74
74
  config.apiPathAI,
75
75
  config.apiKeyAI,
76
+ config.proxyAI,
76
77
  false,
77
78
  config.authHeaderNameAI
78
79
  ).catch(
@@ -166,13 +166,21 @@ class Term extends Component {
166
166
  !isEqual(
167
167
  pick(this.props, names),
168
168
  pick(prevProps, names)
169
- ) ||
170
- shouldChange
169
+ )
171
170
  ) {
172
171
  this.onResize()
173
172
  }
174
173
  if (shouldChange && this.term) {
175
174
  this.term.focus()
175
+ // 标签从 display:none 变为可见时,延迟到下一动画帧再 fit,读取正确布局
176
+ // 尺寸纠正列数;并用 refresh 强制重绘,清除快速连续打开多个连接时隐藏
177
+ // 标签被 0 列 fit 导致的虚假/重复提示符。
178
+ requestAnimationFrame(() => {
179
+ if (this.term && !this.onClose) {
180
+ this.fitAndRefresh()
181
+ setTimeout(() => this.fitAndRefresh(), 80)
182
+ }
183
+ })
176
184
  }
177
185
  this.checkConfigChange(
178
186
  prevProps,
@@ -1530,7 +1538,11 @@ class Term extends Component {
1530
1538
  this.term = term
1531
1539
  term.onSelectionChange(this.onSelectionChange)
1532
1540
  term.attachCustomKeyEventHandler(this.handleKeyboardEvent.bind(this))
1533
- this.fitAddon.fit()
1541
+ // 容器不可见(隐藏标签 display:none)时不 fit,避免算出 0 列把 shell
1542
+ // 提示符逐字符错误换行;待标签激活可见后由 fitAndRefresh 重新适配。
1543
+ if (this.isElementVisible()) {
1544
+ this.fitAddon.fit()
1545
+ }
1534
1546
  await this.remoteInit(term)
1535
1547
  }
1536
1548
 
@@ -1888,7 +1900,10 @@ class Term extends Component {
1888
1900
  this.trzszClient.init(socket)
1889
1901
  this.xmodemClient = new XmodemClient(this)
1890
1902
  this.xmodemClient.init(socket)
1891
- this.fitAddon.fit()
1903
+ // 仅在可见时 fit,隐藏标签跳过,避免 0 列损坏提示符。
1904
+ if (this.isElementVisible()) {
1905
+ this.fitAddon.fit()
1906
+ }
1892
1907
  term.displayRaw = displayRaw
1893
1908
  term.loadAddon(
1894
1909
  new KeywordHighlighterAddon(keywords)
@@ -1952,20 +1967,38 @@ class Term extends Component {
1952
1967
  })
1953
1968
  }
1954
1969
 
1955
- onResize = throttle(() => {
1956
- const cid = this.props.currentBatchTabId
1957
- const tid = this.props.tab?.id
1958
- if (
1959
- this.props.tab.status === statusMap.success &&
1960
- cid === tid &&
1961
- this.term
1962
- ) {
1963
- try {
1964
- this.fitAddon.fit()
1965
- } catch (e) {
1966
- console.info('resize failed')
1967
- }
1970
+ // 判断终端挂载容器当前是否真实可见(非 display:none)。
1971
+ // 隐藏标签的 clientWidth/clientHeight 为 0,此时不应 fit,否则会算出 0 列。
1972
+ isElementVisible = () => {
1973
+ const el = this.domRef.current
1974
+ if (!el) {
1975
+ return false
1968
1976
  }
1977
+ return el.clientWidth > 0 && el.clientHeight > 0
1978
+ }
1979
+
1980
+ // 重新适配终端尺寸并强制重绘可见区域。
1981
+ // 仅当容器真正可见时才 fit:隐藏标签(display:none)下 clientWidth 为 0,
1982
+ // 若在此时 fit 会算出 0/极小列数,使 shell 提示符被逐字符错误换行,
1983
+ // 出现快速连续打开多个连接时的虚假/重复提示符。因此隐藏态跳过 fit,
1984
+ // 保留 xterm 默认 80x24,待标签激活可见后再 fit 重绘,从根上避免换行损坏。
1985
+ fitAndRefresh = () => {
1986
+ if (!this.term || !this.fitAddon || this.onClose) {
1987
+ return
1988
+ }
1989
+ if (!this.isElementVisible()) {
1990
+ return
1991
+ }
1992
+ try {
1993
+ this.fitAddon.fit()
1994
+ this.term.refresh(0, this.term.rows - 1)
1995
+ } catch (e) {
1996
+ console.info('resize failed', e)
1997
+ }
1998
+ }
1999
+
2000
+ onResize = throttle(() => {
2001
+ this.fitAndRefresh()
1969
2002
  }, 200)
1970
2003
 
1971
2004
  onerrorSocket = err => {
@@ -12,7 +12,7 @@ export default function TreeListRow (props) {
12
12
  activeItemId,
13
13
  searchSelectedRowKey,
14
14
  staticList,
15
- leftSidebarWidth,
15
+ leftSidePanelWidth,
16
16
  handleExpand,
17
17
  handleUnExpand,
18
18
  del,
@@ -43,7 +43,7 @@ export default function TreeListRow (props) {
43
43
  itemColor: item?.color,
44
44
  itemDescription: item?.description,
45
45
  itemLevel: item?.level,
46
- leftSidebarWidth,
46
+ leftSidePanelWidth,
47
47
  staticList,
48
48
  selectedItemId: activeItemId,
49
49
  searchSelected: searchSelectedRowKey === row.key,
@@ -748,7 +748,7 @@ export default class ItemListTree extends Component {
748
748
  activeItemId={this.props.activeItemId}
749
749
  searchSelectedRowKey={this.state.searchSelectedRowKey}
750
750
  staticList={this.props.staticList}
751
- leftSidebarWidth={this.props.leftSidebarWidth}
751
+ leftSidePanelWidth={this.props.leftSidePanelWidth}
752
752
  {...pick(
753
753
  this,
754
754
  [
@@ -93,7 +93,8 @@
93
93
  // ---- 3. Footer encode select: show icon, click to open dropdown
94
94
  // match antd Button small icon size (~14px) used by sibling icons
95
95
  .mobile-encode-trigger,
96
- .terminal-info-icon
96
+ .terminal-info-icon,
97
+ .show-sidebar-icon
97
98
  font-size 14px !important
98
99
  line-height 36px
99
100
  padding 0 4px
@@ -7,7 +7,8 @@ import Modal from '../components/common/modal'
7
7
  import { appendMandatoryGuardrails } from '../components/ai/ai-guardrails'
8
8
  import { debounce, some, get, pickBy } from 'lodash-es'
9
9
  import {
10
- leftSidebarWidthKey,
10
+ leftSidePanelWidthKey,
11
+ leftSideBarOpenKey,
11
12
  rightSidebarWidthKey,
12
13
  addPanelWidthLsKey,
13
14
  dismissDelKeyTipLsKey,
@@ -106,8 +107,19 @@ export default Store => {
106
107
  }
107
108
 
108
109
  Store.prototype.setLeftSidePanelWidth = function (v) {
109
- ls.setItem(leftSidebarWidthKey, v)
110
- window.store._leftSidebarWidth = v
110
+ ls.setItem(leftSidePanelWidthKey, v)
111
+ window.store._leftSidePanelWidth = v
112
+ }
113
+
114
+ Store.prototype.toggleLeftSideBar = function () {
115
+ const { store } = window
116
+ const willOpen = !store._leftSideBarOpen
117
+ store._leftSideBarOpen = willOpen
118
+ ls.setItem(leftSideBarOpenKey, willOpen ? 'true' : 'false')
119
+ if (!willOpen) {
120
+ // hiding the bar also closes/unpins the side panel so no space is reserved
121
+ store.handleCloseSidebar()
122
+ }
111
123
  }
112
124
 
113
125
  Store.prototype.setAddPanelWidth = function (v) {
@@ -9,13 +9,14 @@ import {
9
9
  infoTabs,
10
10
  openedSidebarKey,
11
11
  sidebarPinnedKey,
12
+ leftSideBarOpenKey,
12
13
  pinnedQuickCommandBarKey,
13
14
  sftpDefaultSortSettingKey,
14
15
  batchInputLsKey,
15
16
  expandedKeysLsKey,
16
17
  checkedKeysLsKey,
17
18
  localAddrBookmarkLsKey,
18
- leftSidebarWidthKey,
19
+ leftSidePanelWidthKey,
19
20
  rightSidebarWidthKey,
20
21
  addPanelWidthLsKey,
21
22
  dismissDelKeyTipLsKey,
@@ -46,6 +47,10 @@ function getDefaultBookmarkGroups (bookmarks) {
46
47
 
47
48
  export default () => {
48
49
  const layout = ls.getItem('layout') || splitMap.c1
50
+ // far-left icon bar is open by default; once the user toggles it, honor the
51
+ // saved preference ('true'/'false') from localStorage.
52
+ const storedSideBarOpen = ls.getItem(leftSideBarOpenKey)
53
+ const leftSideBarOpen = storedSideBarOpen === '' ? true : storedSideBarOpen === 'true'
49
54
  return {
50
55
  // common
51
56
  wsInited: false,
@@ -164,7 +169,9 @@ export default () => {
164
169
 
165
170
  // sidebar
166
171
  openedSideBar: ls.getItem(openedSidebarKey) || '',
167
- _leftSidebarWidth: parseInt(ls.getItem(leftSidebarWidthKey), 10) || 300,
172
+ _leftSidePanelWidth: parseInt(ls.getItem(leftSidePanelWidthKey), 10) || 300,
173
+ // whether the far-left icon bar is open (hide/show works on every platform)
174
+ _leftSideBarOpen: leftSideBarOpen,
168
175
  addPanelWidth: parseInt(ls.getItem(addPanelWidthLsKey), 10) || 300,
169
176
  menuOpened: false,
170
177
  pinned: ls.getItem(sidebarPinnedKey) === 'true',
@@ -59,12 +59,21 @@ class Store {
59
59
  return window.store.innerWidth
60
60
  }
61
61
 
62
- get leftSidebarWidth () {
62
+ get leftSidePanelWidth () {
63
63
  const { store } = window
64
64
  if (store.isMobile) {
65
- return Math.min(store.innerWidth - sidebarWidth, 500)
65
+ return Math.min(store.innerWidth - store.leftSideBarWidth, 500)
66
66
  }
67
- return store._leftSidebarWidth
67
+ return store._leftSidePanelWidth
68
+ }
69
+
70
+ // width of the far-left icon bar (the ~43px vertical strip with the menu
71
+ // / bookmarks / settings icons). Distinct from leftSidePanelWidth, which is
72
+ // the expandable bookmarks/history panel. Controlled by _leftSideBarOpen on
73
+ // every platform: sidebarWidth when open, 0 when hidden.
74
+ get leftSideBarWidth () {
75
+ const { store } = window
76
+ return store._leftSideBarOpen ? sidebarWidth : 0
68
77
  }
69
78
 
70
79
  get rightPanelWidth () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/electerm-react",
3
- "version": "5.0.6",
3
+ "version": "5.0.7",
4
4
  "description": "react components src for electerm",
5
5
  "main": "./client/components/main/main.jsx",
6
6
  "license": "MIT",