@electerm/electerm-react 5.3.15 → 5.5.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 (149) hide show
  1. package/README.md +246 -12
  2. package/client/common/choose-save-folder.js +21 -2
  3. package/client/common/constants.js +23 -6
  4. package/client/common/create-title.jsx +6 -0
  5. package/client/common/default-setting.js +14 -0
  6. package/client/common/init-setting-item.js +5 -0
  7. package/client/common/platform.js +24 -0
  8. package/client/components/ai/agent-tool-call-card.jsx +10 -2
  9. package/client/components/ai/agent.js +4 -1
  10. package/client/components/ai/ai-attachments.js +87 -0
  11. package/client/components/ai/ai-chat-empty.jsx +43 -0
  12. package/client/components/ai/ai-chat-entry.jsx +13 -4
  13. package/client/components/ai/ai-chat-history-item.jsx +28 -4
  14. package/client/components/ai/ai-chat-history.jsx +2 -1
  15. package/client/components/ai/ai-chat.jsx +104 -13
  16. package/client/components/ai/ai-config-props.js +4 -130
  17. package/client/components/ai/ai-config.jsx +135 -15
  18. package/client/components/ai/ai-presets.js +173 -0
  19. package/client/components/ai/ai.styl +83 -0
  20. package/client/components/bg/css-overwrite.jsx +11 -0
  21. package/client/components/bookmark-form/bookmark-schema.js +4 -0
  22. package/client/components/bookmark-form/common/fields.jsx +3 -0
  23. package/client/components/bookmark-form/config/common-fields.js +12 -0
  24. package/client/components/bookmark-form/config/local.js +3 -2
  25. package/client/components/bookmark-form/config/serial.js +3 -2
  26. package/client/components/bookmark-form/config/ssh.js +2 -1
  27. package/client/components/bookmark-form/config/telnet.js +3 -2
  28. package/client/components/bookmark-form/index.jsx +10 -8
  29. package/client/components/common/item-filter.jsx +76 -0
  30. package/client/components/common/item-filter.styl +54 -0
  31. package/client/components/common/lazy-boundary.jsx +34 -0
  32. package/client/components/common/opacity.jsx +3 -1
  33. package/client/components/footer/footer-entry.jsx +35 -5
  34. package/client/components/footer/footer.styl +8 -1
  35. package/client/components/layout/layout.jsx +23 -31
  36. package/client/components/layout/pixed.js +3 -0
  37. package/client/components/main/main.jsx +80 -28
  38. package/client/components/main/term-fullscreen.styl +9 -3
  39. package/client/components/quick-commands/qm.styl +3 -2
  40. package/client/components/rdp/file-transfer.js +4 -4
  41. package/client/components/remote-monitor/monitor-details.jsx +458 -0
  42. package/client/components/remote-monitor/monitor-details.styl +87 -0
  43. package/client/components/remote-monitor/monitor-model.js +764 -0
  44. package/client/components/remote-monitor/remote-monitor-bar-entry.jsx +18 -0
  45. package/client/components/remote-monitor/remote-monitor-bar.jsx +344 -0
  46. package/client/components/remote-monitor/remote-monitor-bar.styl +106 -0
  47. package/client/components/remote-monitor/session-monitor.js +361 -0
  48. package/client/components/remote-monitor/use-monitor-details.js +10 -0
  49. package/client/components/remote-monitor/visibility.js +18 -0
  50. package/client/components/session/session-control.jsx +29 -2
  51. package/client/components/session/session.jsx +40 -18
  52. package/client/components/session/sessions.jsx +5 -2
  53. package/client/components/setting-panel/col.jsx +32 -6
  54. package/client/components/setting-panel/list.styl +12 -0
  55. package/client/components/setting-panel/setting-common.jsx +25 -56
  56. package/client/components/setting-panel/setting-mobile-breadcrumb.jsx +29 -0
  57. package/client/components/setting-panel/setting-modal.jsx +64 -44
  58. package/client/components/setting-panel/setting-terminal.jsx +7 -2
  59. package/client/components/setting-panel/setting-wrap.jsx +0 -3
  60. package/client/components/setting-panel/setting-wrap.styl +80 -11
  61. package/client/components/setting-panel/setting.styl +1 -1
  62. package/client/components/setting-panel/tab-triggers.jsx +71 -0
  63. package/client/components/sftp/file-compare-modal.jsx +6 -3
  64. package/client/components/sftp/file-item.jsx +5 -3
  65. package/client/components/sftp/owner-list.js +11 -2
  66. package/client/components/side-panel-r/side-panel-r.jsx +2 -2
  67. package/client/components/sidebar/index.jsx +28 -21
  68. package/client/components/sidebar/left-sidebar-icons.jsx +1 -1
  69. package/client/components/sidebar/sidebar.styl +16 -0
  70. package/client/components/sys-menu/menu-btn.jsx +30 -1
  71. package/client/components/tabs/index.jsx +2 -2
  72. package/client/components/tabs/tab-title.jsx +3 -2
  73. package/client/components/tabs/tab.jsx +5 -1
  74. package/client/components/tabs/tabs.styl +4 -0
  75. package/client/components/terminal/attach-addon-custom.js +295 -49
  76. package/client/components/terminal/automation/index.js +64 -0
  77. package/client/components/terminal/automation/keys.js +49 -0
  78. package/client/components/terminal/automation/stream-matcher.js +82 -0
  79. package/client/components/terminal/automation/strip-ansi.js +19 -0
  80. package/client/components/terminal/automation/trigger-engine.js +233 -0
  81. package/client/components/terminal/automation/trigger-presets.js +110 -0
  82. package/client/components/terminal/command-tracker-addon.js +31 -1
  83. package/client/components/terminal/mixins/index.js +27 -0
  84. package/client/components/terminal/mixins/term-attach.js +123 -0
  85. package/client/components/terminal/mixins/term-config.jsx +84 -0
  86. package/client/components/terminal/mixins/term-content.js +88 -0
  87. package/client/components/terminal/mixins/term-context-menu.jsx +291 -0
  88. package/client/components/terminal/mixins/term-file-drop.js +149 -0
  89. package/client/components/terminal/mixins/term-init.js +122 -0
  90. package/client/components/terminal/mixins/term-log.jsx +96 -0
  91. package/client/components/terminal/mixins/term-resize.js +87 -0
  92. package/client/components/terminal/mixins/term-socket.js +433 -0
  93. package/client/components/terminal/mixins/term-suggestions.js +224 -0
  94. package/client/components/terminal/mixins/term-theme.js +190 -0
  95. package/client/components/terminal/mixins/term-touch.js +136 -0
  96. package/client/components/terminal/shell-detect.js +37 -0
  97. package/client/components/terminal/shell.js +0 -28
  98. package/client/components/terminal/shortcut-bar-entry.jsx +32 -27
  99. package/client/components/terminal/shortcut-bar.jsx +4 -6
  100. package/client/components/terminal/shortcut-bar.styl +4 -0
  101. package/client/components/terminal/ssh-reload-state.js +56 -0
  102. package/client/components/terminal/startup-queue.js +379 -0
  103. package/client/components/terminal/terminal-interactive.jsx +32 -0
  104. package/client/components/terminal/terminal.jsx +102 -2007
  105. package/client/components/terminal/transfer-client-base.js +5 -29
  106. package/client/components/terminal/trzsz-client.js +1 -3
  107. package/client/components/terminal/xmodem-client.js +1 -3
  108. package/client/components/terminal/xterm-loader.js +7 -0
  109. package/client/components/terminal/zmodem-client.js +1 -3
  110. package/client/components/terminal-info/base.jsx +18 -70
  111. package/client/components/terminal-info/terminal-info-entry.jsx +13 -5
  112. package/client/components/terminal-info/terminal-info.jsx +50 -59
  113. package/client/components/terminal-info/terminal-info.styl +6 -49
  114. package/client/components/text-editor/open-text-editor.js +30 -0
  115. package/client/components/text-editor/text-editor-entry.jsx +17 -6
  116. package/client/components/text-editor/text-editor.jsx +14 -0
  117. package/client/components/theme/theme-form.jsx +22 -0
  118. package/client/components/tree-list/bookmark-transport.jsx +7 -31
  119. package/client/components/tree-list/tree-list.styl +13 -0
  120. package/client/components/triggers/trigger-editor.jsx +246 -0
  121. package/client/components/triggers/trigger-form.jsx +200 -0
  122. package/client/components/triggers/trigger-session-modal.jsx +199 -0
  123. package/client/components/triggers/trigger-setting-list.jsx +89 -0
  124. package/client/components/triggers/trigger-transport.jsx +60 -0
  125. package/client/components/triggers/triggers-field.jsx +19 -0
  126. package/client/components/widgets/widgets-list.jsx +11 -1
  127. package/client/css/mobile.styl +9 -1
  128. package/client/store/common.js +12 -1
  129. package/client/store/init-state.js +14 -0
  130. package/client/store/mcp-handler.js +158 -30
  131. package/client/store/setting.js +20 -0
  132. package/client/store/sidebar.js +12 -0
  133. package/client/store/store.js +2 -0
  134. package/client/store/sync.js +45 -9
  135. package/client/store/system-menu.js +2 -0
  136. package/client/store/tab.js +41 -3
  137. package/client/store/trigger.js +118 -0
  138. package/client/store/watch.js +13 -5
  139. package/client/views/index.pug +3 -0
  140. package/package.json +1 -1
  141. package/client/components/terminal-info/activity.jsx +0 -57
  142. package/client/components/terminal-info/data-cols-parser.jsx +0 -54
  143. package/client/components/terminal-info/disk.jsx +0 -45
  144. package/client/components/terminal-info/format-disks.js +0 -45
  145. package/client/components/terminal-info/network.jsx +0 -121
  146. package/client/components/terminal-info/resource.jsx +0 -99
  147. package/client/components/terminal-info/run-cmd.jsx +0 -263
  148. package/client/components/terminal-info/sys-info.jsx +0 -56
  149. package/client/components/terminal-info/up.jsx +0 -17
