@electerm/electerm-react 1.51.21 → 1.60.16

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 (39) hide show
  1. package/client/common/constants.js +2 -0
  2. package/client/common/default-setting.js +12 -1
  3. package/client/components/ai/ai-chat-history-item.jsx +69 -0
  4. package/client/components/ai/ai-chat-history.jsx +31 -0
  5. package/client/components/ai/ai-chat.jsx +172 -0
  6. package/client/components/ai/ai-config.jsx +145 -0
  7. package/client/components/ai/ai-output.jsx +118 -0
  8. package/client/components/ai/ai.styl +70 -0
  9. package/client/components/ai/get-brand.js +34 -0
  10. package/client/components/ai/providers.js +14 -0
  11. package/client/components/bookmark-form/rdp-form-ui.jsx +1 -1
  12. package/client/components/footer/batch-input.jsx +13 -67
  13. package/client/components/footer/footer-entry.jsx +19 -3
  14. package/client/components/footer/footer.styl +4 -0
  15. package/client/components/footer/tab-select.jsx +9 -3
  16. package/client/components/layout/layout.jsx +5 -4
  17. package/client/components/main/main.jsx +20 -4
  18. package/client/components/shortcuts/shortcut-control.jsx +17 -2
  19. package/client/components/shortcuts/shortcut-handler.js +24 -8
  20. package/client/components/shortcuts/shortcuts-defaults.js +6 -0
  21. package/client/components/side-panel-r/right-side-panel.styl +6 -7
  22. package/client/components/side-panel-r/side-panel-r.jsx +32 -10
  23. package/client/components/sidebar/app-running-time.jsx +35 -0
  24. package/client/components/sidebar/history-item.jsx +20 -3
  25. package/client/components/sidebar/history.jsx +4 -1
  26. package/client/components/sidebar/info-modal.jsx +2 -0
  27. package/client/components/tabs/app-drag.jsx +1 -1
  28. package/client/components/tabs/index.jsx +16 -43
  29. package/client/components/tabs/no-session.jsx +40 -0
  30. package/client/components/tabs/tabs.styl +6 -0
  31. package/client/components/terminal/index.jsx +2 -2
  32. package/client/store/common.js +37 -2
  33. package/client/store/index.js +2 -290
  34. package/client/store/init-state.js +7 -1
  35. package/client/store/store.js +313 -0
  36. package/client/store/sync.js +4 -1
  37. package/client/store/tab.js +56 -2
  38. package/client/store/watch.js +9 -2
  39. package/package.json +1 -1
@@ -26,37 +26,23 @@ export default class BatchInput extends Component {
26
26
  super(props)
27
27
  this.state = {
28
28
  cmd: '',
29
- selectedTabIds: [props.activeTabId],
30
29
  open: false,
31
30
  enter: false
32
31
  }
33
32
  }
34
33
 
35
- componentDidUpdate (prevProps) {
36
- if (prevProps.activeTabId !== this.props.activeTabId) {
37
- this.setState(prevState => {
38
- const newSelectedTabIds = prevState.selectedTabIds.filter(
39
- id => id !== this.props.activeTabId
40
- )
41
- newSelectedTabIds.unshift(this.props.activeTabId)
42
- return {
43
- selectedTabIds: newSelectedTabIds
44
- }
45
- })
46
- }
47
- }
48
-
49
34
  componentWillUnmount () {
50
35
  clearTimeout(this.timer)
51
36
  }
52
37
 
