@electerm/electerm-react 3.15.198 → 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.
- package/client/common/constants.js +2 -1
- package/client/common/fetch-from-server.js +12 -2
- package/client/components/common/drawer.styl +1 -1
- package/client/components/footer/footer-entry.jsx +24 -4
- package/client/components/footer/footer.styl +1 -1
- package/client/components/layout/layout.jsx +13 -8
- package/client/components/main/main.jsx +6 -2
- package/client/components/quick-commands/qm.styl +1 -1
- package/client/components/quick-commands/quick-commands-box.jsx +3 -2
- package/client/components/session/session.jsx +1 -1
- package/client/components/session/sessions.jsx +1 -1
- package/client/components/setting-panel/setting-modal.jsx +1 -1
- package/client/components/setting-panel/setting-wrap.jsx +1 -2
- package/client/components/setting-panel/setting-wrap.styl +1 -1
- package/client/components/setting-panel/setting.styl +1 -1
- package/client/components/sftp/sftp-entry.jsx +36 -55
- package/client/components/sidebar/bookmark-select.jsx +2 -2
- package/client/components/sidebar/index.jsx +23 -7
- package/client/components/sidebar/side-panel.jsx +3 -3
- package/client/components/sidebar/sidebar.styl +6 -1
- package/client/components/terminal/shell.js +10 -2
- package/client/components/terminal/terminal-apis.js +4 -4
- package/client/components/terminal/terminal-command-dropdown.jsx +1 -0
- package/client/components/terminal/terminal.jsx +50 -17
- package/client/components/tree-list/tree-list-row.jsx +2 -2
- package/client/components/tree-list/tree-list.jsx +1 -1
- package/client/components/vnc/vnc-session.jsx +65 -2
- package/client/css/mobile.styl +2 -1
- package/client/store/common.js +15 -3
- package/client/store/init-state.js +9 -2
- package/client/store/store.js +12 -3
- 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
|
|
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
|
-
|
|
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)
|
|
@@ -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
|
-
|
|
172
|
+
leftSidePanelWidth,
|
|
173
|
+
leftSideBarWidth,
|
|
169
174
|
openedSideBar,
|
|
170
175
|
inActiveTerminal
|
|
171
176
|
} = props.store
|
|
172
|
-
const w =
|
|
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()}
|
|
@@ -33,7 +33,8 @@ export default auto(function Layout (props) {
|
|
|
33
33
|
height,
|
|
34
34
|
pinnedQuickCommandBar,
|
|
35
35
|
// tabsHeight,
|
|
36
|
-
|
|
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 ?
|
|
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
|
-
|
|
63
|
+
leftSidePanelWidth,
|
|
64
|
+
leftSideBarWidth,
|
|
63
65
|
rightPanelVisible,
|
|
64
66
|
rightPanelPinned,
|
|
65
67
|
rightPanelWidth,
|
|
66
68
|
pinned
|
|
67
69
|
} = props.store
|
|
68
|
-
const l = pinned ?
|
|
70
|
+
const l = pinned ? leftSidePanelWidth : 0
|
|
69
71
|
const r = rightPanelPinned && rightPanelVisible ? rightPanelWidth : 0
|
|
70
|
-
|
|
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
|
-
'
|
|
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
|
-
'
|
|
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
|
-
'
|
|
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
|
-
'
|
|
174
|
+
'leftSidePanelWidth',
|
|
175
|
+
'leftSideBarWidth',
|
|
172
176
|
'transferTab',
|
|
173
177
|
'sidebarPanelTab',
|
|
174
178
|
'openWidgetsModal'
|
|
@@ -176,7 +176,8 @@ export default function QuickCommandsFooterBox (props) {
|
|
|
176
176
|
pinnedQuickCommandBar,
|
|
177
177
|
qmSortByFrequency,
|
|
178
178
|
inActiveTerminal,
|
|
179
|
-
|
|
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 ?
|
|
207
|
+
const w = openedSideBar ? leftSideBarWidth + leftSidePanelWidth : leftSideBarWidth
|
|
207
208
|
const qmProps = {
|
|
208
209
|
className: 'qm-wrap-tooltip',
|
|
209
210
|
style: {
|
|
@@ -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 -
|
|
35
|
+
size: this.props.innerWidth - window.store.leftSideBarWidth,
|
|
37
36
|
zIndex: 888,
|
|
38
37
|
placement: 'left'
|
|
39
38
|
}
|
|
@@ -10,7 +10,6 @@ import { isEqual, last, isNumber, some, isArray, pick, uniq, debounce } from 'lo
|
|
|
10
10
|
import FileSection from './file-item'
|
|
11
11
|
import resolve from '../../common/resolve'
|
|
12
12
|
import wait from '../../common/wait'
|
|
13
|
-
import isAbsPath from '../../common/is-absolute-path'
|
|
14
13
|
import classnames from 'classnames'
|
|
15
14
|
import sorterIndex from '../../common/index-sorter'
|
|
16
15
|
import { handleErr } from '../../common/fetch'
|
|
@@ -107,8 +106,6 @@ export default class Sftp extends Component {
|
|
|
107
106
|
this.sftp = null
|
|
108
107
|
clearTimeout(this.timer4)
|
|
109
108
|
this.timer4 = null
|
|
110
|
-
clearTimeout(this.timer5)
|
|
111
|
-
this.timer5 = null
|
|
112
109
|
// Clear sort cache to prevent memory leaks
|
|
113
110
|
this._sortCache?.clear()
|
|
114
111
|
this._lastSortArgs = null
|
|
@@ -455,9 +452,7 @@ export default class Sftp extends Component {
|
|
|
455
452
|
}
|
|
456
453
|
const type = files[0]?.type || _type
|
|
457
454
|
const func = this[type + 'Del']
|
|
458
|
-
|
|
459
|
-
await func(f)
|
|
460
|
-
}
|
|
455
|
+
await Promise.all(files.map(f => func(f)))
|
|
461
456
|
if (type === typeMap.remote) {
|
|
462
457
|
await wait(500)
|
|
463
458
|
}
|
|
@@ -560,8 +555,10 @@ export default class Sftp extends Component {
|
|
|
560
555
|
}
|
|
561
556
|
|
|
562
557
|
initRemoteAll = async () => {
|
|
563
|
-
await
|
|
564
|
-
|
|
558
|
+
await Promise.all([
|
|
559
|
+
this.remoteList(),
|
|
560
|
+
this.remoteListOwner().catch(e => console.debug('remoteListOwner error:', e))
|
|
561
|
+
])
|
|
565
562
|
}
|
|
566
563
|
|
|
567
564
|
modifier = (...args) => {
|
|
@@ -647,12 +644,10 @@ export default class Sftp extends Component {
|
|
|
647
644
|
}
|
|
648
645
|
|
|
649
646
|
remoteListOwner = async () => {
|
|
650
|
-
const remoteUidTree = await
|
|
651
|
-
this.props.pid
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
this.props.pid
|
|
655
|
-
)
|
|
647
|
+
const [remoteUidTree, remoteGidTree] = await Promise.all([
|
|
648
|
+
owner.remoteListUsers(this.props.pid),
|
|
649
|
+
owner.remoteListGroups(this.props.pid)
|
|
650
|
+
])
|
|
656
651
|
this.setState({
|
|
657
652
|
remoteGidTree,
|
|
658
653
|
remoteUidTree
|
|
@@ -660,8 +655,10 @@ export default class Sftp extends Component {
|
|
|
660
655
|
}
|
|
661
656
|
|
|
662
657
|
localListOwner = async () => {
|
|
663
|
-
const localUidTree = await
|
|
664
|
-
|
|
658
|
+
const [localUidTree, localGidTree] = await Promise.all([
|
|
659
|
+
owner.localListUsers(),
|
|
660
|
+
owner.localListGroups()
|
|
661
|
+
])
|
|
665
662
|
this.setState({
|
|
666
663
|
localGidTree,
|
|
667
664
|
localUidTree
|
|
@@ -800,14 +797,6 @@ export default class Sftp extends Component {
|
|
|
800
797
|
sftpCreated: true
|
|
801
798
|
})
|
|
802
799
|
})
|
|
803
|
-
this.timer5 = setTimeout(() => {
|
|
804
|
-
if (this.type !== 'ftp') {
|
|
805
|
-
this.updateRemoteList(remote, remotePath, sftp)
|
|
806
|
-
}
|
|
807
|
-
this.props.editTab(tab.id, {
|
|
808
|
-
sftpCreated: true
|
|
809
|
-
})
|
|
810
|
-
}, 1000)
|
|
811
800
|
} catch (e) {
|
|
812
801
|
const update = {
|
|
813
802
|
remoteLoading: false,
|
|
@@ -828,41 +817,34 @@ export default class Sftp extends Component {
|
|
|
828
817
|
remotePath,
|
|
829
818
|
sftp
|
|
830
819
|
) => {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
})
|
|
841
|
-
if (!realpath) {
|
|
842
|
-
continue
|
|
843
|
-
}
|
|
844
|
-
if (!isAbsPath(realpath)) {
|
|
845
|
-
realpath = resolve(remotePath, realpath)
|
|
846
|
-
realpath = await sftp.realpath(realpath)
|
|
820
|
+
// Resolve all symlinks in parallel for faster updates.
|
|
821
|
+
// Use sftp.stat (which follows symlinks) directly on the link path
|
|
822
|
+
// instead of readlink + realpath + stat, reducing 2-3 SFTP round
|
|
823
|
+
// trips per symlink to just 1.
|
|
824
|
+
const results = await Promise.all(
|
|
825
|
+
remotes.map(async r => {
|
|
826
|
+
if (!r.isSymbol) {
|
|
827
|
+
r.isSymbolicLink = false
|
|
828
|
+
return r
|
|
847
829
|
}
|
|
830
|
+
const linkPath = resolve(remotePath, r.name)
|
|
848
831
|
const realFileInfo = await getRemoteFileInfo(
|
|
849
832
|
sftp,
|
|
850
|
-
|
|
833
|
+
linkPath
|
|
851
834
|
).catch(e => {
|
|
852
835
|
console.debug('seems a bad symbolic link')
|
|
853
836
|
console.debug(e)
|
|
854
837
|
return null
|
|
855
838
|
})
|
|
856
839
|
if (!realFileInfo) {
|
|
857
|
-
|
|
840
|
+
return null
|
|
858
841
|
}
|
|
859
842
|
r.isSymbolicLink = true
|
|
860
843
|
r.isDirectory = realFileInfo.isDirectory
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
}
|
|
844
|
+
return r
|
|
845
|
+
})
|
|
846
|
+
)
|
|
847
|
+
const remote = results.filter(Boolean)
|
|
866
848
|
const update = {
|
|
867
849
|
remote,
|
|
868
850
|
remoteFileTree: this.buildTree(remote, typeMap.remote)
|
|
@@ -892,14 +874,13 @@ export default class Sftp extends Component {
|
|
|
892
874
|
this.getCwdLocal() ||
|
|
893
875
|
this.getLocalHome()
|
|
894
876
|
const locals = await window.fs.readdirAsync(localPath)
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
}
|
|
877
|
+
const results = await Promise.all(
|
|
878
|
+
locals.map(name => {
|
|
879
|
+
const p = resolve(localPath, name)
|
|
880
|
+
return getLocalFileInfo(p).catch(console.log)
|
|
881
|
+
})
|
|
882
|
+
)
|
|
883
|
+
const local = results.filter(Boolean)
|
|
903
884
|
const update = {
|
|
904
885
|
local,
|
|
905
886
|
inited: true,
|
|
@@ -10,7 +10,7 @@ export default auto(function BookmarkSelect (props) {
|
|
|
10
10
|
const {
|
|
11
11
|
listStyle,
|
|
12
12
|
openedSideBar,
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: `${
|
|
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:
|
|
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
|
-
|
|
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 +
|
|
18
|
+
el1.style.left = (nw + props.leftSideBarWidth) + 'px'
|
|
19
19
|
}
|
|
20
|
-
}, [props.
|
|
20
|
+
}, [props.leftSidePanelWidth, props.leftSideBarWidth])
|
|
21
21
|
const dragProps = {
|
|
22
22
|
min: 343,
|
|
23
23
|
max: 600,
|
|
24
|
-
width: props.
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
751
|
+
leftSidePanelWidth={this.props.leftSidePanelWidth}
|
|
752
752
|
{...pick(
|
|
753
753
|
this,
|
|
754
754
|
[
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from '@ant-design/icons'
|
|
16
16
|
import message from '../common/message'
|
|
17
17
|
import Modal from '../common/modal'
|
|
18
|
-
import { copy } from '../../common/clipboard'
|
|
18
|
+
import { copy, readClipboard } from '../../common/clipboard'
|
|
19
19
|
import VncForm from './vnc-form'
|
|
20
20
|
import RemoteFloatControl from '../common/remote-float-control'
|
|
21
21
|
import './vnc.styl'
|
|
@@ -49,11 +49,16 @@ export default class VncSession extends PureComponent {
|
|
|
49
49
|
|
|
50
50
|
domRef = createRef()
|
|
51
51
|
|
|
52
|
+
// clipboard sync state (local -> remote direction)
|
|
53
|
+
lastClip = null
|
|
54
|
+
vncViewOnly = false
|
|
55
|
+
|
|
52
56
|
componentDidMount () {
|
|
53
57
|
this.remoteInit()
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
componentWillUnmount () {
|
|
61
|
+
this._detachClipboardSync()
|
|
57
62
|
this.rfb && this.rfb.disconnect()
|
|
58
63
|
delete this.rfb
|
|
59
64
|
clearTimeout(this.timer)
|
|
@@ -379,6 +384,7 @@ export default class VncSession extends PureComponent {
|
|
|
379
384
|
rfb.compressionLevel = compressionLevel
|
|
380
385
|
rfb.viewOnly = viewOnly
|
|
381
386
|
rfb.showDotCursor = showDotCursor
|
|
387
|
+
this.vncViewOnly = viewOnly
|
|
382
388
|
this.rfb = rfb
|
|
383
389
|
}
|
|
384
390
|
|
|
@@ -388,12 +394,14 @@ export default class VncSession extends PureComponent {
|
|
|
388
394
|
this.setState({
|
|
389
395
|
loading: false
|
|
390
396
|
})
|
|
397
|
+
this._attachClipboardSync()
|
|
391
398
|
}
|
|
392
399
|
|
|
393
400
|
onDesktopsize = (event) => {
|
|
394
401
|
}
|
|
395
402
|
|
|
396
403
|
onDisconnect = () => {
|
|
404
|
+
this._detachClipboardSync()
|
|
397
405
|
this.setStatus(statusMap.error)
|
|
398
406
|
}
|
|
399
407
|
|
|
@@ -433,7 +441,61 @@ export default class VncSession extends PureComponent {
|
|
|
433
441
|
}
|
|
434
442
|
|
|
435
443
|
onClipboard = (event) => {
|
|
436
|
-
|
|
444
|
+
const text = event?.detail?.text
|
|
445
|
+
if (!text || text === this.lastClip) {
|
|
446
|
+
return
|
|
447
|
+
}
|
|
448
|
+
// Track so our click-driven sync won't echo this same text back to the
|
|
449
|
+
// server (some servers re-broadcast what they receive).
|
|
450
|
+
this.lastClip = text
|
|
451
|
+
copy(text)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Push the current local clipboard to the remote when it has changed.
|
|
455
|
+
// This implements the local -> remote direction that noVNC cannot do itself.
|
|
456
|
+
// Triggered on view click instead of polling, so it only runs right when the
|
|
457
|
+
// user is about to interact with the remote desktop (e.g. before pasting).
|
|
458
|
+
handleViewMouseDown = () => {
|
|
459
|
+
const rfb = this.rfb
|
|
460
|
+
if (!rfb || this.vncViewOnly) {
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
let text
|
|
464
|
+
try {
|
|
465
|
+
text = readClipboard()
|
|
466
|
+
} catch (e) {
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
if (!text || text === this.lastClip) {
|
|
470
|
+
return
|
|
471
|
+
}
|
|
472
|
+
this.lastClip = text
|
|
473
|
+
try {
|
|
474
|
+
rfb.clipboardPasteFrom(text)
|
|
475
|
+
} catch (e) {
|
|
476
|
+
// clipboardPasteFrom can throw if the socket is in a bad state; ignore
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// noVNC calls stopPropagation() on canvas mouse events in the bubble phase,
|
|
481
|
+
// so React's delegated onClick (or any bubble-phase listener) never fires.
|
|
482
|
+
// We attach a CAPTURE-phase mousedown listener on the wrapper so it runs
|
|
483
|
+
// before noVNC can stop propagation. mousedown also fires earlier than click,
|
|
484
|
+
// giving the remote clipboard time to update before the user pastes.
|
|
485
|
+
_attachClipboardSync = () => {
|
|
486
|
+
this._detachClipboardSync()
|
|
487
|
+
const el = this.domRef.current
|
|
488
|
+
if (!el) {
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
el.addEventListener('mousedown', this.handleViewMouseDown, true)
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
_detachClipboardSync = () => {
|
|
495
|
+
const el = this.domRef.current
|
|
496
|
+
if (el) {
|
|
497
|
+
el.removeEventListener('mousedown', this.handleViewMouseDown, true)
|
|
498
|
+
}
|
|
437
499
|
}
|
|
438
500
|
|
|
439
501
|
onBell = (event) => {
|
|
@@ -542,6 +604,7 @@ export default class VncSession extends PureComponent {
|
|
|
542
604
|
}
|
|
543
605
|
|
|
544
606
|
handleReInit = () => {
|
|
607
|
+
this._detachClipboardSync()
|
|
545
608
|
this.rfb?.disconnect()
|
|
546
609
|
delete this.rfb
|
|
547
610
|
this.remoteInit()
|
package/client/css/mobile.styl
CHANGED
|
@@ -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
|
package/client/store/common.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
110
|
-
window.store.
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
package/client/store/store.js
CHANGED
|
@@ -59,12 +59,21 @@ class Store {
|
|
|
59
59
|
return window.store.innerWidth
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
get
|
|
62
|
+
get leftSidePanelWidth () {
|
|
63
63
|
const { store } = window
|
|
64
64
|
if (store.isMobile) {
|
|
65
|
-
return Math.min(store.innerWidth -
|
|
65
|
+
return Math.min(store.innerWidth - store.leftSideBarWidth, 500)
|
|
66
66
|
}
|
|
67
|
-
return store.
|
|
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 () {
|