@electerm/electerm-react 3.15.152 → 3.15.155
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 +1 -0
- package/client/components/bookmark-form/bookmark-form.styl +9 -0
- package/client/components/bookmark-form/common/run-scripts.jsx +1 -1
- package/client/components/footer/batch-input.jsx +94 -107
- package/client/components/footer/footer-entry.jsx +27 -4
- package/client/components/footer/footer.styl +17 -13
- package/client/components/layout/layout.jsx +2 -1
- package/client/components/main/main.jsx +3 -1
- package/client/components/quick-commands/qm.styl +24 -1
- package/client/components/quick-commands/quick-command-item.jsx +1 -1
- package/client/components/quick-commands/quick-commands-list-form.jsx +1 -1
- package/client/components/session/session.jsx +7 -3
- package/client/components/setting-panel/deep-link-control.jsx +2 -2
- package/client/components/setting-panel/setting-modal.jsx +1 -0
- package/client/components/setting-panel/setting-passwords.jsx +60 -88
- package/client/components/setting-panel/setting-wrap.jsx +13 -4
- package/client/components/setting-panel/setting.styl +37 -1
- package/client/components/setting-sync/data-import.jsx +1 -1
- package/client/components/sidebar/sidebar-panel.jsx +5 -0
- package/client/components/sidebar/transfer-list.jsx +1 -1
- package/client/components/tabs/index.jsx +73 -0
- package/client/css/mobile.styl +131 -5
- package/client/store/common.js +12 -3
- package/client/store/init-state.js +3 -1
- package/client/store/sidebar.js +9 -0
- package/package.json +1 -1
|
@@ -197,6 +197,7 @@ export const commonLineEndings = commonTxLineEndings
|
|
|
197
197
|
|
|
198
198
|
export const maxBatchInput = 30
|
|
199
199
|
export const windowControlWidth = 94
|
|
200
|
+
export const mobileBreakpoint = 800
|
|
200
201
|
export const baseUpdateCheckUrls = [
|
|
201
202
|
packInfo.homepage,
|
|
202
203
|
'https://gitee.com/github-zxdong262/electerm/raw/gh-pages'
|
|
@@ -16,6 +16,15 @@
|
|
|
16
16
|
.number-input
|
|
17
17
|
min-width 210px
|
|
18
18
|
|
|
19
|
+
// On mobile, stack loginScriptDelay and loginScript vertically instead of
|
|
20
|
+
// horizontally (the antd Space renders them side by side by default).
|
|
21
|
+
.is-mobile
|
|
22
|
+
.run-scripts-space.ant-space
|
|
23
|
+
flex-direction column
|
|
24
|
+
align-items stretch
|
|
25
|
+
.ant-space-item
|
|
26
|
+
width 100%
|
|
27
|
+
|
|
19
28
|
.tree-select-wrapper
|
|
20
29
|
display flex
|
|
21
30
|
flex-direction column
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* batch input module
|
|
3
|
+
*
|
|
4
|
+
* Unified click-to-open behavior for mobile and desktop:
|
|
5
|
+
* - click the compact trigger -> expand the textarea + tab-select panel
|
|
6
|
+
* - click outside the panel -> collapse it
|
|
7
|
+
* - press Enter in the textarea -> send `cmd` to all selected tabs
|
|
8
|
+
*
|
|
9
|
+
* On desktop the compact trigger is rendered as a read-only Input that only
|
|
10
|
+
* serves as a clickable affordance — it is never a real input. On mobile it
|
|
11
|
+
* is a small "B" button to save horizontal space. The open/close logic is
|
|
12
|
+
* identical for both.
|
|
3
13
|
*/
|
|
4
14
|
|
|
5
|
-
import { Component } from 'react'
|
|
15
|
+
import { Component, createRef } from 'react'
|
|
6
16
|
import {
|
|
7
|
-
|
|
17
|
+
Button,
|
|
8
18
|
Input
|
|
9
19
|
} from 'antd'
|
|
10
20
|
import {
|
|
11
|
-
batchInputLsKey,
|
|
12
21
|
terminalWebType,
|
|
13
22
|
terminalRdpType,
|
|
14
23
|
terminalVncType
|
|
@@ -24,97 +33,71 @@ export default class BatchInput extends Component {
|
|
|
24
33
|
super(props)
|
|
25
34
|
this.state = {
|
|
26
35
|
cmd: '',
|
|
27
|
-
open: false
|
|
28
|
-
enter: false
|
|
36
|
+
open: false
|
|
29
37
|
}
|
|
38
|
+
this.outerRef = createRef()
|
|
39
|
+
this.textAreaRef = createRef()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
componentDidMount () {
|
|
43
|
+
document.addEventListener('mousedown', this.handleDocClick)
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
componentWillUnmount () {
|
|
33
|
-
|
|
47
|
+
document.removeEventListener('mousedown', this.handleDocClick)
|
|
34
48
|
}
|
|
35
49
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
// Collapse the panel when the user clicks anywhere outside of it.
|
|
51
|
+
// Clicks inside an antd Popover (e.g. the tab-select popover, which renders
|
|
52
|
+
// in a portal outside this node) are treated as "inside" so interacting with
|
|
53
|
+
// tab selection does not collapse the panel.
|
|
54
|
+
handleDocClick = (ev) => {
|
|
55
|
+
if (!this.state.open) {
|
|
40
56
|
return
|
|
41
57
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
cmd: '',
|
|
46
|
-
open: false
|
|
47
|
-
})
|
|
48
|
-
e.stopPropagation()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
handleChange = (v = '') => {
|
|
52
|
-
let vv = v.replace(/^\d+:/, '').replace(/\n$/, '')
|
|
53
|
-
if (vv === batchInputLsKey) {
|
|
54
|
-
window.store.clearBatchInput()
|
|
55
|
-
vv = ''
|
|
58
|
+
const node = this.outerRef.current
|
|
59
|
+
if (node && node.contains(ev.target)) {
|
|
60
|
+
return
|
|
56
61
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
open: false
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
handleClick = () => {
|
|
64
|
-
this.setState({
|
|
65
|
-
open: true
|
|
66
|
-
})
|
|
67
|
-
window.store.filterBatchInputSelectedTabIds()
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
handleBlur = () => {
|
|
71
|
-
this.setState({
|
|
72
|
-
open: false
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
mapper = (v, i) => {
|
|
77
|
-
return {
|
|
78
|
-
value: `${i}:${v}`,
|
|
79
|
-
label: v
|
|
62
|
+
if (ev.target && ev.target.closest && ev.target.closest('.ant-popover')) {
|
|
63
|
+
return
|
|
80
64
|
}
|
|
65
|
+
this.setState({ open: false })
|
|
81
66
|
}
|
|
82
67
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
handleToggle = () => {
|
|
69
|
+
const willOpen = !this.state.open
|
|
70
|
+
if (willOpen) {
|
|
71
|
+
window.store.filterBatchInputSelectedTabIds()
|
|
87
72
|
}
|
|
73
|
+
this.setState({ open: willOpen }, () => {
|
|
74
|
+
if (willOpen && this.textAreaRef.current) {
|
|
75
|
+
this.textAreaRef.current.focus()
|
|
76
|
+
}
|
|
77
|
+
})
|
|
88
78
|
}
|
|
89
79
|
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
this.renderClear()
|
|
96
|
-
]
|
|
80
|
+
handleSubmit = (ev) => {
|
|
81
|
+
const { batchInputSelectedTabIds } = this.props
|
|
82
|
+
const { cmd } = this.state
|
|
83
|
+
if (!cmd.trim()) {
|
|
84
|
+
return
|
|
97
85
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
handleMouseEnter = () => {
|
|
102
|
-
clearTimeout(this.timer)
|
|
86
|
+
window.store.addBatchInput(cmd)
|
|
87
|
+
this.props.input(cmd, Array.from(batchInputSelectedTabIds))
|
|
103
88
|
this.setState({
|
|
104
|
-
|
|
89
|
+
cmd: '',
|
|
90
|
+
open: false
|
|
105
91
|
})
|
|
92
|
+
ev?.stopPropagation()
|
|
106
93
|
}
|
|
107
94
|
|
|
108
|
-
|
|
95
|
+
handleChange = (ev) => {
|
|
109
96
|
this.setState({
|
|
110
|
-
|
|
97
|
+
cmd: ev.target.value
|
|
111
98
|
})
|
|
112
99
|
}
|
|
113
100
|
|
|
114
|
-
handleMouseLeave = () => {
|
|
115
|
-
this.timer = setTimeout(this.leave, 5000)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
101
|
getTabs = () => {
|
|
119
102
|
const { activeTabId } = this.props
|
|
120
103
|
return deepCopy(this.props.tabs.filter(tab => {
|
|
@@ -122,43 +105,54 @@ export default class BatchInput extends Component {
|
|
|
122
105
|
tab.type !== terminalRdpType &&
|
|
123
106
|
tab.type !== terminalVncType
|
|
124
107
|
})).sort((a, b) => {
|
|
125
|
-
//
|
|
108
|
+
// current tab goes first
|
|
126
109
|
if (a.id === activeTabId) return -1
|
|
127
110
|
if (b.id === activeTabId) return 1
|
|
128
111
|
return 0
|
|
129
112
|
})
|
|
130
113
|
}
|
|
131
114
|
|
|
132
|
-
|
|
133
|
-
const {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
115
|
+
renderTrigger () {
|
|
116
|
+
const { isMobile } = this.props
|
|
117
|
+
if (isMobile) {
|
|
118
|
+
return (
|
|
119
|
+
<Button
|
|
120
|
+
size='small'
|
|
121
|
+
type='text'
|
|
122
|
+
onClick={this.handleToggle}
|
|
123
|
+
>
|
|
124
|
+
B
|
|
125
|
+
</Button>
|
|
126
|
+
)
|
|
144
127
|
}
|
|
128
|
+
// Desktop: an input-shaped, read-only affordance. It is NOT a real input —
|
|
129
|
+
// clicking it expands the panel where the user actually types.
|
|
130
|
+
return (
|
|
131
|
+
<Input
|
|
132
|
+
size='small'
|
|
133
|
+
readOnly
|
|
134
|
+
placeholder={e('batchInput')}
|
|
135
|
+
className='batch-input-holder'
|
|
136
|
+
style={{ cursor: 'pointer' }}
|
|
137
|
+
onClick={this.handleToggle}
|
|
138
|
+
/>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
render () {
|
|
143
|
+
const { open } = this.state
|
|
144
|
+
const { batchInputSelectedTabIds } = this.props
|
|
145
145
|
const cls = classNames(
|
|
146
146
|
'batch-input-outer',
|
|
147
|
-
{
|
|
148
|
-
'bi-show': open || enter
|
|
149
|
-
}
|
|
147
|
+
{ 'bi-show': open }
|
|
150
148
|
)
|
|
151
|
-
const inputProps = {
|
|
152
|
-
size: 'small',
|
|
153
|
-
placeholder: e('batchInput'),
|
|
154
|
-
className: 'batch-input-holder'
|
|
155
|
-
}
|
|
156
149
|
const textAreaProps = {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
150
|
+
ref: this.textAreaRef,
|
|
151
|
+
onPressEnter: this.handleSubmit,
|
|
152
|
+
onChange: this.handleChange,
|
|
153
|
+
value: this.state.cmd,
|
|
160
154
|
size: 'small',
|
|
161
|
-
autoSize: { minRows:
|
|
155
|
+
autoSize: { minRows: 2, maxRows: 6 },
|
|
162
156
|
placeholder: e('batchInput'),
|
|
163
157
|
allowClear: true
|
|
164
158
|
}
|
|
@@ -173,22 +167,15 @@ export default class BatchInput extends Component {
|
|
|
173
167
|
return (
|
|
174
168
|
<span
|
|
175
169
|
className={cls}
|
|
176
|
-
|
|
177
|
-
onMouseLeave={this.handleMouseLeave}
|
|
170
|
+
ref={this.outerRef}
|
|
178
171
|
>
|
|
179
172
|
<span className='bi-compact'>
|
|
180
|
-
|
|
181
|
-
{...inputProps}
|
|
182
|
-
/>
|
|
173
|
+
{this.renderTrigger()}
|
|
183
174
|
</span>
|
|
184
175
|
<span className='bi-full'>
|
|
185
|
-
<
|
|
186
|
-
{...
|
|
187
|
-
|
|
188
|
-
<Input.TextArea
|
|
189
|
-
{...textAreaProps}
|
|
190
|
-
/>
|
|
191
|
-
</AutoComplete>
|
|
176
|
+
<Input.TextArea
|
|
177
|
+
{...textAreaProps}
|
|
178
|
+
/>
|
|
192
179
|
<TabSelect {...tabSelectProps} />
|
|
193
180
|
</span>
|
|
194
181
|
</span>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { auto } from 'manate/react'
|
|
2
2
|
import {
|
|
3
|
-
Select
|
|
3
|
+
Select,
|
|
4
|
+
Dropdown
|
|
4
5
|
} from 'antd'
|
|
5
|
-
import { InfoCircleOutlined } from '@ant-design/icons'
|
|
6
|
+
import { InfoCircleOutlined, TranslationOutlined } from '@ant-design/icons'
|
|
6
7
|
import './footer.styl'
|
|
7
8
|
import { statusMap } from '../../common/constants'
|
|
8
9
|
import BatchInput from './batch-input'
|
|
@@ -54,9 +55,9 @@ export default auto(function FooterEntry (props) {
|
|
|
54
55
|
const batchProps = {
|
|
55
56
|
input: batchInput,
|
|
56
57
|
tabs: store.tabs,
|
|
57
|
-
batchInputs: store.batchInputs,
|
|
58
58
|
batchInputSelectedTabIds: store.batchInputSelectedTabIds,
|
|
59
|
-
activeTabId: store.activeTabId
|
|
59
|
+
activeTabId: store.activeTabId,
|
|
60
|
+
isMobile: store.isMobile
|
|
60
61
|
}
|
|
61
62
|
return (
|
|
62
63
|
<div className='terminal-footer-unit terminal-footer-center'>
|
|
@@ -96,6 +97,28 @@ export default auto(function FooterEntry (props) {
|
|
|
96
97
|
size: 'small',
|
|
97
98
|
popupMatchSelectWidth: false
|
|
98
99
|
}
|
|
100
|
+
if (props.store.isMobile) {
|
|
101
|
+
const items = encodes.map(k => {
|
|
102
|
+
return {
|
|
103
|
+
key: k,
|
|
104
|
+
label: k.toUpperCase(),
|
|
105
|
+
onClick: () => handleSwitchEncoding(k)
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
return (
|
|
109
|
+
<div className='terminal-footer-unit terminal-footer-info'>
|
|
110
|
+
<Dropdown
|
|
111
|
+
menu={{ items }}
|
|
112
|
+
placement='topRight'
|
|
113
|
+
trigger={['click']}
|
|
114
|
+
>
|
|
115
|
+
<TranslationOutlined
|
|
116
|
+
className='pointer font18 mobile-encode-trigger'
|
|
117
|
+
/>
|
|
118
|
+
</Dropdown>
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
99
122
|
return (
|
|
100
123
|
<div className='terminal-footer-unit terminal-footer-info'>
|
|
101
124
|
<div className='fleft relative'>
|
|
@@ -39,22 +39,26 @@
|
|
|
39
39
|
// margin so the history icon stays roughly equidistant from AI and Q
|
|
40
40
|
margin-left 3px
|
|
41
41
|
.batch-input-outer
|
|
42
|
+
position relative
|
|
42
43
|
.bi-full
|
|
43
44
|
display none
|
|
44
45
|
&.bi-show
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
display inline
|
|
46
|
+
// hide the trigger while the popup is open so we don't show two
|
|
47
|
+
// "batch input" fields at once (desktop trigger is an input-shaped affordance)
|
|
48
48
|
.bi-compact
|
|
49
49
|
display none
|
|
50
|
+
.bi-full
|
|
51
|
+
display flex
|
|
52
|
+
flex-direction column
|
|
53
|
+
gap 6px
|
|
54
|
+
position absolute
|
|
55
|
+
bottom 100%
|
|
56
|
+
left 0
|
|
57
|
+
z-index 1000
|
|
58
|
+
background var(--main)
|
|
59
|
+
padding 8px 10px
|
|
60
|
+
box-shadow 0 -2px 8px rgba(0, 0, 0, 0.2)
|
|
61
|
+
border-radius 4px 4px 0 0
|
|
62
|
+
min-width 360px
|
|
50
63
|
.batch-input-holder
|
|
51
|
-
width 100px
|
|
52
|
-
@media (max-width: 500px)
|
|
53
|
-
.batch-input-outer
|
|
54
|
-
&.bi-show
|
|
55
|
-
position fixed
|
|
56
|
-
left 100px
|
|
57
|
-
right 0
|
|
58
|
-
bottom 0
|
|
59
|
-
z-index 100
|
|
60
|
-
background var(--main)
|
|
64
|
+
width 100px
|
|
@@ -113,7 +113,9 @@ export default auto(function Index (props) {
|
|
|
113
113
|
'not-win': !isWin,
|
|
114
114
|
'qm-pinned': pinnedQuickCommandBar,
|
|
115
115
|
fullscreen,
|
|
116
|
-
'is-main': !isSecondInstance
|
|
116
|
+
'is-main': !isSecondInstance,
|
|
117
|
+
'is-mobile': store.isMobile,
|
|
118
|
+
'is-desktop': !store.isMobile
|
|
117
119
|
})
|
|
118
120
|
const ext1 = {
|
|
119
121
|
className: cls
|
|
@@ -78,4 +78,27 @@
|
|
|
78
78
|
max-width none
|
|
79
79
|
width 100%
|
|
80
80
|
.qm-label-select
|
|
81
|
-
width 100%
|
|
81
|
+
width 100%
|
|
82
|
+
|
|
83
|
+
// On mobile, stack the quick-command row (name / delay / command / remove)
|
|
84
|
+
// vertically instead of side by side — the horizontal Space.Compact layout
|
|
85
|
+
// is too cramped on narrow screens.
|
|
86
|
+
.is-mobile
|
|
87
|
+
.qm-cmd-row.ant-space-compact
|
|
88
|
+
display flex
|
|
89
|
+
flex-direction column
|
|
90
|
+
align-items stretch
|
|
91
|
+
gap 8px
|
|
92
|
+
.ant-space-compact-item
|
|
93
|
+
width 100%
|
|
94
|
+
// reset the constrained widths so inputs fill the row when stacked
|
|
95
|
+
.qm-name-input
|
|
96
|
+
max-width none
|
|
97
|
+
width 100%
|
|
98
|
+
.ant-input-number
|
|
99
|
+
width 100% !important
|
|
100
|
+
// keep the drag handle + remove button on a tidy line of their own
|
|
101
|
+
> .drag
|
|
102
|
+
align-self flex-start
|
|
103
|
+
> button:last-child
|
|
104
|
+
align-self flex-end
|
|
@@ -111,7 +111,7 @@ export default class QuickCommandsItem extends PureComponent {
|
|
|
111
111
|
title={this.renderTooltip()}
|
|
112
112
|
placement='top'
|
|
113
113
|
mouseEnterDelay={0.5}
|
|
114
|
-
|
|
114
|
+
classNames={{ root: 'qm-tooltip-overlay' }}
|
|
115
115
|
>
|
|
116
116
|
<Button
|
|
117
117
|
key={id}
|
|
@@ -594,6 +594,9 @@ export default class SessionWrapper extends Component {
|
|
|
594
594
|
}
|
|
595
595
|
|
|
596
596
|
renderSplitToggle = () => {
|
|
597
|
+
if (window.store.isMobile) {
|
|
598
|
+
return null
|
|
599
|
+
}
|
|
597
600
|
if (!this.canSplitView() || this.isNotTerminalType()) {
|
|
598
601
|
return null
|
|
599
602
|
}
|
|
@@ -653,7 +656,8 @@ export default class SessionWrapper extends Component {
|
|
|
653
656
|
const simpleMapper = {
|
|
654
657
|
[paneMap.terminal]: 'T',
|
|
655
658
|
[paneMap.fileManager]: 'F',
|
|
656
|
-
[paneMap.ssh]: 'T'
|
|
659
|
+
[paneMap.ssh]: 'T',
|
|
660
|
+
[paneMap.sftp]: 'S'
|
|
657
661
|
}
|
|
658
662
|
return (
|
|
659
663
|
<div className='term-sftp-tabs fleft'>
|
|
@@ -673,8 +677,8 @@ export default class SessionWrapper extends Component {
|
|
|
673
677
|
onClick={() => this.onChangePane(types[i])}
|
|
674
678
|
>
|
|
675
679
|
<span className='type-tab-txt'>
|
|
676
|
-
<span className='
|
|
677
|
-
<span className='
|
|
680
|
+
<span className='w800'>{e(type)}</span>
|
|
681
|
+
<span className='l800'>{simpleMapper[type]}</span>
|
|
678
682
|
<span className='type-tab-line' />
|
|
679
683
|
</span>
|
|
680
684
|
</span>
|
|
@@ -110,7 +110,7 @@ export default function DeepLinkControl () {
|
|
|
110
110
|
<Tooltip
|
|
111
111
|
title={renderTooltipContent()}
|
|
112
112
|
>
|
|
113
|
-
<
|
|
113
|
+
<div className='deep-link-btns'>
|
|
114
114
|
{
|
|
115
115
|
!isAllRegistered && (
|
|
116
116
|
<Button
|
|
@@ -134,7 +134,7 @@ export default function DeepLinkControl () {
|
|
|
134
134
|
</Button>
|
|
135
135
|
)
|
|
136
136
|
}
|
|
137
|
-
</
|
|
137
|
+
</div>
|
|
138
138
|
</Tooltip>
|
|
139
139
|
</div>
|
|
140
140
|
)
|
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
} from '@ant-design/icons'
|
|
12
12
|
import {
|
|
13
13
|
Button,
|
|
14
|
+
Pagination,
|
|
14
15
|
Modal,
|
|
15
16
|
Space,
|
|
16
|
-
Table,
|
|
17
17
|
Tag,
|
|
18
18
|
Tooltip,
|
|
19
19
|
Typography,
|
|
@@ -126,8 +126,10 @@ export default class SettingPasswords extends Component {
|
|
|
126
126
|
})
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
130
|
-
this.setState({
|
|
129
|
+
handlePageChange = (page, pageSize) => {
|
|
130
|
+
this.setState({
|
|
131
|
+
pagination: { current: page, pageSize }
|
|
132
|
+
})
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
getFilteredData = () => {
|
|
@@ -143,88 +145,48 @@ export default class SettingPasswords extends Component {
|
|
|
143
145
|
})
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const props0 = {
|
|
184
|
-
title: titles.join('\n'),
|
|
185
|
-
children: <span>{display}</span>
|
|
186
|
-
}
|
|
187
|
-
return <Tooltip {...props0} />
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
title: e('actions'),
|
|
192
|
-
key: 'actions',
|
|
193
|
-
width: 80,
|
|
194
|
-
render: (_, record) => {
|
|
195
|
-
const copyProps0 = {
|
|
196
|
-
type: 'text',
|
|
197
|
-
icon: <CopyOutlined />,
|
|
198
|
-
onClick: () => this.handleCopyPassword(record.password)
|
|
199
|
-
}
|
|
200
|
-
const editProps0 = {
|
|
201
|
-
type: 'text',
|
|
202
|
-
icon: <EditOutlined />,
|
|
203
|
-
onClick: () => this.showEditModal(record)
|
|
204
|
-
}
|
|
205
|
-
const copyTooltipProps = {
|
|
206
|
-
title: e('copy'),
|
|
207
|
-
children: <Button {...copyProps0} />
|
|
208
|
-
}
|
|
209
|
-
const editTooltipProps = {
|
|
210
|
-
title: e('changePassword'),
|
|
211
|
-
children: <Button {...editProps0} />
|
|
212
|
-
}
|
|
213
|
-
const spaceProps0 = {
|
|
214
|
-
children: [
|
|
215
|
-
<Tooltip key='copy' {...copyTooltipProps} />,
|
|
216
|
-
<Tooltip key='edit' {...editTooltipProps} />
|
|
217
|
-
]
|
|
218
|
-
}
|
|
219
|
-
return <Space>{spaceProps0.children}</Space>
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
]
|
|
223
|
-
return columns
|
|
148
|
+
renderItem = (record) => {
|
|
149
|
+
const { password, count, titles } = record
|
|
150
|
+
const display = titles.length > 2
|
|
151
|
+
? `${titles.slice(0, 2).join(', ')}... (+${titles.length - 2})`
|
|
152
|
+
: titles.join(', ')
|
|
153
|
+
const copyProps = {
|
|
154
|
+
type: 'text',
|
|
155
|
+
icon: <CopyOutlined />,
|
|
156
|
+
onClick: () => this.handleCopyPassword(password)
|
|
157
|
+
}
|
|
158
|
+
const editProps = {
|
|
159
|
+
type: 'text',
|
|
160
|
+
icon: <EditOutlined />,
|
|
161
|
+
onClick: () => this.showEditModal(record)
|
|
162
|
+
}
|
|
163
|
+
return (
|
|
164
|
+
<div className='setting-passwords-item' key={password}>
|
|
165
|
+
<div className='setting-passwords-item-inner'>
|
|
166
|
+
<Space className='setting-passwords-pwd-cell'>
|
|
167
|
+
<KeyOutlined />
|
|
168
|
+
<TextAnt keyboard>********</TextAnt>
|
|
169
|
+
</Space>
|
|
170
|
+
<Tag color='blue' className='setting-passwords-count-cell'>{count}</Tag>
|
|
171
|
+
<Tooltip title={titles.join('\n')}>
|
|
172
|
+
<span className='setting-passwords-host-cell'>{display}</span>
|
|
173
|
+
</Tooltip>
|
|
174
|
+
<Space className='setting-passwords-actions-cell'>
|
|
175
|
+
<Tooltip title={e('copy')}>
|
|
176
|
+
<Button {...copyProps} />
|
|
177
|
+
</Tooltip>
|
|
178
|
+
<Tooltip title={e('changePassword')}>
|
|
179
|
+
<Button {...editProps} />
|
|
180
|
+
</Tooltip>
|
|
181
|
+
</Space>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
224
185
|
}
|
|
225
186
|
|
|
226
187
|
renderContent () {
|
|
227
188
|
const { search, pagination } = this.state
|
|
189
|
+
const { current, pageSize } = pagination
|
|
228
190
|
const data = this.getFilteredData()
|
|
229
191
|
|
|
230
192
|
if (data.length === 0) {
|
|
@@ -241,19 +203,29 @@ export default class SettingPasswords extends Component {
|
|
|
241
203
|
onChange: this.handleSearchChange,
|
|
242
204
|
placeholder: e('search')
|
|
243
205
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
206
|
+
|
|
207
|
+
// clamp current page when filtering shrinks the data set
|
|
208
|
+
const pageCount = Math.ceil(data.length / pageSize)
|
|
209
|
+
const safePage = Math.min(current, Math.max(pageCount, 1))
|
|
210
|
+
const pagedData = data.slice((safePage - 1) * pageSize, safePage * pageSize)
|
|
211
|
+
|
|
212
|
+
const paginationProps0 = {
|
|
213
|
+
total: data.length,
|
|
214
|
+
current: safePage,
|
|
215
|
+
pageSize,
|
|
216
|
+
showSizeChanger: true,
|
|
217
|
+
onChange: this.handlePageChange,
|
|
218
|
+
onShowSizeChange: this.handlePageChange,
|
|
250
219
|
size: 'small'
|
|
251
220
|
}
|
|
252
221
|
|
|
253
222
|
return (
|
|
254
223
|
<div>
|
|
255
224
|
<Search {...searchProps0} />
|
|
256
|
-
<
|
|
225
|
+
<div className='setting-passwords-list'>
|
|
226
|
+
{pagedData.map(item => this.renderItem(item))}
|
|
227
|
+
</div>
|
|
228
|
+
<Pagination {...paginationProps0} />
|
|
257
229
|
</div>
|
|
258
230
|
)
|
|
259
231
|
}
|
|
@@ -16,6 +16,18 @@ export default class SettingWrap extends Component {
|
|
|
16
16
|
)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
renderRightClose () {
|
|
20
|
+
if (this.props.isMobile) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
return (
|
|
24
|
+
<CloseCircleOutlined
|
|
25
|
+
className='close-setting-wrap-icon close-setting-wrap'
|
|
26
|
+
onClick={this.props.onCancel}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
render () {
|
|
20
32
|
const pops = {
|
|
21
33
|
open: this.props.visible,
|
|
@@ -29,10 +41,7 @@ export default class SettingWrap extends Component {
|
|
|
29
41
|
<Drawer
|
|
30
42
|
{...pops}
|
|
31
43
|
>
|
|
32
|
-
|
|
33
|
-
className='close-setting-wrap-icon close-setting-wrap'
|
|
34
|
-
onClick={this.props.onCancel}
|
|
35
|
-
/>
|
|
44
|
+
{this.renderRightClose()}
|
|
36
45
|
<CloseCircleOutlined
|
|
37
46
|
className='close-setting-wrap alt-close-setting-wrap'
|
|
38
47
|
onClick={this.props.onCancel}
|
|
@@ -10,4 +10,40 @@
|
|
|
10
10
|
left 43px
|
|
11
11
|
height 60px
|
|
12
12
|
right 0
|
|
13
|
-
z-index 888
|
|
13
|
+
z-index 888
|
|
14
|
+
.deep-link-btns
|
|
15
|
+
display flex
|
|
16
|
+
flex-wrap wrap
|
|
17
|
+
gap 8px
|
|
18
|
+
|
|
19
|
+
// passwords list — replaces the old table so content wraps
|
|
20
|
+
// instead of forcing horizontal scroll on narrow/mobile screens
|
|
21
|
+
.setting-passwords-list
|
|
22
|
+
border 1px solid var(--main-light)
|
|
23
|
+
border-radius 6px
|
|
24
|
+
overflow hidden
|
|
25
|
+
margin 8px 0
|
|
26
|
+
.setting-passwords-item
|
|
27
|
+
padding 8px 12px
|
|
28
|
+
&:not(:last-child)
|
|
29
|
+
border-bottom 1px solid var(--main-light)
|
|
30
|
+
.setting-passwords-item
|
|
31
|
+
.setting-passwords-item-inner
|
|
32
|
+
display flex
|
|
33
|
+
flex-wrap wrap
|
|
34
|
+
align-items center
|
|
35
|
+
gap 8px
|
|
36
|
+
width 100%
|
|
37
|
+
.setting-passwords-pwd-cell
|
|
38
|
+
flex-shrink 0
|
|
39
|
+
.setting-passwords-count-cell
|
|
40
|
+
flex-shrink 0
|
|
41
|
+
margin 0
|
|
42
|
+
.setting-passwords-host-cell
|
|
43
|
+
flex 1 1 120px
|
|
44
|
+
min-width 120px
|
|
45
|
+
word-break break-all
|
|
46
|
+
line-height 1.5
|
|
47
|
+
.setting-passwords-actions-cell
|
|
48
|
+
flex-shrink 0
|
|
49
|
+
margin-left auto
|
|
@@ -9,6 +9,7 @@ import { Tabs, Tooltip } from 'antd'
|
|
|
9
9
|
import MultiSelectModal from '../common/multi-select-modal'
|
|
10
10
|
import {
|
|
11
11
|
ArrowsAltOutlined,
|
|
12
|
+
CloseOutlined,
|
|
12
13
|
EditOutlined,
|
|
13
14
|
PlusCircleOutlined,
|
|
14
15
|
ShrinkOutlined,
|
|
@@ -105,6 +106,10 @@ export default memo(function SidebarPanel (props) {
|
|
|
105
106
|
onClick={store.handlePin}
|
|
106
107
|
/>
|
|
107
108
|
</Tooltip>
|
|
109
|
+
<CloseOutlined
|
|
110
|
+
{...prps}
|
|
111
|
+
onClick={store.handleCloseSidebar}
|
|
112
|
+
/>
|
|
108
113
|
</div>
|
|
109
114
|
<div className='pd1y pd2x'>
|
|
110
115
|
<Tabs {...tabsProps} />
|
|
@@ -37,7 +37,7 @@ export default memo(function TransferList (props) {
|
|
|
37
37
|
const popProps = {
|
|
38
38
|
placement: 'right',
|
|
39
39
|
destroyOnHidden: true,
|
|
40
|
-
|
|
40
|
+
classNames: { root: 'transfer-list-card' },
|
|
41
41
|
content: <TransferModal {...transferModalProps} />
|
|
42
42
|
}
|
|
43
43
|
return (
|
|
@@ -83,6 +83,9 @@ export default class Tabs extends Component {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
isOverflow = () => {
|
|
86
|
+
if (this.props.isMobile || !this.domRef.current) {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
86
89
|
const { tabs = [] } = this.props
|
|
87
90
|
const len = tabs.length
|
|
88
91
|
const addBtnWidth = 22
|
|
@@ -120,6 +123,9 @@ export default class Tabs extends Component {
|
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
adjustScroll = () => {
|
|
126
|
+
if (this.props.isMobile || !this.domRef.current) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
123
129
|
const { tabs, currentBatchTabId, batch } = this.props
|
|
124
130
|
const index = tabs.findIndex(t => t.id === currentBatchTabId)
|
|
125
131
|
const tabsDomWith = Array.from(
|
|
@@ -139,6 +145,9 @@ export default class Tabs extends Component {
|
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
handleScrollLeft = () => {
|
|
148
|
+
if (!this.domRef.current) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
142
151
|
let { scrollLeft } = this.domRef.current
|
|
143
152
|
scrollLeft = scrollLeft - tabMargin - tabWidth
|
|
144
153
|
if (scrollLeft < 0) {
|
|
@@ -148,6 +157,9 @@ export default class Tabs extends Component {
|
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
handleScrollRight = () => {
|
|
160
|
+
if (!this.domRef.current) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
151
163
|
let { scrollLeft } = this.domRef.current
|
|
152
164
|
scrollLeft = scrollLeft + tabMargin + tabWidth
|
|
153
165
|
if (scrollLeft < 0) {
|
|
@@ -349,8 +361,69 @@ export default class Tabs extends Component {
|
|
|
349
361
|
return null
|
|
350
362
|
}
|
|
351
363
|
|
|
364
|
+
renderMobileTabsDropdown (items) {
|
|
365
|
+
return (
|
|
366
|
+
<Dropdown
|
|
367
|
+
menu={{ items }}
|
|
368
|
+
placement='bottomRight'
|
|
369
|
+
trigger={['click']}
|
|
370
|
+
>
|
|
371
|
+
<DownOutlined className='tabs-dd-icon' />
|
|
372
|
+
</Dropdown>
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
renderMobileTabsExtra (items) {
|
|
377
|
+
return (
|
|
378
|
+
<div className='mobile-tabs-extra'>
|
|
379
|
+
{this.renderAddBtn()}
|
|
380
|
+
{
|
|
381
|
+
items && items.length
|
|
382
|
+
? this.renderMobileTabsDropdown(items)
|
|
383
|
+
: null
|
|
384
|
+
}
|
|
385
|
+
</div>
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
renderMobileTabs () {
|
|
390
|
+
const { tabs = [], currentBatchTabId } = this.props
|
|
391
|
+
const currentTab = tabs.find(t => t.id === currentBatchTabId)
|
|
392
|
+
const items = tabs.map((t, i) => {
|
|
393
|
+
return {
|
|
394
|
+
key: i + '##' + t.id,
|
|
395
|
+
label: (
|
|
396
|
+
<span><TabTitle tab={t} /></span>
|
|
397
|
+
),
|
|
398
|
+
onClick: () => this.handleClickMenu({ key: i + '##' + t.id })
|
|
399
|
+
}
|
|
400
|
+
})
|
|
401
|
+
const tabProps = {
|
|
402
|
+
...this.props,
|
|
403
|
+
tab: currentTab,
|
|
404
|
+
isLast: true,
|
|
405
|
+
addTab: this.handleTabAdd,
|
|
406
|
+
tabIndex: 0
|
|
407
|
+
}
|
|
408
|
+
const currentTabEl = currentTab
|
|
409
|
+
? <Tab {...tabProps} key={currentTab.id} />
|
|
410
|
+
: null
|
|
411
|
+
return (
|
|
412
|
+
<div className='tabs mobile-tabs' ref={this.tabsRef}>
|
|
413
|
+
<div className='tabs-inner mobile-tabs-inner'>
|
|
414
|
+
{currentTabEl}
|
|
415
|
+
</div>
|
|
416
|
+
{this.renderMobileTabsExtra(items)}
|
|
417
|
+
{this.renderWindowControl()}
|
|
418
|
+
</div>
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
|
|
352
422
|
renderTabs () {
|
|
353
423
|
const { overflow } = this.state
|
|
424
|
+
if (this.props.isMobile) {
|
|
425
|
+
return this.renderMobileTabs()
|
|
426
|
+
}
|
|
354
427
|
return (
|
|
355
428
|
<div className='tabs' ref={this.tabsRef}>
|
|
356
429
|
{this.renderContent()}
|
package/client/css/mobile.styl
CHANGED
|
@@ -1,8 +1,134 @@
|
|
|
1
|
-
.
|
|
1
|
+
.l800
|
|
2
2
|
display none !important
|
|
3
3
|
|
|
4
|
-
@media (max-width:
|
|
5
|
-
.
|
|
4
|
+
@media (max-width: 800px)
|
|
5
|
+
.w800
|
|
6
6
|
display none !important
|
|
7
|
-
.
|
|
8
|
-
display inline !important
|
|
7
|
+
.l800
|
|
8
|
+
display inline !important
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Mobile UI driven by store.isMobile (is-mobile class on root)
|
|
12
|
+
// Breakpoint: 800px (see mobileBreakpoint in constants.js)
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
.is-mobile
|
|
15
|
+
// ---- 1. Tabs: show only current tab + dropdown, close icon always visible
|
|
16
|
+
.tabs.mobile-tabs
|
|
17
|
+
display flex
|
|
18
|
+
align-items center
|
|
19
|
+
overflow hidden
|
|
20
|
+
// leave room for window controls on desktop platforms resized narrow
|
|
21
|
+
&.not-system-ui.not-mac.not-webapp
|
|
22
|
+
.tabs.mobile-tabs
|
|
23
|
+
padding-right 94px
|
|
24
|
+
.mobile-tabs-inner
|
|
25
|
+
flex 1
|
|
26
|
+
min-width 0
|
|
27
|
+
overflow hidden
|
|
28
|
+
display flex
|
|
29
|
+
align-items center
|
|
30
|
+
margin-left 0 !important
|
|
31
|
+
width auto !important
|
|
32
|
+
.tab
|
|
33
|
+
flex 1
|
|
34
|
+
max-width none
|
|
35
|
+
min-width 0
|
|
36
|
+
width auto
|
|
37
|
+
text-align left
|
|
38
|
+
// close icon always visible on mobile
|
|
39
|
+
.tab-close
|
|
40
|
+
display block
|
|
41
|
+
.mobile-tabs-extra
|
|
42
|
+
flex-shrink 0
|
|
43
|
+
display flex
|
|
44
|
+
align-items center
|
|
45
|
+
padding 0 10px
|
|
46
|
+
line-height 36px
|
|
47
|
+
z-index 20
|
|
48
|
+
.tabs-add-btn
|
|
49
|
+
margin 0
|
|
50
|
+
&.empty
|
|
51
|
+
font-size 20px
|
|
52
|
+
margin 0
|
|
53
|
+
.tabs-dd-icon
|
|
54
|
+
margin-left 8px
|
|
55
|
+
font-size 14px
|
|
56
|
+
color var(--text)
|
|
57
|
+
&:hover
|
|
58
|
+
color var(--text-light)
|
|
59
|
+
// tabs-extra must not render on mobile (defensive css too)
|
|
60
|
+
.tabs-extra
|
|
61
|
+
display none !important
|
|
62
|
+
|
|
63
|
+
// ---- 2. Footer batch input: show "B" button, click to expand
|
|
64
|
+
// match the quick-commands "Q" button: no flex-grow, same small margin
|
|
65
|
+
.terminal-footer-center
|
|
66
|
+
flex-grow 0
|
|
67
|
+
margin-left 3px
|
|
68
|
+
.batch-input-outer
|
|
69
|
+
&.bi-show
|
|
70
|
+
.bi-full
|
|
71
|
+
// full-width fixed popup above the footer (mobile is narrow)
|
|
72
|
+
position fixed
|
|
73
|
+
left 0
|
|
74
|
+
right 0
|
|
75
|
+
bottom 36px
|
|
76
|
+
z-index 200
|
|
77
|
+
background var(--main)
|
|
78
|
+
padding 8px 10px
|
|
79
|
+
box-shadow 0 -2px 8px rgba(0, 0, 0, 0.2)
|
|
80
|
+
width auto
|
|
81
|
+
min-width 0
|
|
82
|
+
border-radius 0
|
|
83
|
+
|
|
84
|
+
// ---- 3. Footer encode select: show icon, click to open dropdown
|
|
85
|
+
// match antd Button small icon size (~14px) used by sibling icons
|
|
86
|
+
.mobile-encode-trigger,
|
|
87
|
+
.terminal-info-icon
|
|
88
|
+
font-size 14px !important
|
|
89
|
+
line-height 36px
|
|
90
|
+
padding 0 4px
|
|
91
|
+
&:hover
|
|
92
|
+
color var(--text-light)
|
|
93
|
+
|
|
94
|
+
// ---- 4. Modal: full width / full bleed on mobile
|
|
95
|
+
.custom-modal-container
|
|
96
|
+
padding 0
|
|
97
|
+
.custom-modal-content
|
|
98
|
+
width 100% !important
|
|
99
|
+
max-width 100vw
|
|
100
|
+
margin-top 0
|
|
101
|
+
min-height 100vh
|
|
102
|
+
border-radius 0
|
|
103
|
+
|
|
104
|
+
// ---- 5. Sync panel data transport: stack export/import and auto-sync
|
|
105
|
+
// vertically with a gap. On narrow screens the right-floated
|
|
106
|
+
// auto-sync block wraps under the import button with no vertical
|
|
107
|
+
// gap — left-align it and add breathing room.
|
|
108
|
+
.sync-data-transport
|
|
109
|
+
.fleft,
|
|
110
|
+
.fright
|
|
111
|
+
float none
|
|
112
|
+
.fright
|
|
113
|
+
margin-top 8px
|
|
114
|
+
// the switch carries mg3l (32px left margin) to separate it from the
|
|
115
|
+
// import button on desktop — reset it when stacked so it left-aligns
|
|
116
|
+
.ant-switch
|
|
117
|
+
margin-left 0
|
|
118
|
+
|
|
119
|
+
// ---- 6. Solid Radio.Button groups (e.g. SSH auth type selector, session
|
|
120
|
+
// type selector): on narrow screens the connected button row wraps,
|
|
121
|
+
// but antd styles it as a single horizontal strip — adjacent buttons
|
|
122
|
+
// share borders via -1px margin and border-radius is only applied to
|
|
123
|
+
// the first/last child. When the row wraps, radius/borders break and
|
|
124
|
+
// the UI looks messed up. Convert each button to a standalone pill so
|
|
125
|
+
// wrapping stays tidy.
|
|
126
|
+
.ant-radio-group.ant-radio-group-solid
|
|
127
|
+
display flex
|
|
128
|
+
flex-wrap wrap
|
|
129
|
+
gap 6px
|
|
130
|
+
.ant-radio-button-wrapper
|
|
131
|
+
border-radius 3px
|
|
132
|
+
margin-inline-end 0
|
|
133
|
+
&::before
|
|
134
|
+
display none
|
package/client/store/common.js
CHANGED
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
addPanelWidthLsKey,
|
|
12
12
|
dismissDelKeyTipLsKey,
|
|
13
13
|
connectionMap,
|
|
14
|
-
lastAiChatSessionIdKey
|
|
14
|
+
lastAiChatSessionIdKey,
|
|
15
|
+
mobileBreakpoint,
|
|
16
|
+
splitMap
|
|
15
17
|
} from '../common/constants'
|
|
16
18
|
import * as ls from '../common/safe-local-storage'
|
|
17
19
|
import { refs, refsStatic } from '../components/common/ref'
|
|
@@ -58,14 +60,21 @@ export default Store => {
|
|
|
58
60
|
Store.prototype.onResize = debounce(async function () {
|
|
59
61
|
const { width, height } = await window.pre.runGlobalAsync('getScreenSize')
|
|
60
62
|
const isMaximized = window.pre.runSync('isMaximized')
|
|
63
|
+
const w = window.innerWidth
|
|
64
|
+
const isMobile = w <= mobileBreakpoint
|
|
61
65
|
const update = {
|
|
62
66
|
height: window.innerHeight,
|
|
63
|
-
innerWidth:
|
|
67
|
+
innerWidth: w,
|
|
64
68
|
screenWidth: width,
|
|
65
69
|
screenHeight: height,
|
|
66
|
-
isMaximized
|
|
70
|
+
isMaximized,
|
|
71
|
+
isMobile
|
|
67
72
|
}
|
|
68
73
|
window.store.storeAssign(update)
|
|
74
|
+
// Force single-column layout on mobile
|
|
75
|
+
if (isMobile && window.store.layout !== splitMap.c1) {
|
|
76
|
+
window.store.setLayout(splitMap.c1)
|
|
77
|
+
}
|
|
69
78
|
window.pre.runGlobalAsync('setWindowSize', {
|
|
70
79
|
...update,
|
|
71
80
|
height: window.outerHeight
|
|
@@ -23,7 +23,8 @@ import {
|
|
|
23
23
|
resolutionsLsKey,
|
|
24
24
|
syncServerDataKey,
|
|
25
25
|
splitMap,
|
|
26
|
-
lastAiChatSessionIdKey
|
|
26
|
+
lastAiChatSessionIdKey,
|
|
27
|
+
mobileBreakpoint
|
|
27
28
|
} from '../common/constants'
|
|
28
29
|
import * as ls from '../common/safe-local-storage'
|
|
29
30
|
import { exclude } from 'manate'
|
|
@@ -200,6 +201,7 @@ export default () => {
|
|
|
200
201
|
height: 500,
|
|
201
202
|
isMaximized: window.pre.runSync('isMaximized'),
|
|
202
203
|
hasNodePty: window.pre.runSync('nodePtyCheck'),
|
|
204
|
+
isMobile: window.innerWidth <= mobileBreakpoint,
|
|
203
205
|
fullscreen: false,
|
|
204
206
|
hideDelKeyTip: ls.getItem(dismissDelKeyTipLsKey) === 'y',
|
|
205
207
|
tabsHeight: 36,
|
package/client/store/sidebar.js
CHANGED
|
@@ -29,6 +29,15 @@ export default Store => {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
Store.prototype.handleCloseSidebar = function () {
|
|
33
|
+
const { store } = window
|
|
34
|
+
if (store.pinned) {
|
|
35
|
+
ls.setItem(sidebarPinnedKey, 'false')
|
|
36
|
+
store.pinned = false
|
|
37
|
+
}
|
|
38
|
+
store.setOpenedSideBar('')
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
Store.prototype.handleSidebarPanelTab = function (tab) {
|
|
33
42
|
window.store.sidebarPanelTab = tab
|
|
34
43
|
}
|