@@ -4,16 +4,15 @@
4
4
  */
5
5
 
6
6
  // import { transferTypeMap } from '../../common/constants.js'
7
- import { getItem, setItem } from '../../common/safe-local-storage.js'
7
+ import { chooseSaveDirectory } from '../../common/choose-save-folder.js'
8
8
  import { getLocalFileInfo } from '../sftp/file-read.js'
9
9
 
10
10
  /**
11
11
  * TransferClientBase class - abstract base for file transfer protocols
12
12
  */
13
13
  export class TransferClientBase {
14
- constructor (terminal, storageKey) {
14
+ constructor (terminal) {
15
15
  this.terminal = terminal
16
- this.storageKey = storageKey
17
16
  this.socket = null
18
17
  this.isActive = false
19
18
  this.currentTransfer = null
@@ -233,38 +232,15 @@ export class TransferClientBase {
233
232
  if (window._apiControlSelectFolder) {
234
233
  const folder = window._apiControlSelectFolder
235
234
  delete window._apiControlSelectFolder
236
- if (this.storageKey) {
237
- setItem(this.storageKey, folder)
238
- }
239
235
  return folder
240
236
  }
241
237
 
242
- // Try to use last saved path
243
- const lastPath = this.storageKey ? getItem(this.storageKey) : null
244
-
245
- const savePaths = await window.api.openDialog({
246
- title: 'Choose a folder to save file(s)',
247
- message: 'Choose a folder to save file(s)',
248
- defaultPath: lastPath || undefined,
249
- properties: [
250
- 'openDirectory',
251
- 'showHiddenFiles',
252
- 'createDirectory',
253
- 'noResolveAliases',
254
- 'treatPackageAsDirectory',
255
- 'dontAddToRecent'
256
- ],
238
+ // Last saved path is remembered by chooseSaveDirectory in localStorage
239
+ const savePath = await chooseSaveDirectory({
257
240
  noBrowserTransfer: true
258
241
  }).catch(() => false)
259
242
 
260
- if (!savePaths || !savePaths.length) {
261
- return null
262
- }
263
-
264
- if (this.storageKey) {
265
- setItem(this.storageKey, savePaths[0])
266
- }
267
- return savePaths[0]
243
+ return savePath || null
268
244
  }
269
245
 
270
246
  /**
@@ -8,14 +8,12 @@ import { filesize } from 'filesize'
8
8
  import { TransferClientBase } from './transfer-client-base.js'
9
9
  import { transferTypeMap } from '../../common/constants.js'
10
10
 
11
- const TRZSZ_SAVE_PATH_KEY = 'trzsz-save-path'
12
-
13
11
  /**
14
12
  * TrzszClient class handles trzsz UI and client-side logic
15
13
  */
16
14
  export class TrzszClient extends TransferClientBase {
17
15
  constructor (terminal) {
18
- super(terminal, TRZSZ_SAVE_PATH_KEY)
16
+ super(terminal)
19
17
  this.transferStartTime = 0
20
18
  this.totalTransferred = 0
21
19
  this.totalSpeed = 0
@@ -9,14 +9,12 @@ import { filesize } from 'filesize'
9
9
  import { TransferClientBase } from './transfer-client-base.js'
10
10
  import { transferTypeMap } from '../../common/constants.js'
11
11
 
12
- const XMODEM_SAVE_PATH_KEY = 'xmodem-save-path'
13
-
14
12
  /**
15
13
  * XmodemClient class handles XMODEM UI and client-side logic
16
14
  */
17
15
  export class XmodemClient extends TransferClientBase {
18
16
  constructor (terminal) {
19
- super(terminal, XMODEM_SAVE_PATH_KEY)
17
+ super(terminal)
20
18
  this.transferStartTime = 0
21
19
  this.pendingMode = null // 'send' or 'receive' - waiting for user action
22
20
  }
@@ -72,6 +72,13 @@ export async function loadImageAddon () {
72
72
  return window.xtermAddons.ImageAddon
73
73
  }
74
74
 
75
+ export async function loadSerializeAddon () {
76
+ if (window.xtermAddons.SerializeAddon) return window.xtermAddons.SerializeAddon
77
+ const mod = await import('@xterm/addon-serialize')
78
+ window.xtermAddons.SerializeAddon = mod.SerializeAddon
79
+ return window.xtermAddons.SerializeAddon
80
+ }
81
+
75
82
  export function getTerminal () {
76
83
  return window.xtermAddons.Terminal
77
84
  }
@@ -8,14 +8,12 @@ import { filesize } from 'filesize'
8
8
  import { TransferClientBase } from './transfer-client-base.js'
9
9
  import { transferTypeMap } from '../../common/constants.js'
10
10
 
11
- const ZMODEM_SAVE_PATH_KEY = 'zmodem-save-path'
12
-
13
11
  /**
14
12
  * ZmodemClient class handles zmodem UI and client-side logic
15
13
  */
16
14
  export class ZmodemClient extends TransferClientBase {
17
15
  constructor (terminal) {
18
- super(terminal, ZMODEM_SAVE_PATH_KEY)
16
+ super(terminal)
19
17
  this.transferStartTime = 0
20
18
  }
21
19
 
@@ -2,23 +2,10 @@
2
2
  * show base terminal info, id sessionID
3
3
  */
4
4
  import { Component } from 'react'
5
- import {
6
- Button,
7
- Dropdown
8
- } from 'antd'
5
+ import ItemFilter from '../common/item-filter'
9
6
  import SwitchLabel from '../common/switch'
10
- import defaults from '../../common/default-setting'
7
+ import { INFO_PANEL_ITEM_IDS } from '../remote-monitor/monitor-model'
11
8
  import { toggleTerminalLog, toggleTerminalLogTimestamp } from '../terminal/terminal-apis'
12
- import {
13
- ClockCircleOutlined,
14
- BorderlessTableOutlined,
15
- DatabaseOutlined,
16
- BarsOutlined,
17
- ApiOutlined,
18
- PartitionOutlined,
19
- FilterOutlined,
20
- CheckOutlined
21
- } from '@ant-design/icons'
22
9
  import { refs } from '../common/ref'
23
10
  import ShowItem from '../common/show-item'
24
11
  import { osResolve } from '../../common/resolve'
@@ -26,15 +13,6 @@ import createDefaultLogPath from '../../common/default-log-path'
26
13
 
27
14
  const e = window.translate
28
15
 
29
- const mapper = {
30
- uptime: <ClockCircleOutlined />,
31
- cpu: <BorderlessTableOutlined />,
32
- mem: <DatabaseOutlined />,
33
- activities: <BarsOutlined />,
34
- network: <ApiOutlined />,
35
- disks: <PartitionOutlined />
36
- }
37
-
38
16
  export default class TerminalInfoBase extends Component {
39
17
  state = {
40
18
  saveTerminalLogToFile: false,
@@ -73,14 +51,14 @@ export default class TerminalInfoBase extends Component {
73
51
  })
74
52
  }
75
53
 
76
- toggleTerminalLogInfo = (h) => {
77
- const { terminalInfos } = this.props
78
- const nv = terminalInfos.includes(h)
79
- ? terminalInfos.filter(f => f !== h)
80
- : [...terminalInfos, h]
81
- window.store.setConfig({
82
- terminalInfos: nv
83
- })
54
+ handleToggleInfo = (id) => {
55
+ const selected = new Set(this.props.terminalInfos || [])
56
+ if (selected.has(id)) {
57
+ selected.delete(id)
58
+ } else {
59
+ selected.add(id)
60
+ }
61
+ window.store.setTerminalInfos(INFO_PANEL_ITEM_IDS.filter(x => selected.has(x)))
84
62
  }
85
63
 
86
64
  handleToggle = () => {
@@ -134,43 +112,13 @@ export default class TerminalInfoBase extends Component {
134
112
  )
135
113
  }
136
114
 
137
- renderInfoSelection () {
138
- const {
139
- terminalInfos
140
- } = this.props
141
- const items = defaults.terminalInfos.map(f => {
142
- const checked = terminalInfos.includes(f)
143
- return {
144
- key: f,
145
- label: (
146
- <span
147
- className='term-info-filter-label'
148
- style={{
149
- display: 'flex',
150
- alignItems: 'center',
151
- gap: '6px'
152
- }}
153
- >
154
- <span style={{ width: '14px' }}>{checked ? <CheckOutlined /> : null}</span>
155
- {mapper[f]} {f}
156
- </span>
157
- ),
158
- onClick: () => this.toggleTerminalLogInfo(f)
159
- }
160
- })
115
+ renderInfoFilter () {
161
116
  return (
162
- <Dropdown
163
- menu={{ items }}
164
- trigger={['click']}
165
- placement='bottomRight'
166
- >
167
- <Button
168
- size='small'
169
- icon={<FilterOutlined />}
170
- >
171
- {e('filter')}({terminalInfos.length}/{defaults.terminalInfos.length})
172
- </Button>
173
- </Dropdown>
117
+ <ItemFilter
118
+ ids={INFO_PANEL_ITEM_IDS}
119
+ onToggle={this.handleToggleInfo}
120
+ selected={this.props.terminalInfos || []}
121
+ />
174
122
  )
175
123
  }
176
124
 
@@ -209,9 +157,9 @@ export default class TerminalInfoBase extends Component {
209
157
  )
210
158
  : null
211
159
  }
212
- <div className='pd2y'>
160
+ <div className='terminal-info-filter-wrap'>
213
161
  {
214
- this.renderInfoSelection()
162
+ this.renderInfoFilter()
215
163
  }
216
164
  </div>
217
165
 
@@ -1,11 +1,19 @@
1
1
  import { lazy, Suspense } from 'react'
2
+ import { auto } from 'manate/react'
3
+ import { getRemoteMonitorTab } from '../remote-monitor/visibility'
4
+ import LazyBoundary from '../common/lazy-boundary'
2
5
 
3
6
  const TerminalInfo = lazy(() => import('./terminal-info'))
4
7
 
5
- export default function TerminalInfoEntry (props) {
8
+ export default auto(function TerminalInfoEntry (props) {
9
+ const tab = getRemoteMonitorTab(props.store)
10
+ if (props.store.rightPanelTab !== 'info' || props.pid !== tab?.id) return null
11
+
6
12
  return (
7
- <Suspense fallback={null}>
8
- <TerminalInfo {...props} />
9
- </Suspense>
13
+ <LazyBoundary>
14
+ <Suspense fallback={null}>
15
+ <TerminalInfo {...props} />
16
+ </Suspense>
17
+ </LazyBoundary>
10
18
  )
11
- }
19
+ })
@@ -1,65 +1,56 @@
1
- /**
2
- * info content module
3
- */
4
-
5
- import { PureComponent } from 'react'
1
+ import { auto } from 'manate/react'
2
+ import { useEffect, useState } from 'react'
6
3
  import TerminalInfoBase from './base'
7
- import TerminalInfoUp from './up'
8
- import TerminalInfoNetwork from './network'
9
- import TerminalInfoResource from './resource'
10
- import TerminalInfoActivities from './activity'
11
- import TerminalInfoDisk from './disk'
12
- import TerminalInfoSys from './sys-info'
13
- import RunCmd from './run-cmd'
4
+ import MonitorDetails from '../remote-monitor/monitor-details'
5
+ import { getInfoPanelItems } from '../remote-monitor/monitor-model'
6
+ import { useMonitorDetails } from '../remote-monitor/use-monitor-details'
7
+ import { createEmptyMonitorSnapshot } from '../remote-monitor/session-monitor'
8
+ import { getRemoteMonitorTab } from '../remote-monitor/visibility'
9
+ import { statusMap } from '../../common/constants'
14
10
  import { runCmd } from '../terminal/terminal-apis'
11
+ import resolveLocalInfo from './local-info-resolver'
15
12
  import './terminal-info.styl'
16
13
 
17
- export default class TerminalInfoContent extends PureComponent {
18
- state = {
19
- uptime: '',
20
- cpu: '',
21
- mem: {},
22
- swap: {},
23
- activities: [],
24
- disks: [],
25
- network: {},
26
- sysInfo: null
27
- }
28
-
29
- setStateRef = (...args) => {
30
- this.setState(...args)
31
- }
32
-
33
- killProcess = async (id) => {
34
- const {
35
- pid
36
- } = this.props
37
- const cmd = `kill ${id}`
38
- runCmd(pid, cmd)
39
- }
14
+ function LocalSystemInfo ({ pid }) {
15
+ const [info, setInfo] = useState(null)
16
+ useEffect(() => {
17
+ let active = true
18
+ resolveLocalInfo(pid).then(info => { if (active) setInfo(info) })
19
+ return () => { active = false }
20
+ }, [pid])
21
+ if (!info) return null
22
+ const snapshot = createEmptyMonitorSnapshot(pid)
23
+ snapshot.groups.sysinfo = { status: 'ready', data: info }
24
+ return <MonitorDetails id='hostname' snapshot={snapshot} config={{}} tab={{}} />
25
+ }
40
26
 
41
- render () {
42
- const { props, state } = this
43
- if (props.rightPanelTab === 'ai') {
44
- return null
45
- }
46
- return (
47
- <>
48
- <TerminalInfoBase {...props} {...state} />
49
- <TerminalInfoSys {...props} {...state} />
50
- <TerminalInfoUp {...props} {...state} />
51
- <TerminalInfoResource
52
- {...props} {...state}
53
- />
54
- <TerminalInfoActivities
55
- {...props}
56
- {...state}
57
- killProcess={this.killProcess}
58
- />
59
- <TerminalInfoNetwork {...props} {...state} />
60
- <TerminalInfoDisk {...props} {...state} />
61
- <RunCmd {...props} setState={this.setStateRef} />
62
- </>
63
- )
64
- }
27
+ function RemoteInfo ({ tab, config }) {
28
+ const items = getInfoPanelItems(config.terminalInfos)
29
+ const { snapshot, levels } = useMonitorDetails(tab.id, items, tab.status === statusMap.success)
30
+ return items.map(id => (
31
+ <MonitorDetails
32
+ key={id}
33
+ id={id}
34
+ snapshot={snapshot}
35
+ levels={levels}
36
+ tab={tab}
37
+ config={config}
38
+ includeActivity={false}
39
+ onKillProcess={pid => runCmd(tab.id, `kill ${pid}`)}
40
+ />
41
+ ))
65
42
  }
43
+
44
+ export default auto(function TerminalInfo ({ store, ...props }) {
45
+ const tab = getRemoteMonitorTab(store)
46
+ if (store.rightPanelTab !== 'info' || props.pid !== tab?.id) return null
47
+ const config = store.config
48
+ return (
49
+ <>
50
+ <TerminalInfoBase {...props} terminalInfos={config.terminalInfos} />
51
+ {props.isRemote
52
+ ? <RemoteInfo key={tab.id} tab={tab} config={config} />
53
+ : <LocalSystemInfo key={tab.id} pid={tab.id} />}
54
+ </>
55
+ )
56
+ })
@@ -2,53 +2,10 @@
2
2
  color var(--text-dark)
3
3
  &:hover
4
4
  color var(--text)
5
- .info-panel-wrap
6
- position fixed
7
- right 0
8
- top 100px
9
- bottom 0
10
- width 500px
11
- background var(--main)
12
- border 1px solid var(--main-darker)
13
- border-radius 5px 0 0 5px
14
- border-right none
15
- color var(--text)
16
- z-index 100
17
- overflow-y scroll
18
-
19
- .drag-handle
20
- left 0
21
- right auto
22
- display block
23
- .terminal-info-section
5
+ .terminal-info-section,
6
+ .right-side-panel-content > .remote-monitor-details
24
7
  padding 10px 0
25
- .terminal-info-sys
26
- border-bottom 1px solid var(--main-darker)
27
- .terminal-info-sys-rows
28
- display flex
29
- flex-direction column
30
- gap 4px
31
- .terminal-info-sys-row
32
- display flex
33
- gap 8px
34
- align-items baseline
35
- font-size 12px
36
- .terminal-info-sys-key
37
- flex none
38
- width 80px
39
- color var(--text-dark)
40
- text-transform capitalize
41
- .terminal-info-sys-val
42
- flex 1
43
- overflow hidden
44
- text-overflow ellipsis
45
- white-space nowrap
46
- .activity-item
47
- max-height 1.6em
48
- line-height 1.6
49
- overflow hidden
50
- &:hover
51
- max-height none
52
- .info-panel-wrap-pin
53
- .toggle-info-panel-wrap
54
- color var(--success)
8
+
9
+ .terminal-info-filter-wrap
10
+ display flex
11
+ justify-content flex-end
@@ -0,0 +1,30 @@
1
+ /**
2
+ * open the text editor, load/mount it on demand
3
+ */
4
+
5
+ import { refsStatic } from '../common/ref'
6
+
7
+ let pending = null
8
+
9
+ /**
10
+ * request to open the text editor with file data,
11
+ * if the editor is not mounted yet, mount it first
12
+ * and keep the data as pending, editor will consume
13
+ * it on mount
14
+ * @param {object} data { id, file }
15
+ */
16
+ export function openTextEditor (data) {
17
+ const editor = refsStatic.get('text-editor')
18
+ if (editor) {
19
+ editor.openEditor(data)
20
+ return
21
+ }
22
+ pending = data
23
+ window.store.textEditorRequested = true
24
+ }
25
+
26
+ export function takePendingTextEditorData () {
27
+ const data = pending
28
+ pending = null
29
+ return data
30
+ }
@@ -1,11 +1,22 @@
1
- import { lazy, Suspense } from 'react'
2
-
3
- const TextEditor = lazy(() => import('./text-editor'))
1
+ import { lazy, Suspense, useMemo } from 'react'
2
+ import LazyBoundary from '../common/lazy-boundary'
4
3
 
5
4
  export default function TextEditorEntry (props) {
5
+ // one lazy component per mount: the entry is remounted (the mount latch is
6
+ // released by the boundary below) exactly when a previous load failed, and
7
+ // React.lazy caches a rejected import() forever — so a retry has to go
8
+ // through a *new* lazy component
9
+ const TextEditor = useMemo(() => lazy(() => import('./text-editor')), [])
10
+ const onError = () => {
11
+ // release the mount latch so the next "edit file" request mounts a fresh
12
+ // lazy component and retries the fetch
13
+ window.store.textEditorRequested = false
14
+ }
6
15
  return (
7
- <Suspense fallback={null}>
8
- <TextEditor {...props} />
9
- </Suspense>
16
+ <LazyBoundary onError={onError}>
17
+ <Suspense fallback={null}>
18
+ <TextEditor {...props} />
19
+ </Suspense>
20
+ </LazyBoundary>
10
21
  )
11
22
  }
@@ -13,6 +13,7 @@ import Modal from '../common/modal'
13
13
  import resolve from '../../common/resolve'
14
14
  import { safeGetItem } from '../../common/safe-local-storage.js'
15
15
  import { refsStatic, refs } from '../common/ref'
16
+ import { takePendingTextEditorData } from './open-text-editor'
16
17
 
17
18
  const e = window.translate
18
19
 
@@ -27,6 +28,19 @@ export default class TextEditor extends PureComponent {
27
28
 
28
29
  componentDidMount () {
29
30
  refsStatic.add('text-editor', this)
31
+ // editor may be mounted by an open request,
32
+ // consume the pending data now
33
+ const data = takePendingTextEditorData()
34
+ if (data) {
35
+ this.openEditor(data)
36
+ }
37
+ }
38
+
39
+ componentWillUnmount () {
40
+ // the entry can be unmounted (a failed chunk load releases the mount
41
+ // latch); without this a later request would find this dead instance and
42
+ // call openEditor on it, so the modal would never show up
43
+ refsStatic.remove('text-editor')
30
44
  }
31
45
 
32
46
  setStateProxy = (state, cb) => {
@@ -40,6 +40,9 @@ export default function ThemeForm (props) {
40
40
  action.current = 'saveOnly'
41
41
  form.submit()
42
42
  }
43
+ function applyOnly () {
44
+ props.store.setTheme(props.formData.id)
45
+ }
43
46
  // A function to validate the input text
44
47
  async function validateInput (_, value) {
45
48
  const input = value
@@ -189,6 +192,25 @@ export default function ThemeForm (props) {
189
192
  if (!id) {
190
193
  return null
191
194
  }
195
+ if (readonly) {
196
+ return (
197
+ <FormItem>
198
+ <Button
199
+ type='primary'
200
+ className='mg1r mg1b'
201
+ onClick={applyOnly}
202
+ >
203
+ {e('apply')}
204
+ </Button>
205
+ <Button
206
+ type='dashed'
207
+ onClick={exporter}
208
+ >
209
+ {e('export')}
210
+ </Button>
211
+ </FormItem>
212
+ )
213
+ }
192
214
  return (
193
215
  <FormItem>
194
216
  <Button
@@ -5,8 +5,7 @@
5
5
  import { PureComponent } from 'react'
6
6
  import {
7
7
  ExportOutlined,
8
- ImportOutlined,
9
- EditOutlined
8
+ ImportOutlined
10
9
  } from '@ant-design/icons'
11
10
  import { Button, Space } from 'antd'
12
11
  import Upload from '../common/upload'
@@ -19,21 +18,6 @@ export default class BookmarkTransport extends PureComponent {
19
18
 
20
19
  handleDownload = () => {}
21
20
 
22
- handleToggleEdit = () => {
23
- window.store.bookmarkSelectMode = true
24
- }
25
-
26
- renderEdit () {
27
- return (
28
- <Button
29
- icon={<EditOutlined />}
30
- onClick={this.handleToggleEdit}
31
- title={e('edit')}
32
- key='edit-and-del'
33
- />
34
- )
35
- }
36
-
37
21
  renderExport () {
38
22
  return (
39
23
  <Button
@@ -63,19 +47,11 @@ export default class BookmarkTransport extends PureComponent {
63
47
  }
64
48
 
65
49
  render () {
66
- const edit = this.renderEdit()
67
- if (edit === null) {
68
- return (
69
- <Space.Compact>
70
- {this.renderExport()}
71
- {this.renderImport()}
72
- </Space.Compact>
73
- )
74
- }
75
- return [
76
- edit,
77
- this.renderExport(),
78
- this.renderImport()
79
- ]
50
+ return (
51
+ <Space.Compact>
52
+ {this.renderExport()}
53
+ {this.renderImport()}
54
+ </Space.Compact>
55
+ )
80
56
  }
81
57
  }
@@ -187,3 +187,16 @@
187
187
  &.is-active
188
188
  opacity 1
189
189
  pointer-events auto
190
+ // touch screens have no hover — always reveal the hover-only action icons
191
+ .is-touch-device
192
+ .tree-list-row
193
+ .tree-item-title
194
+ padding-right 60px
195
+ .tree-item-op-wrap
196
+ opacity 1
197
+ pointer-events auto
198
+ .add-menu-wrap
199
+ .sidebar-panel
200
+ .tree-list-row
201
+ .tree-item-title
202
+ padding-right 20px