53
38
  handleEnter = (e) => {
54
- const { cmd, selectedTabIds } = this.state
39
+ const { batchInputSelectedTabIds } = window.store
40
+ const { cmd } = this.state
55
41
  if (!cmd.trim()) {
56
42
  return
57
43
  }
58
44
  window.store.addBatchInput(cmd)
59
- this.props.input(cmd, selectedTabIds)
45
+ this.props.input(cmd, Array.from(batchInputSelectedTabIds))
60
46
  this.setState({
61
47
  cmd: '',
62
48
  open: false
@@ -64,43 +50,6 @@ export default class BatchInput extends Component {
64
50
  e.stopPropagation()
65
51
  }
66
52
 
67
- onSelectAll = () => {
68
- this.setState({
69
- selectedTabIds: this.getTabs().map(tab => tab.id)
70
- })
71
- }
72
-
73
- onSelectNone = () => {
74
- this.setState({
75
- selectedTabIds: [this.props.activeTabId]
76
- })
77
- }
78
-
79
- filterValidTabIds = (tabIds) => {
80
- return tabIds.filter(id => {
81
- return this.props.tabs.some(tab => tab.id === id)
82
- })
83
- }
84
-
85
- onSelect = (id) => {
86
- this.setState(prevState => {
87
- const selectedTabIds = prevState.selectedTabIds.includes(id)
88
- ? prevState.selectedTabIds.filter(tabId => tabId !== id)
89
- : [...prevState.selectedTabIds, id]
90
-
91
- // Ensure at least the current tab is selected
92
- if (selectedTabIds.length === 0) {
93
- return {
94
- selectedTabIds: [this.props.activeTabId]
95
- }
96
- }
97
-
98
- return {
99
- selectedTabIds
100
- }
101
- })
102
- }
103
-
104
53
  handleChange = (v = '') => {
105
54
  let vv = v.replace(/^\d+:/, '').replace(/\n$/, '')
106
55
  if (vv === batchInputLsKey) {
@@ -118,15 +67,9 @@ export default class BatchInput extends Component {
118
67
 
119
68
  handleClick = () => {
120
69
  this.setState({
121
- open: true,
122
- selectedTabIds: this.filterValidTabIds(this.state.selectedTabIds)
123
- })
124
- }
125
-
126
- handleChangeAll = toAll => {
127
- this.setState({
128
- toAll
70
+ open: true
129
71
  })
72
+ window.store.filterBatchInputSelectedTabIds()
130
73
  }
131
74
 
132
75
  handleBlur = () => {
@@ -192,7 +135,10 @@ export default class BatchInput extends Component {
192
135
  }
193
136
 
194
137
  render () {
195
- const { cmd, open, selectedTabIds, enter } = this.state
138
+ const { cmd, open, enter } = this.state
139
+ const {
140
+ batchInputSelectedTabIds
141
+ } = this.props
196
142
  const opts = {
197
143
  options: this.buildOptions(),
198
144
  placeholder: e('batchInput'),
@@ -217,10 +163,10 @@ export default class BatchInput extends Component {
217
163
  const tabSelectProps = {
218
164
  activeTabId: this.props.activeTabId,
219
165
  tabs: this.getTabs(),
220
- selectedTabIds,
221
- onSelectAll: this.onSelectAll,
222
- onSelectNone: this.onSelectNone,
223
- onSelect: this.onSelect
166
+ selectedTabIds: batchInputSelectedTabIds,
167
+ onSelectAll: window.store.selectAllBatchInputTabs,
168
+ onSelectNone: window.store.selectNoneBatchInputTabs,
169
+ onSelect: window.store.onSelectBatchInputSelectedTabId
224
170
  }
225
171
  return (
226
172
  <span
@@ -49,11 +49,13 @@ export default auto(function FooterEntry (props) {
49
49
  }
50
50
 
51
51
  function renderBatchInputs () {
52
+ const { store } = props
52
53
  const batchProps = {
53
54
  input: batchInput,
54
- batchInputs: props.store.batchInputs,
55
- tabs: props.store.tabs,
56
- activeTabId: props.store.activeTabId
55
+ tabs: store.tabs,
56
+ batchInputs: store.batchInputs,
57
+ batchInputSelectedTabIds: store.batchInputSelectedTabIds,
58
+ activeTabId: store.activeTabId
57
59
  }
58
60
  return (
59
61
  <div className='terminal-footer-unit terminal-footer-center'>
@@ -72,6 +74,19 @@ export default auto(function FooterEntry (props) {
72
74
  )
73
75
  }
74
76
 
77
+ function renderAIIcon () {
78
+ return (
79
+ <div className='terminal-footer-unit terminal-footer-ai'>
80
+ <span
81
+ className='ai-icon'
82
+ onClick={window.store.handleOpenAIPanel}
83
+ >
84
+ AI
85
+ </span>
86
+ </div>
87
+ )
88
+ }
89
+
75
90
  function renderEncodingInfo () {
76
91
  const selectProps = {
77
92
  style: {
@@ -148,6 +163,7 @@ export default auto(function FooterEntry (props) {
148
163
  return (
149
164
  <div {...sideProps}>
150
165
  <div className='terminal-footer-flex'>
166
+ {renderAIIcon()}
151
167
  {renderQuickCommands()}
152
168
  {renderBatchInputs()}
153
169
  {renderEncodingInfo()}
@@ -14,6 +14,10 @@
14
14
  .qm-wrap-tooltip
15
15
  left 343px
16
16
 
17
+ .ai-icon
18
+ display inline-block
19
+ padding 0 6px
20
+ cursor pointer
17
21
 
18
22
  .terminal-footer-flex
19
23
  display flex
@@ -14,7 +14,7 @@ export default function TabSelect (props) {
14
14
  const itemProps = {
15
15
  tab,
16
16
  selected,
17
- onSelect: props.onSelect,
17
+ onSelect: window.store.onSelectBatchInputSelectedTabId,
18
18
  id: tab.id,
19
19
  isCurrent: tab.id === activeTabId
20
20
  }
@@ -26,18 +26,24 @@ export default function TabSelect (props) {
26
26
  )
27
27
  })
28
28
  }
29
+ function onSelectAll () {
30
+ window.store.selectAllBatchInputTabs()
31
+ }
32
+ function onSelectNone () {
33
+ window.store.selectNoneBatchInputTabs()
34
+ }
29
35
  function renderBtns () {
30
36
  return (
31
37
  <div className='pd1t pd2b font12'>
32
38
  <span
33
39
  className='mg1r pointer'
34
- onClick={props.onSelectAll}
40
+ onClick={onSelectAll}
35
41
  >
36
42
  All
37
43
  </span>
38
44
  <span
39
45
  className='pointer'
40
- onClick={props.onSelectNone}
46
+ onClick={onSelectNone}
41
47
  >
42
48
  None
43
49
  </span>
@@ -61,12 +61,13 @@ export default auto(function Layout (props) {
61
61
  width,
62
62
  pinnedQuickCommandBar,
63
63
  leftSidebarWidth,
64
- infoPanelPinned,
65
- pinned,
66
- rightSidebarWidth
64
+ rightPanelVisible,
65
+ rightPanelPinned,
66
+ rightPanelWidth,
67
+ pinned
67
68
  } = props.store
68
69
  const l = pinned ? leftSidebarWidth : 0
69
- const r = infoPanelPinned ? rightSidebarWidth : 0
70
+ const r = rightPanelPinned && rightPanelVisible ? rightPanelWidth : 0
70
71
  const w = width - l - r - 42
71
72
  const h = height - footerHeight - (pinnedQuickCommandBar ? quickCommandBoxHeight : 0)
72
73
  return layoutAlg(layout, w, h)
@@ -29,6 +29,7 @@ import RightSidePanel from '../side-panel-r/side-panel-r'
29
29
  import ConnectionHoppingWarning from './connection-hopping-warnning'
30
30
  import SshConfigLoadNotify from '../ssh-config/ssh-config-load-notify'
31
31
  import LoadSshConfigs from '../ssh-config/load-ssh-configs'
32
+ import AIChat from '../ai/ai-chat'
32
33
  import { pick } from 'lodash-es'
33
34
  import deepCopy from 'json-deep-copy'
34
35
  import './wrapper.styl'
@@ -81,7 +82,7 @@ export default auto(function Index (props) {
81
82
  store.isSecondInstance = window.pre.runSync('isSecondInstance')
82
83
  store.initData()
83
84
  store.checkForDbUpgrade()
84
- window.pre.runGlobalAsync('registerDeepLink')
85
+ // window.pre.runGlobalAsync('registerDeepLink')
85
86
  }, [])
86
87
 
87
88
  const { store } = props
@@ -98,7 +99,9 @@ export default auto(function Index (props) {
98
99
  uiThemeConfig,
99
100
  transferHistory,
100
101
  transferToConfirm,
101
- openResolutionEdit
102
+ openResolutionEdit,
103
+ rightPanelTitle,
104
+ rightPanelTab
102
105
  } = store
103
106
  const upgradeInfo = deepCopy(store.upgradeInfo)
104
107
  const cls = classnames({
@@ -190,7 +193,9 @@ export default auto(function Index (props) {
190
193
  const rightPanelProps = {
191
194
  rightPanelVisible: store.rightPanelVisible,
192
195
  rightPanelPinned: store.rightPanelPinned,
193
- rightPanelWidth: store.rightPanelWidth
196
+ rightPanelWidth: store.rightPanelWidth,
197
+ title: rightPanelTitle,
198
+ rightPanelTab
194
199
  }
195
200
  const terminalInfoProps = {
196
201
  ...deepCopy(store.terminalInfoProps),
@@ -213,6 +218,17 @@ export default auto(function Index (props) {
213
218
  hasOldConnectionHoppingBookmark: store.hasOldConnectionHoppingBookmark,
214
219
  configLoaded
215
220
  }
221
+ const aiChatProps = {
222
+ aiChatHistory: store.aiChatHistory,
223
+ config,
224
+ selectedTabIds: store.batchInputSelectedTabIds,
225
+ tabs: store.getTabs(),
226
+ activeTabId: store.activeTabId,
227
+ showAIConfig: store.showAIConfig
228
+ }
229
+ const rightPanelContent = rightPanelTab === 'ai'
230
+ ? <AIChat {...aiChatProps} />
231
+ : <TerminalInfo {...terminalInfoProps} />
216
232
  return (
217
233
  <ConfigProvider
218
234
  theme={uiThemeConfig}
@@ -269,7 +285,7 @@ export default auto(function Index (props) {
269
285
  <Resolutions {...resProps} />
270
286
  <InfoModal {...infoModalProps} />
271
287
  <RightSidePanel {...rightPanelProps}>
272
- <TerminalInfo {...terminalInfoProps} />
288
+ {rightPanelContent}
273
289
  </RightSidePanel>
274
290
  <SshConfigLoadNotify {...sshConfigProps} />
275
291
  <LoadSshConfigs
@@ -9,8 +9,10 @@ import { throttle } from 'lodash-es'
9
9
 
10
10
  class ShortcutControl extends React.PureComponent {
11
11
  componentDidMount () {
12
- window.addEventListener('keydown', this.handleKeyboardEvent.bind(this))
13
- window.addEventListener('mousewheel', this.handleKeyboardEvent.bind(this))
12
+ const onEvent = this.handleKeyboardEvent.bind(this)
13
+ window.addEventListener('keydown', onEvent)
14
+ window.addEventListener('mousedown', onEvent)
15
+ window.addEventListener('mousewheel', onEvent)
14
16
  }
15
17
 
16
18
  closeCurrentTabShortcut = throttle((e) => {
@@ -21,6 +23,19 @@ class ShortcutControl extends React.PureComponent {
21
23
  }
22
24
  }, 500)
23
25
 
26
+ mouseWheelDownCloseTabShortcut = throttle((e) => {
27
+ e.stopPropagation()
28
+
29
+ // Check if the event target is within a .tab element
30
+ const tabElement = e.target.closest('.tab')
31
+ if (tabElement) {
32
+ const tabId = tabElement.getAttribute('data-id')
33
+ if (tabId) {
34
+ window.store.delTab(tabId)
35
+ }
36
+ }
37
+ }, 500)
38
+
24
39
  reloadCurrentTabShortcut = throttle((e) => {
25
40
  e.stopPropagation()
26
41
  window.store.reloadTab()
@@ -53,12 +53,14 @@ export function shortcutExtend (Cls) {
53
53
  metaKey,
54
54
  altKey,
55
55
  wheelDeltaY,
56
+ button,
56
57
  type,
57
58
  key
58
59
  } = event
59
60
  if (this.cmdAddon) {
60
61
  this.cmdAddon.handleKey(event)
61
62
  }
63
+
62
64
  if (
63
65
  this.term &&
64
66
  key === 'Backspace' &&
@@ -66,6 +68,7 @@ export function shortcutExtend (Cls) {
66
68
  !altKey &&
67
69
  !ctrlKey
68
70
  ) {
71
+ console.log('handleKeyboardEvent: Handling Backspace key')
69
72
  this.props.onDelKeyPressed()
70
73
  const delKey = this.props.config.backspaceMode === '^?' ? 8 : 127
71
74
  const altDelDelKey = delKey === 8 ? 127 : 8
@@ -78,6 +81,7 @@ export function shortcutExtend (Cls) {
78
81
  ) {
79
82
  return true
80
83
  }
84
+
81
85
  if (
82
86
  this.term &&
83
87
  key === 'c' &&
@@ -89,31 +93,42 @@ export function shortcutExtend (Cls) {
89
93
  ) {
90
94
  this.onZmodemEnd()
91
95
  }
92
- const codeName = event instanceof window.WheelEvent
93
- ? (wheelDeltaY > 0 ? 'mouseWheelUp' : 'mouseWheelDown')
94
- : code
96
+
97
+ let codeName
98
+ if (type === 'mousedown' && button === 1) {
99
+ codeName = 'mouseWheel'
100
+ } else {
101
+ codeName = event instanceof window.WheelEvent
102
+ ? (wheelDeltaY > 0 ? 'mouseWheelUp' : 'mouseWheelDown')
103
+ : code
104
+ }
105
+
95
106
  const codeK = getKeyCharacter(codeName)
96
- const noControlKey = !ctrlKey && !shiftKey && !metaKey && !altKey
107
+
108
+ const noControlKey = type !== 'mousedown' && !ctrlKey && !shiftKey && !metaKey && !altKey
97
109
  if (noControlKey) {
98
110
  return
99
111
  }
100
- const r = (ctrlKey ? 'ctrl+' : '') +
112
+ const r = codeName === 'mouseWheel'
113
+ ? 'mouseWheel'
114
+ : (ctrlKey ? 'ctrl+' : '') +
101
115
  (metaKey ? 'meta+' : '') +
102
116
  (shiftKey ? 'shift+' : '') +
103
117
  (altKey ? 'alt+' : '') +
104
118
  codeK.toLowerCase()
119
+
105
120
  const shortcutsConfig = buildConfig(this.props.config, d => !d.hidden)
106
121
  const keys = Object.keys(shortcutsConfig)
107
122
  const len = keys.length
123
+
108
124
  if (this.term) {
109
125
  const qmMatch = window.store.quickCommands.find(d => d.shortcut === r)
110
126
  if (qmMatch) {
111
- window.store.runQuickCommandItem(
112
- qmMatch.id
113
- )
127
+ window.store.runQuickCommandItem(qmMatch.id)
114
128
  return false
115
129
  }
116
130
  }
131
+
117
132
  for (let i = 0; i < len; i++) {
118
133
  const k = keys[i]
119
134
  const conf = shortcutsConfig[k]
@@ -128,6 +143,7 @@ export function shortcutExtend (Cls) {
128
143
  }
129
144
  }
130
145
  }
146
+
131
147
  return !!this.term
132
148
  }
133
149
  return Cls
@@ -5,6 +5,12 @@ export default () => {
5
5
  shortcut: 'alt+w',
6
6
  shortcutMac: 'alt+w'
7
7
  },
8
+ {
9
+ name: 'app_mouseWheelDownCloseTab',
10
+ shortcut: 'mouseWheel',
11
+ shortcutMac: 'mouseWheel',
12
+ readonly: true
13
+ },
8
14
  {
9
15
  name: 'app_reloadCurrentTab',
10
16
  shortcut: 'alt+r',
@@ -3,29 +3,28 @@
3
3
  .right-side-panel
4
4
  position absolute
5
5
  right 0
6
- top 0
6
+ top 36px
7
7
  bottom 0
8
8
  z-index 200
9
9
  background main
10
10
  color text-light
11
11
  border-left 1px solid darken(main, 30%)
12
+ &.right-side-panel-pinned
13
+ top 0
12
14
 
13
15
  .drag-handle
14
16
  left 0
15
17
  right auto
16
18
  display block
17
-
19
+ .right-panel-title
20
+ border-bottom 1px solid darken(main, 30%)
18
21
  .right-side-panel-controls
19
- position absolute
20
- left 10px
21
- top 10px
22
22
  color text-dark
23
23
  font-size 16px
24
24
  &:hover
25
25
  color text
26
26
  cursor pointer
27
27
  &.right-side-panel-pin
28
- left 30px
29
28
  &.pinned
30
29
  color success
31
30
  .right-side-panel-content
@@ -33,7 +32,7 @@
33
32
  overflow-y scroll
34
33
  position absolute
35
34
  left 0
36
- top 30px
35
+ top 55px
37
36
  right 0
38
37
  bottom 0
39
38
  .animate-fast
@@ -3,20 +3,31 @@ import DragHandle from '../common/drag-handle'
3
3
  import './right-side-panel.styl'
4
4
  import {
5
5
  CloseCircleOutlined,
6
- PushpinOutlined
6
+ PushpinOutlined,
7
+ InfoCircleOutlined
7
8
  } from '@ant-design/icons'
9
+ import {
10
+ Typography,
11
+ Flex,
12
+ Tag
13
+ } from 'antd'
8
14
 
9
15
  export default memo(function RightSidePanel (
10
16
  {
11
17
  rightPanelVisible,
12
18
  rightPanelPinned,
13
19
  rightPanelWidth,
14
- children
20
+ children,
21
+ title,
22
+ rightPanelTab
15
23
  }
16
24
  ) {
17
25
  if (!rightPanelVisible) {
18
26
  return null
19
27
  }
28
+ const tag = rightPanelTab === 'ai'
29
+ ? <Tag className='mg1r'>AI</Tag>
30
+ : <InfoCircleOutlined className='mg1r' />
20
31
 
21
32
  function onDragEnd (nw) {
22
33
  window.store.setRightSidePanelWidth(nw)
@@ -36,7 +47,7 @@ export default memo(function RightSidePanel (
36
47
  }
37
48
 
38
49
  const panelProps = {
39
- className: 'right-side-panel animate-fast',
50
+ className: 'right-side-panel animate-fast' + (rightPanelPinned ? ' right-side-panel-pinned' : ''),
40
51
  id: 'right-side-panel',
41
52
  style: {
42
53
  width: `${rightPanelWidth}px`
@@ -60,13 +71,24 @@ export default memo(function RightSidePanel (
60
71
  {...panelProps}
61
72
  >
62
73
  <DragHandle {...dragProps} />
63
- <CloseCircleOutlined
64
- className='right-side-panel-close right-side-panel-controls'
65
- onClick={onClose}
66
- />
67
- <PushpinOutlined
68
- {...pinProps}
69
- />
74
+ <Flex
75
+ className='right-panel-title pd2'
76
+ justify='space-between'
77
+ align='center'
78
+ >
79
+ <Typography.Text level={4} ellipsis style={{ margin: 0, flex: 1 }}>
80
+ {tag} {title}
81
+ </Typography.Text>
82
+ <Flex>
83
+ <PushpinOutlined
84
+ {...pinProps}
85
+ />
86
+ <CloseCircleOutlined
87
+ className='right-side-panel-close right-side-panel-controls mg1l'
88
+ onClick={onClose}
89
+ />
90
+ </Flex>
91
+ </Flex>
70
92
  <div className='right-side-panel-content'>
71
93
  {children}
72
94
  </div>
@@ -0,0 +1,35 @@
1
+ import { useState, useEffect } from 'react'
2
+ import {
3
+ ClockCircleOutlined
4
+ } from '@ant-design/icons'
5
+ import dayjs from 'dayjs'
6
+ import duration from 'dayjs/plugin/duration'
7
+
8
+ dayjs.extend(duration)
9
+
10
+ const e = window.translate
11
+
12
+ export default function RunningTime () {
13
+ const [runningTime, setRunningTime] = useState(0)
14
+
15
+ useEffect(() => {
16
+ const startTime = window.pre.runSync('getInitTime')
17
+
18
+ const timer = setInterval(() => {
19
+ const currentTime = Date.now()
20
+ const diff = Math.floor((currentTime - startTime) / 1000)
21
+ setRunningTime(diff)
22
+ }, 1000)
23
+
24
+ return () => clearInterval(timer)
25
+ }, [])
26
+ const formatRunningTime = (seconds) => {
27
+ return dayjs.duration(seconds, 'seconds').format('HH:mm:ss')
28
+ }
29
+ return (
30
+ <p className='mg2b'>
31
+ <ClockCircleOutlined /> <b>{e('runningTime')} ➾</b>
32
+ <span className='mg1l'>{formatRunningTime(runningTime)}</span>
33
+ </p>
34
+ )
35
+ }
@@ -1,3 +1,4 @@
1
+ import React, { useCallback, useEffect, useRef } from 'react'
1
2
  import createTitle, { createTitleWithTag } from '../../common/create-title'
2
3
  import { DeleteOutlined } from '@ant-design/icons'
3
4
 
@@ -7,9 +8,25 @@ export default function HistoryItem (props) {
7
8
  item,
8
9
  index
9
10
  } = props
10
- function handleClick () {
11
- store.onSelectHistory(item.tab)
12
- }
11
+ const timeoutRef = useRef(null)
12
+
13
+ const handleClick = useCallback(() => {
14
+ if (timeoutRef.current) {
15
+ clearTimeout(timeoutRef.current)
16
+ }
17
+ timeoutRef.current = setTimeout(() => {
18
+ store.onSelectHistory(item.tab)
19
+ }, 10)
20
+ }, [item.tab])
21
+
22
+ useEffect(() => {
23
+ return () => {
24
+ if (timeoutRef.current) {
25
+ clearTimeout(timeoutRef.current)
26
+ }
27
+ }
28
+ }, [])
29
+
13
30
  function handleDelete (e) {
14
31
  e.stopPropagation()
15
32
  store.history.splice(index, 1)
@@ -10,13 +10,16 @@ export default auto(function HistoryPanel (props) {
10
10
  const {
11
11
  history
12
12
  } = store
13
+ const arr = props.sort
14
+ ? [...history].sort((a, b) => { return b.count - a.count })
15
+ : history
13
16
  return (
14
17
  <div
15
18
  className='sidebar-panel-history'
16
19
  >
17
20
  <div className='pd2x'>
18
21
  {
19
- history.map((item, i) => {
22
+ arr.map((item, i) => {
20
23
  return (
21
24
  <HistoryItem
22
25
  key={item.id}
@@ -15,6 +15,7 @@ import {
15
15
  import { Modal, Tabs, Button } from 'antd'
16
16
  import Link from '../common/external-link'
17
17
  import LogoElem from '../common/logo-elem'
18
+ import RunningTime from './app-running-time'
18
19
 
19
20
  import {
20
21
  packInfo,
@@ -121,6 +122,7 @@ export default memo(function InfoModal (props) {
121
122
  <div>
122
123
  <LogoElem />
123
124
  <p className='mg2b'>{e('desc')}</p>
125
+ <RunningTime />
124
126
  <p className='mg1b'>
125
127
  <UserOutlined /> <b className='mg1r'>{e('author')} ➾</b>
126
128
  <Link to={authorUrl} className='mg1l'>
@@ -18,7 +18,7 @@ export default function AppDrag (props) {
18
18
  }
19
19
 
20
20
  function onMouseDown (e) {
21
- e.stopPropagation()
21
+ // e.stopPropagation()
22
22
  if (canOperate(e)) {
23
23
  window.pre.runSync('windowMove', true)
24
24
  }