@electerm/electerm-react 2.13.6 → 2.16.6

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 (59) hide show
  1. package/client/components/ai/ai-chat.jsx +44 -2
  2. package/client/components/ai/ai-stop-icon.jsx +13 -0
  3. package/client/components/ai/ai.styl +10 -0
  4. package/client/components/bg/css-overwrite.jsx +158 -187
  5. package/client/components/bg/custom-css.jsx +9 -15
  6. package/client/components/bookmark-form/bookmark-schema.js +7 -1
  7. package/client/components/bookmark-form/common/color-picker.jsx +4 -8
  8. package/client/components/bookmark-form/common/exec-settings-field.jsx +44 -0
  9. package/client/components/bookmark-form/common/fields.jsx +3 -0
  10. package/client/components/bookmark-form/config/common-fields.js +1 -0
  11. package/client/components/bookmark-form/config/local.js +3 -1
  12. package/client/components/common/animate-text.jsx +22 -23
  13. package/client/components/common/modal.jsx +2 -0
  14. package/client/components/common/notification.jsx +1 -1
  15. package/client/components/common/opacity.jsx +8 -6
  16. package/client/components/common/password.jsx +19 -32
  17. package/client/components/footer/cmd-history.jsx +154 -0
  18. package/client/components/footer/cmd-history.styl +73 -0
  19. package/client/components/footer/footer-entry.jsx +15 -1
  20. package/client/components/main/main.jsx +2 -3
  21. package/client/components/main/ui-theme.jsx +10 -6
  22. package/client/components/profile/profile-list.jsx +1 -1
  23. package/client/components/quick-commands/quick-commands-list.jsx +1 -1
  24. package/client/components/quick-commands/quick-commands-select.jsx +1 -4
  25. package/client/components/rdp/rdp-session.jsx +23 -4
  26. package/client/components/session/session.styl +1 -3
  27. package/client/components/setting-panel/list.styl +7 -0
  28. package/client/components/setting-panel/terminal-bg-config.jsx +2 -0
  29. package/client/components/setting-panel/text-bg-modal.jsx +9 -9
  30. package/client/components/setting-sync/setting-sync-form.jsx +10 -5
  31. package/client/components/sftp/file-item.jsx +22 -0
  32. package/client/components/sidebar/history-item.jsx +6 -3
  33. package/client/components/sidebar/history.jsx +48 -5
  34. package/client/components/sidebar/sidebar-panel.jsx +0 -13
  35. package/client/components/sidebar/sidebar.styl +19 -0
  36. package/client/components/ssh-config/load-ssh-configs-item.jsx +99 -0
  37. package/client/components/ssh-config/load-ssh-configs.jsx +38 -9
  38. package/client/components/ssh-config/ssh-config.styl +3 -0
  39. package/client/components/tabs/add-btn-menu.jsx +28 -4
  40. package/client/components/tabs/add-btn.jsx +1 -1
  41. package/client/components/tabs/add-btn.styl +8 -0
  42. package/client/components/terminal/terminal.jsx +28 -11
  43. package/client/components/terminal/transfer-client-base.js +44 -0
  44. package/client/components/terminal/trzsz-client.js +10 -11
  45. package/client/components/terminal/zmodem-client.js +10 -11
  46. package/client/components/text-editor/edit-with-custom-editor.jsx +49 -0
  47. package/client/components/text-editor/simple-editor.jsx +38 -6
  48. package/client/components/text-editor/text-editor-form.jsx +13 -5
  49. package/client/components/text-editor/text-editor.jsx +20 -1
  50. package/client/components/vnc/vnc-session.jsx +3 -0
  51. package/client/components/vnc/vnc.styl +1 -1
  52. package/client/store/bookmark.js +3 -11
  53. package/client/store/common.js +31 -4
  54. package/client/store/init-state.js +26 -1
  55. package/client/store/store.js +1 -1
  56. package/client/store/sync.js +2 -3
  57. package/client/store/watch.js +8 -1
  58. package/package.json +1 -1
  59. package/client/components/ssh-config/ssh-config-item.jsx +0 -24
@@ -301,6 +301,7 @@ export const sshAuthFields = [
301
301
  type: 'sshAgent',
302
302
  name: 'useSshAgent'
303
303
  },
304
+ { type: 'switch', name: 'isMFA', label: () => e('MFA/OTP'), valuePropName: 'checked' },
304
305
  commonFields.runScripts,
305
306
  commonFields.description,
306
307
  commonFields.setEnv,
@@ -86,7 +86,9 @@ const localConfig = {
86
86
  step: 1000
87
87
  }
88
88
  },
89
- { type: 'terminalBackground', name: 'terminalBackground', label: () => e('terminalBackgroundImage') }
89
+ { type: 'terminalBackground', name: 'terminalBackground', label: () => e('terminalBackgroundImage') },
90
+ // Exec settings - stored as flat properties on bookmark
91
+ { type: 'execSettings' }
90
92
  ]
91
93
  },
92
94
  {
@@ -2,34 +2,33 @@
2
2
  * animate text when text change
3
3
  */
4
4
 
5
- import React from 'react'
5
+ import React, { useRef, useEffect } from 'react'
6
6
 
7
- export default class AnimateText extends React.PureComponent {
8
- constructor (props) {
9
- super(props)
10
- this.textRef = React.createRef()
11
- }
7
+ export default function AnimateText ({ children, className }) {
8
+ const textRef = useRef(null)
9
+ const timerRef = useRef(null)
12
10
 
13
- componentDidUpdate () {
14
- const dom = this.textRef.current
15
- dom.className = (this.props.className || 'animate-text-wrap')
16
- this.timer = setTimeout(() => {
11
+ useEffect(() => {
12
+ const dom = textRef.current
13
+ if (!dom) return
14
+
15
+ dom.className = className || 'animate-text-wrap'
16
+ timerRef.current = setTimeout(() => {
17
17
  if (dom) {
18
- dom.className = this.props.className || 'animate-text-wrap'
18
+ dom.className = className || 'animate-text-wrap'
19
19
  }
20
20
  }, 450)
21
- }
22
21
 
23
- componentWillUnmount () {
24
- clearTimeout(this.timer)
25
- }
22
+ return () => {
23
+ if (timerRef.current) {
24
+ clearTimeout(timerRef.current)
25
+ }
26
+ }
27
+ }, [className])
26
28
 
27
- render () {
28
- const { children, className } = this.props
29
- return (
30
- <div className={className} ref={this.textRef}>
31
- {children}
32
- </div>
33
- )
34
- }
29
+ return (
30
+ <div className={className} ref={textRef}>
31
+ {children}
32
+ </div>
33
+ )
35
34
  }
@@ -113,6 +113,8 @@ export default function Modal (props) {
113
113
  )
114
114
  }
115
115
 
116
+ Modal.displayName = 'Modal'
117
+
116
118
  function createModalInstance (type, options) {
117
119
  const {
118
120
  title,
@@ -59,7 +59,7 @@ export function NotificationContainer () {
59
59
  {nots.map(notif => (
60
60
  <NotificationItem
61
61
  key={notif.key}
62
- message={notif.message}
62
+ message={notif.message || notif.type}
63
63
  description={notif.description}
64
64
  type={notif.type}
65
65
  duration={notif.duration}
@@ -1,5 +1,4 @@
1
- import { useEffect } from 'react'
2
- import { useDelta, useConditionalEffect } from 'react-delta-hooks'
1
+ import { useEffect, useRef } from 'react'
3
2
  import eq from 'fast-deep-equal'
4
3
 
5
4
  const opacityDomId = 'opacity-style'
@@ -14,7 +13,7 @@ const opacityDomId = 'opacity-style'
14
13
  export default function Opacity ({ opacity }) {
15
14
  // Default to 1 if opacity is not provided
16
15
  const currentOpacity = opacity !== undefined ? opacity : 1
17
- const delta = useDelta(currentOpacity)
16
+ const prevRef = useRef(null)
18
17
 
19
18
  function applyOpacity () {
20
19
  let styleElement = document.getElementById(opacityDomId)
@@ -58,9 +57,12 @@ export default function Opacity ({ opacity }) {
58
57
  }
59
58
  }, [])
60
59
 
61
- useConditionalEffect(() => {
62
- applyOpacity()
63
- }, delta && !eq(delta.prev, delta.curr))
60
+ useEffect(() => {
61
+ if (prevRef.current && !eq(prevRef.current, currentOpacity)) {
62
+ applyOpacity()
63
+ }
64
+ prevRef.current = currentOpacity
65
+ }, [currentOpacity])
64
66
 
65
67
  return null
66
68
  }
@@ -1,17 +1,12 @@
1
- import { useState, useCallback, forwardRef } from 'react'
1
+ import { useState, useCallback } from 'react'
2
2
  import {
3
3
  Input,
4
4
  Tag
5
5
  } from 'antd'
6
6
 
7
- /**
8
- * Password component that extends Ant Design's Password component
9
- * with caps lock detection and visual indicator
10
- */
11
- export default forwardRef(function Password (props, ref) {
7
+ export default function Password ({ ref, onKeyDown, onKeyUp, onFocus, onBlur, prefix, ...props }) {
12
8
  const [isCapsLockOn, setIsCapsLockOn] = useState(false)
13
9
 
14
- // Check caps lock state from keyboard event
15
10
  const checkCapsLock = useCallback((event) => {
16
11
  if (event.getModifierState) {
17
12
  const capsLockState = event.getModifierState('CapsLock')
@@ -19,37 +14,30 @@ export default forwardRef(function Password (props, ref) {
19
14
  }
20
15
  }, [])
21
16
 
22
- // Handle key events to detect caps lock changes
23
17
  const handleKeyEvent = useCallback((event) => {
24
18
  checkCapsLock(event)
25
- // Call original onKeyDown/onKeyUp if provided
26
- if (props.onKeyDown && event.type === 'keydown') {
27
- props.onKeyDown(event)
19
+ if (onKeyDown && event.type === 'keydown') {
20
+ onKeyDown(event)
28
21
  }
29
- if (props.onKeyUp && event.type === 'keyup') {
30
- props.onKeyUp(event)
22
+ if (onKeyUp && event.type === 'keyup') {
23
+ onKeyUp(event)
31
24
  }
32
- }, [props.onKeyDown, props.onKeyUp, checkCapsLock])
25
+ }, [onKeyDown, onKeyUp, checkCapsLock])
33
26
 
34
- // Handle focus event to check initial caps lock state
35
27
  const handleFocus = useCallback((event) => {
36
28
  checkCapsLock(event)
37
- // Call original onFocus if provided
38
- if (props.onFocus) {
39
- props.onFocus(event)
29
+ if (onFocus) {
30
+ onFocus(event)
40
31
  }
41
- }, [props.onFocus, checkCapsLock])
32
+ }, [onFocus, checkCapsLock])
42
33
 
43
- // Handle blur event to reset caps lock state
44
34
  const handleBlur = useCallback((event) => {
45
35
  setIsCapsLockOn(false)
46
- // Call original onBlur if provided
47
- if (props.onBlur) {
48
- props.onBlur(event)
36
+ if (onBlur) {
37
+ onBlur(event)
49
38
  }
50
- }, [props.onBlur])
39
+ }, [onBlur])
51
40
 
52
- // Show caps lock indicator inside prefix to avoid remounting the input wrapper
53
41
  let capsPrefix = null
54
42
  if (isCapsLockOn) {
55
43
  capsPrefix = (
@@ -57,13 +45,12 @@ export default forwardRef(function Password (props, ref) {
57
45
  )
58
46
  }
59
47
 
60
- // Merge any existing prefix from props with our caps indicator
61
- let prefix = capsPrefix
62
- if (props.prefix) {
63
- prefix = (
48
+ let mergedPrefix = capsPrefix
49
+ if (prefix) {
50
+ mergedPrefix = (
64
51
  <>
65
52
  {capsPrefix}
66
- {props.prefix}
53
+ {prefix}
67
54
  </>
68
55
  )
69
56
  }
@@ -72,11 +59,11 @@ export default forwardRef(function Password (props, ref) {
72
59
  <Input.Password
73
60
  {...props}
74
61
  ref={ref}
75
- prefix={prefix}
62
+ prefix={mergedPrefix}
76
63
  onKeyDown={handleKeyEvent}
77
64
  onKeyUp={handleKeyEvent}
78
65
  onFocus={handleFocus}
79
66
  onBlur={handleBlur}
80
67
  />
81
68
  )
82
- })
69
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * cmd history trigger button with popover
3
+ */
4
+
5
+ import { useState, useEffect } from 'react'
6
+ import { Button, Empty, Popover, Switch } from 'antd'
7
+ import { auto } from 'manate/react'
8
+ import { copy } from '../../common/clipboard'
9
+ import { HistoryOutlined, DeleteOutlined, CopyOutlined, UnorderedListOutlined } from '@ant-design/icons'
10
+ import InputAutoFocus from '../common/input-auto-focus'
11
+ import { getItemJSON, setItemJSON } from '../../common/safe-local-storage'
12
+ import './cmd-history.styl'
13
+
14
+ const e = window.translate
15
+ const SORT_BY_FREQ_KEY = 'electerm-cmd-history-sort-by-frequency'
16
+
17
+ export default auto(function CmdHistory (props) {
18
+ const [keyword, setKeyword] = useState('')
19
+ const [sortByFrequency, setSortByFrequency] = useState(() => {
20
+ return getItemJSON(SORT_BY_FREQ_KEY, false)
21
+ })
22
+ const { terminalCommandHistory } = props.store
23
+
24
+ useEffect(() => {
25
+ setItemJSON(SORT_BY_FREQ_KEY, sortByFrequency)
26
+ }, [sortByFrequency])
27
+
28
+ function handleRunCommand (cmd) {
29
+ window.store.runCmdFromHistory(cmd)
30
+ }
31
+
32
+ function handleDeleteCommand (cmd, ev) {
33
+ ev.stopPropagation()
34
+ terminalCommandHistory.delete(cmd)
35
+ }
36
+
37
+ function handleCopyCommand (cmd, ev) {
38
+ ev.stopPropagation()
39
+ copy(cmd)
40
+ }
41
+
42
+ function handleClearAll () {
43
+ window.store.clearAllCmdHistory()
44
+ }
45
+
46
+ function filterArray (array, keyword) {
47
+ if (!keyword) {
48
+ return array
49
+ }
50
+ return array.filter(item => item.cmd.toLowerCase().includes(keyword.toLowerCase()))
51
+ }
52
+
53
+ function handleChange (e) {
54
+ setKeyword(e.target.value)
55
+ }
56
+
57
+ const historyArray = Array.from(terminalCommandHistory || [])
58
+ .map(([cmd, info]) => ({ cmd, ...info }))
59
+ .reverse()
60
+
61
+ let filtered = filterArray(historyArray, keyword)
62
+
63
+ if (sortByFrequency) {
64
+ filtered = filtered.sort((a, b) => b.count - a.count)
65
+ }
66
+
67
+ const handleSortByFrequencyChange = (checked) => {
68
+ setSortByFrequency(checked)
69
+ }
70
+
71
+ const renderList = () => {
72
+ if (filtered.length === 0) {
73
+ return (
74
+ <Empty
75
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
76
+ description={e('noData')}
77
+ />
78
+ )
79
+ }
80
+ return filtered.map((item, index) => (
81
+ <div
82
+ key={index}
83
+ className='cmd-history-item'
84
+ onClick={() => handleRunCommand(item.cmd)}
85
+ >
86
+ <span className='cmd-history-item-text' title={item.cmd}>{item.cmd}</span>
87
+ <div className='cmd-history-item-actions'>
88
+ <span className='cmd-history-item-count' title={e('count') + ': ' + item.count}>
89
+ {item.count}
90
+ </span>
91
+ <Button
92
+ type='text'
93
+ size='small'
94
+ icon={<CopyOutlined />}
95
+ className='cmd-history-item-copy'
96
+ onClick={(ev) => handleCopyCommand(item.cmd, ev)}
97
+ />
98
+ <Button
99
+ type='text'
100
+ size='small'
101
+ icon={<DeleteOutlined />}
102
+ className='cmd-history-item-delete'
103
+ onClick={(ev) => handleDeleteCommand(item.cmd, ev)}
104
+ />
105
+ </div>
106
+ </div>
107
+ ))
108
+ }
109
+
110
+ const content = (
111
+ <div className='cmd-history-popover-content pd2'>
112
+ <div className='cmd-history-search pd2b'>
113
+ <InputAutoFocus
114
+ value={keyword}
115
+ onChange={handleChange}
116
+ placeholder={e('search')}
117
+ className='cmd-history-search-input'
118
+ allowClear
119
+ />
120
+ </div>
121
+ <div className='cmd-history-header pd2b'>
122
+ <Switch
123
+ checkedChildren={e('sortByFrequency')}
124
+ unCheckedChildren={e('sortByFrequency')}
125
+ checked={sortByFrequency}
126
+ onChange={handleSortByFrequencyChange}
127
+ size='small'
128
+ />
129
+ <UnorderedListOutlined
130
+ className='cmd-history-clear-icon pointer clear-ai-icon icon-hover'
131
+ title={e('clear')}
132
+ onClick={handleClearAll}
133
+ />
134
+ </div>
135
+ <div className='cmd-history-list'>
136
+ {renderList()}
137
+ </div>
138
+ </div>
139
+ )
140
+
141
+ return (
142
+ <Popover
143
+ content={content}
144
+ trigger='click'
145
+ placement='topLeft'
146
+ >
147
+ <Button
148
+ size='small'
149
+ type='text'
150
+ icon={<HistoryOutlined />}
151
+ />
152
+ </Popover>
153
+ )
154
+ })
@@ -0,0 +1,73 @@
1
+ .cmd-history-popover-content
2
+ max-height 300px
3
+ width 300px
4
+ overflow hidden
5
+ display flex
6
+ flex-direction column
7
+
8
+ .cmd-history-search
9
+ flex-shrink 0
10
+ position sticky
11
+ top 0
12
+
13
+ .cmd-history-header
14
+ flex-shrink 0
15
+ display flex
16
+ align-items center
17
+ justify-content space-between
18
+ padding-bottom 8px
19
+
20
+ .cmd-history-clear-icon
21
+ font-size 14px
22
+ padding 4px
23
+ cursor pointer
24
+ &:hover
25
+ color red
26
+
27
+ .cmd-history-list
28
+ overflow-x hidden
29
+ overflow-y auto
30
+ flex 1
31
+ min-height 0
32
+
33
+ .cmd-history-item
34
+ display flex
35
+ align-items center
36
+ justify-content space-between
37
+ padding 4px 8px
38
+ cursor pointer
39
+ &:hover
40
+ background var(--main-lighter)
41
+
42
+ .cmd-history-item-text
43
+ overflow hidden
44
+ text-overflow ellipsis
45
+ white-space nowrap
46
+ flex 1
47
+ min-width 0
48
+
49
+ .cmd-history-item-count
50
+ font-size 12px
51
+ color var(--text-color-secondary)
52
+ padding 0 4px
53
+ flex-shrink 0
54
+
55
+ .cmd-history-item-actions
56
+ display none
57
+ flex-shrink 0
58
+ gap 4px
59
+ align-items center
60
+ .cmd-history-item:hover &
61
+ display flex
62
+
63
+ .cmd-history-item-delete
64
+ cursor pointer
65
+ padding 2px 4px
66
+ &:hover
67
+ color red
68
+
69
+ .cmd-history-item-copy
70
+ cursor pointer
71
+ padding 2px 4px
72
+ &:hover
73
+ color blue
@@ -10,6 +10,7 @@ import encodes from '../bookmark-form/common/encodes'
10
10
  import { refs } from '../common/ref'
11
11
  import Qm from '../quick-commands/quick-commands-select'
12
12
  import AIIcon from '../icons/ai-icon'
13
+ import CmdHistory from './cmd-history'
13
14
 
14
15
  const {
15
16
  Option
@@ -131,7 +132,19 @@ export default auto(function FooterEntry (props) {
131
132
  )
132
133
  }
133
134
 
134
- const { leftSidebarWidth, openedSideBar, inActiveTerminal } = props.store
135
+ function renderCmdHistory () {
136
+ return (
137
+ <div className='terminal-footer-unit terminal-footer-history'>
138
+ <CmdHistory store={props.store} />
139
+ </div>
140
+ )
141
+ }
142
+
143
+ const {
144
+ leftSidebarWidth,
145
+ openedSideBar,
146
+ inActiveTerminal
147
+ } = props.store
135
148
  const w = 43 + leftSidebarWidth
136
149
  const sideProps = openedSideBar
137
150
  ? {
@@ -154,6 +167,7 @@ export default auto(function FooterEntry (props) {
154
167
  <div {...sideProps}>
155
168
  <div className='terminal-footer-flex'>
156
169
  {renderAIIcon()}
170
+ {renderCmdHistory()}
157
171
  {renderQuickCommands()}
158
172
  {renderBatchInputs()}
159
173
  {renderEncodingInfo()}
@@ -86,7 +86,6 @@ export default auto(function Index (props) {
86
86
  pinned,
87
87
  isSecondInstance,
88
88
  pinnedQuickCommandBar,
89
- wsInited,
90
89
  installSrc,
91
90
  fileTransfers,
92
91
  uiThemeConfig,
@@ -248,14 +247,14 @@ export default auto(function Index (props) {
248
247
  <ShortcutControl config={config} />
249
248
  <CssOverwrite
250
249
  {...confsCss}
251
- wsInited={wsInited}
250
+ configLoaded={configLoaded}
252
251
  />
253
252
  <Opacity opacity={config.opacity} />
254
253
  <TerminalInteractive />
255
254
  <UiTheme
256
255
  {...themeProps}
257
256
  />
258
- <CustomCss customCss={config.customCss} />
257
+ <CustomCss customCss={config.customCss} configLoaded={configLoaded} />
259
258
  <TextEditor />
260
259
  <UpdateCheck
261
260
  skipVersion={config.skipVersion}
@@ -2,8 +2,7 @@
2
2
  * ui theme
3
3
  */
4
4
 
5
- import { useEffect } from 'react'
6
- import { useDelta, useConditionalEffect } from 'react-delta-hooks'
5
+ import { useEffect, useRef } from 'react'
7
6
  import eq from 'fast-deep-equal'
8
7
  import isColorDark from '../../common/is-color-dark'
9
8
 
@@ -53,7 +52,7 @@ function buildTheme (themeConfig) {
53
52
  export default function UiTheme (props) {
54
53
  const { themeConfig } = props
55
54
 
56
- const delta = useDelta(themeConfig)
55
+ const prevRef = useRef(null)
57
56
 
58
57
  async function applyTheme () {
59
58
  const style = document.getElementById(themeDomId)
@@ -64,8 +63,13 @@ export default function UiTheme (props) {
64
63
  useEffect(() => {
65
64
  applyTheme()
66
65
  }, [])
67
- useConditionalEffect(() => {
68
- applyTheme()
69
- }, delta && delta.prev && !eq(delta.prev, delta.curr))
66
+
67
+ useEffect(() => {
68
+ if (prevRef.current && !eq(prevRef.current, themeConfig)) {
69
+ applyTheme()
70
+ }
71
+ prevRef.current = themeConfig
72
+ }, [themeConfig])
73
+
70
74
  return null
71
75
  }
@@ -28,7 +28,7 @@ export default class ProfileList extends List {
28
28
  const { activeItemId } = this.props
29
29
  const { name, id } = item
30
30
  const cls = classnames(
31
- 'item-list-unit theme-item',
31
+ 'item-list-unit',
32
32
  {
33
33
  active: activeItemId === id
34
34
  }
@@ -63,7 +63,7 @@ export default class QuickCommandsList extends List {
63
63
  const { activeItemId } = this.props
64
64
  const { name, id } = item
65
65
  const cls = classnames(
66
- 'item-list-unit theme-item',
66
+ 'item-list-unit',
67
67
  {
68
68
  active: activeItemId === id
69
69
  }
@@ -6,8 +6,6 @@ import { PureComponent } from 'react'
6
6
  import { Button } from 'antd'
7
7
  import './qm.styl'
8
8
 
9
- const e = window.translate
10
-
11
9
  export default class QuickCommandsFooter extends PureComponent {
12
10
  componentWillUnmount () {
13
11
  clearTimeout(this.timer)
@@ -37,8 +35,7 @@ export default class QuickCommandsFooter extends PureComponent {
37
35
  size='small'
38
36
  type='text'
39
37
  >
40
- <span className='w500'>{e('quickCommands')}</span>
41
- <span className='l500'>Q</span>
38
+ Q
42
39
  </Button>
43
40
  </div>
44
41
  )
@@ -339,10 +339,29 @@ export default class RdpSession extends PureComponent {
339
339
  if (!this.session) return
340
340
  try {
341
341
  const rect = canvas.getBoundingClientRect()
342
- const scaleX = canvas.width / rect.width
343
- const scaleY = canvas.height / rect.height
344
- const x = Math.round((e.clientX - rect.left) * scaleX)
345
- const y = Math.round((e.clientY - rect.top) * scaleY)
342
+ const { scaleViewport } = this.state
343
+ let scaleX = canvas.width / rect.width
344
+ let scaleY = canvas.height / rect.height
345
+ let offsetX = 0
346
+ let offsetY = 0
347
+ if (scaleViewport) {
348
+ const containerRatio = rect.width / rect.height
349
+ const canvasRatio = canvas.width / canvas.height
350
+ let renderWidth, renderHeight
351
+ if (containerRatio > canvasRatio) {
352
+ renderHeight = rect.height
353
+ renderWidth = rect.height * canvasRatio
354
+ offsetX = (rect.width - renderWidth) / 2
355
+ } else {
356
+ renderWidth = rect.width
357
+ renderHeight = rect.width / canvasRatio
358
+ offsetY = (rect.height - renderHeight) / 2
359
+ }
360
+ scaleX = canvas.width / renderWidth
361
+ scaleY = canvas.height / renderHeight
362
+ }
363
+ const x = Math.round((e.clientX - rect.left - offsetX) * scaleX)
364
+ const y = Math.round((e.clientY - rect.top - offsetY) * scaleY)
346
365
  const event = window.ironRdp.DeviceEvent.mouseMove(x, y)
347
366
  const tx = new window.ironRdp.InputTransaction()
348
367
  tx.addEvent(event)
@@ -78,9 +78,7 @@
78
78
  border-radius 0
79
79
  &::-webkit-scrollbar-corner
80
80
  background var(--main-darker)
81
- .vnc-session-wrap > div
82
- display block !important
83
- overflow hidden !important
81
+
84
82
  .not-split-view > .ant-splitter-bar .ant-splitter-bar-dragger
85
83
  display none
86
84
 
@@ -36,6 +36,13 @@
36
36
  .list-item-remove
37
37
  .list-item-bookmark
38
38
  display block
39
+ .theme-item:hover
40
+ .list-item-remove
41
+ right 24px
42
+ .setting-tabs-setting
43
+ .item-list-unit
44
+ .list-item-remove
45
+ display none
39
46
  // .item-list
40
47
  // .list-item-edit
41
48
  // .list-item-apply
@@ -254,3 +254,5 @@ export default function TerminalBackgroundConfig ({
254
254
  </div>
255
255
  )
256
256
  }
257
+
258
+ TerminalBackgroundConfig.displayName = 'TerminalBackgroundConfig'