@electerm/electerm-react 5.0.7 → 5.1.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 (48) hide show
  1. package/client/common/constants.js +2 -0
  2. package/client/common/default-setting.js +10 -1
  3. package/client/common/left-sidebar-icon-defs.js +36 -0
  4. package/client/components/ai/ai-config-props.js +8 -0
  5. package/client/components/bookmark-form/common/fields.jsx +3 -2
  6. package/client/components/bookmark-form/common/ssh-agent.jsx +3 -2
  7. package/client/components/bookmark-form/common/x11.jsx +2 -2
  8. package/client/components/common/logo-elem.jsx +1 -1
  9. package/client/components/common/logo.styl +8 -2
  10. package/client/components/common/switch.jsx +63 -0
  11. package/client/components/common/switch.styl +7 -0
  12. package/client/components/footer/cmd-history.jsx +10 -8
  13. package/client/components/footer/cmd-history.styl +8 -0
  14. package/client/components/layout/layout.jsx +8 -5
  15. package/client/components/main/main.jsx +7 -2
  16. package/client/components/profile/profile-form-elem.jsx +2 -2
  17. package/client/components/quick-commands/quick-commands-box.jsx +2 -19
  18. package/client/components/quick-commands/quick-commands-form-elem.jsx +2 -2
  19. package/client/components/quick-commands/quick-commands-select.jsx +10 -20
  20. package/client/components/rdp/rdp-session.jsx +3 -4
  21. package/client/components/setting-panel/setting-common.jsx +9 -5
  22. package/client/components/setting-panel/setting-left-sidebar-icons.jsx +54 -0
  23. package/client/components/setting-panel/setting-terminal.jsx +3 -4
  24. package/client/components/setting-sync/data-import.jsx +3 -4
  25. package/client/components/setting-sync/setting-sync-form.jsx +3 -2
  26. package/client/components/sftp/file-compare-modal.jsx +1 -1
  27. package/client/components/sidebar/history.jsx +9 -11
  28. package/client/components/sidebar/index.jsx +22 -71
  29. package/client/components/sidebar/left-sidebar-icons.jsx +148 -0
  30. package/client/components/sidebar/sidebar.styl +6 -0
  31. package/client/components/sidebar/transfer-list.jsx +1 -1
  32. package/client/components/spice/spice-session.jsx +4 -5
  33. package/client/components/tabs/no-session.jsx +1 -1
  34. package/client/components/tabs/no-session.styl +31 -8
  35. package/client/components/terminal/shortcut-bar-defs.js +131 -0
  36. package/client/components/terminal/shortcut-bar-edit.jsx +205 -0
  37. package/client/components/terminal/shortcut-bar-entry.jsx +34 -0
  38. package/client/components/terminal/shortcut-bar.jsx +150 -0
  39. package/client/components/terminal/shortcut-bar.styl +186 -0
  40. package/client/components/terminal/terminal-color-query.mjs +47 -0
  41. package/client/components/terminal/terminal.jsx +39 -1
  42. package/client/components/terminal/terminal.styl +8 -0
  43. package/client/components/terminal-info/base.jsx +5 -7
  44. package/client/components/widgets/widget-form.jsx +3 -2
  45. package/client/css/mobile.styl +18 -6
  46. package/client/store/init-state.js +3 -0
  47. package/client/store/mcp-handler.js +29 -1
  48. package/package.json +1 -1
@@ -4,7 +4,6 @@
4
4
 
5
5
  import {
6
6
  Button,
7
- Switch,
8
7
  Select,
9
8
  Space
10
9
  } from 'antd'
@@ -13,6 +12,7 @@ import {
13
12
  ExportOutlined
14
13
  } from '@ant-design/icons'
15
14
  import Upload from '../common/upload'
15
+ import SwitchLabel from '../common/switch'
16
16
  import HelpIcon from '../common/help-icon'
17
17
 
18
18
  const e = window.translate
@@ -87,11 +87,10 @@ export default function DataTransport (props) {
87
87
  </Upload>
88
88
  </div>
89
89
  <div className='fright'>
90
- <Switch
90
+ <SwitchLabel
91
91
  checked={autoSyncEnabled}
92
- checkedChildren={txt}
92
+ label={txt}
93
93
  onChange={handleAutoSync}
94
- unCheckedChildren={txt}
95
94
  className='mg3l mg1r'
96
95
  />
97
96
  {autoSyncEnabled && (
@@ -7,8 +7,9 @@
7
7
  */
8
8
  import { useEffect, useRef } from 'react'
9
9
  import { ArrowDownOutlined, ArrowUpOutlined, SaveOutlined, ClearOutlined } from '@ant-design/icons'
10
- import { Button, Input, Form, Alert, Switch } from 'antd'
10
+ import { Button, Input, Form, Alert } from 'antd'
11
11
  import { notification } from '../common/notification'
12
+ import SwitchLabel from '../common/switch'
12
13
  import Link from '../common/external-link'
13
14
  import dayjs from 'dayjs'
14
15
  import eq from 'fast-deep-equal'
@@ -253,7 +254,7 @@ export default function SyncForm (props) {
253
254
  name='skipVerify'
254
255
  valuePropName='checked'
255
256
  >
256
- <Switch />
257
+ <SwitchLabel />
257
258
  </FormItem>
258
259
  <FormItem
259
260
  label={createLabel(e('password'))}
@@ -21,7 +21,7 @@ const formatTime = time
21
21
  const CodeCompare = lazy(() => import('./code-compare'))
22
22
 
23
23
  const Loading = () => (
24
- <div style={{ padding: 40, textAlign: 'center' }}>
24
+ <div className='aligncenter pd3'>
25
25
  <Spin />
26
26
  </div>
27
27
  )
@@ -4,8 +4,8 @@
4
4
 
5
5
  import React, { useState, useEffect } from 'react'
6
6
  import { auto } from 'manate/react'
7
- import { Switch } from 'antd'
8
7
  import { UnorderedListOutlined } from '@ant-design/icons'
8
+ import SwitchLabel from '../common/switch'
9
9
  import HistoryItem from './history-item'
10
10
  import { getItemJSON, setItemJSON } from '../../common/safe-local-storage.js'
11
11
  import '../setting-panel/list.styl'
@@ -44,22 +44,20 @@ export default auto(function HistoryPanel (props) {
44
44
  }
45
45
  return (
46
46
  <div className='history-header pd2x pd2b'>
47
- <Switch
48
- {...switchProps}
49
- />
47
+ <div className='history-sort'>
48
+ <SwitchLabel
49
+ checked={sortByFrequency}
50
+ onChange={handleSortByFrequencyChange}
51
+ size='small'
52
+ label={e('sortByFrequency')}
53
+ />
54
+ </div>
50
55
  <UnorderedListOutlined
51
56
  {...clearIconProps}
52
57
  />
53
58
  </div>
54
59
  )
55
60
  }
56
- const switchProps = {
57
- checkedChildren: e('sortByFrequency'),
58
- unCheckedChildren: e('sortByFrequency'),
59
- checked: sortByFrequency,
60
- onChange: handleSortByFrequencyChange,
61
- size: 'small'
62
- }
63
61
  const clearIconProps = {
64
62
  className: 'history-clear-icon pointer clear-ai-icon icon-hover',
65
63
  title: window.translate('clear'),
@@ -1,27 +1,20 @@
1
1
  import {
2
- BookOutlined,
3
- CloudSyncOutlined,
4
2
  InfoCircleOutlined,
5
- PictureOutlined,
6
- PlusCircleOutlined,
7
- SettingOutlined,
8
3
  UpCircleOutlined,
9
- AppstoreOutlined,
10
- ThunderboltOutlined,
11
4
  AimOutlined,
12
5
  MenuFoldOutlined
13
6
  } from '@ant-design/icons'
14
- import { Tooltip, Popover } from 'antd'
7
+ import { Tooltip } from 'antd'
15
8
  import SideBarPanel from './sidebar-panel'
16
- import TransferList from './transfer-list'
17
9
  import MenuBtn from '../sys-menu/menu-btn'
18
- import QuickConnect from '../tabs/quick-connect'
19
10
  import {
20
11
  settingMap,
21
12
  modals
22
13
  } from '../../common/constants'
23
14
  import SideIcon from './side-icon'
24
15
  import SidePanel from './side-panel'
16
+ import LeftSidebarIcons from './left-sidebar-icons'
17
+ import TransferList from './transfer-list'
25
18
  import hasActiveInput from '../../common/has-active-input'
26
19
  import './sidebar.styl'
27
20
 
@@ -47,7 +40,9 @@ export default function Sidebar (props) {
47
40
  showInfoModal,
48
41
  sidebarPanelTab,
49
42
  openWidgetsModal,
50
- zoom
43
+ zoom,
44
+ leftSideBarIcons,
45
+ widgetInstancesLength
51
46
  } = props
52
47
 
53
48
  const { store } = window
@@ -146,67 +141,23 @@ export default function Sidebar (props) {
146
141
  <div className='control-icon-wrap'>
147
142
  <MenuBtn store={store} config={store.config} />
148
143
  </div>
149
- <SideIcon
150
- title={e('newBookmark')}
151
- >
152
- <PlusCircleOutlined
153
- className='font22 iblock control-icon'
154
- onClick={onNewSsh}
155
- />
156
- </SideIcon>
157
- <Popover
158
- content={<QuickConnect inputOnly />}
159
- trigger='click'
160
- placement='right'
161
- >
162
- <div className='control-icon-wrap' title={e('quickConnect')}>
163
- <ThunderboltOutlined
164
- className='font20 iblock control-icon'
165
- />
166
- </div>
167
- </Popover>
168
- <SideIcon
169
- title={e(settingMap.bookmarks)}
170
- active={bookmarksActive}
171
- >
172
- <BookOutlined
173
- onClick={handleClickBookmark}
174
- className='font20 iblock control-icon'
175
- />
176
- </SideIcon>
144
+ <LeftSidebarIcons
145
+ iconIds={leftSideBarIcons}
146
+ handleClickBookmark={handleClickBookmark}
147
+ onNewSsh={onNewSsh}
148
+ openSetting={openSetting}
149
+ openSettingSync={openSettingSync}
150
+ openTerminalThemes={openTerminalThemes}
151
+ openWidgetsModal={openWidgetsModal}
152
+ bookmarksActive={bookmarksActive}
153
+ themeActive={themeActive}
154
+ settingActive={settingActive}
155
+ syncActive={syncActive}
156
+ widgetsActive={widgetsActive}
157
+ isSyncingSetting={isSyncingSetting}
158
+ widgetInstancesLength={widgetInstancesLength}
159
+ />
177
160
  <TransferList {...transferProps} />
178
- <SideIcon
179
- title={e(settingMap.terminalThemes)}
180
- active={themeActive}
181
- >
182
- <PictureOutlined
183
- className='font20 iblock pointer control-icon'
184
- onClick={openTerminalThemes}
185
- />
186
- </SideIcon>
187
- <SideIcon
188
- title={e(settingMap.setting)}
189
- active={settingActive}
190
- >
191
- <SettingOutlined className='iblock font20 control-icon' onClick={openSetting} />
192
- </SideIcon>
193
- <SideIcon
194
- title={e('settingSync')}
195
- active={syncActive}
196
- >
197
- <CloudSyncOutlined
198
- className='iblock font20 control-icon'
199
- onClick={openSettingSync}
200
- spin={isSyncingSetting}
201
- />
202
- </SideIcon>
203
- <SideIcon
204
- title={e('widgets')}
205
- active={widgetsActive}
206
- >
207
- <AppstoreOutlined className='iblock font20 control-icon' onClick={openWidgetsModal} />
208
- </SideIcon>
209
-
210
161
  <SideIcon
211
162
  title={e('about')}
212
163
  active={showInfoModal}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * renders the customizable far-left sidebar icons.
3
+ *
4
+ * which icons appear, and in what order, is driven by `iconIds`
5
+ * (from config.leftSideBarIcons). The "about", "hide sidebar" and
6
+ * "file transfer" icons are not customizable — they are rendered
7
+ * directly by the parent (sidebar/index.jsx).
8
+ */
9
+
10
+ import {
11
+ AppstoreOutlined,
12
+ BookOutlined,
13
+ CloudSyncOutlined,
14
+ PictureOutlined,
15
+ PlusCircleOutlined,
16
+ SettingOutlined,
17
+ ThunderboltOutlined
18
+ } from '@ant-design/icons'
19
+ import { Badge, Popover } from 'antd'
20
+ import SideIcon from './side-icon'
21
+ import QuickConnect from '../tabs/quick-connect'
22
+ import { settingMap } from '../../common/constants'
23
+ import { defaultLeftSideBarIcons } from '../../common/left-sidebar-icon-defs'
24
+
25
+ const e = window.translate
26
+
27
+ export default function LeftSidebarIcons (props) {
28
+ const {
29
+ iconIds,
30
+ handleClickBookmark,
31
+ onNewSsh,
32
+ openSetting,
33
+ openSettingSync,
34
+ openTerminalThemes,
35
+ openWidgetsModal,
36
+ bookmarksActive,
37
+ themeActive,
38
+ settingActive,
39
+ syncActive,
40
+ widgetsActive,
41
+ isSyncingSetting,
42
+ widgetInstancesLength
43
+ } = props
44
+
45
+ const ids = Array.isArray(iconIds) && iconIds.length
46
+ ? iconIds
47
+ : defaultLeftSideBarIcons
48
+
49
+ const registry = {
50
+ newBookmark: (
51
+ <SideIcon
52
+ key='newBookmark'
53
+ title={e('newBookmark')}
54
+ >
55
+ <PlusCircleOutlined
56
+ className='font22 iblock control-icon'
57
+ onClick={onNewSsh}
58
+ />
59
+ </SideIcon>
60
+ ),
61
+ quickConnect: (
62
+ <Popover
63
+ key='quickConnect'
64
+ content={<QuickConnect inputOnly />}
65
+ trigger='click'
66
+ placement='right'
67
+ >
68
+ <div
69
+ className='control-icon-wrap'
70
+ title={e('quickConnect')}
71
+ >
72
+ <ThunderboltOutlined className='font20 iblock control-icon' />
73
+ </div>
74
+ </Popover>
75
+ ),
76
+ bookmarks: (
77
+ <SideIcon
78
+ key='bookmarks'
79
+ title={e(settingMap.bookmarks)}
80
+ active={bookmarksActive}
81
+ >
82
+ <BookOutlined
83
+ onClick={handleClickBookmark}
84
+ className='font20 iblock control-icon'
85
+ />
86
+ </SideIcon>
87
+ ),
88
+ terminalThemes: (
89
+ <SideIcon
90
+ key='terminalThemes'
91
+ title={e(settingMap.terminalThemes)}
92
+ active={themeActive}
93
+ >
94
+ <PictureOutlined
95
+ className='font20 iblock pointer control-icon'
96
+ onClick={openTerminalThemes}
97
+ />
98
+ </SideIcon>
99
+ ),
100
+ setting: (
101
+ <SideIcon
102
+ key='setting'
103
+ title={e(settingMap.setting)}
104
+ active={settingActive}
105
+ >
106
+ <SettingOutlined
107
+ className='iblock font20 control-icon'
108
+ onClick={openSetting}
109
+ />
110
+ </SideIcon>
111
+ ),
112
+ settingSync: (
113
+ <SideIcon
114
+ key='settingSync'
115
+ title={e('settingSync')}
116
+ active={syncActive}
117
+ >
118
+ <CloudSyncOutlined
119
+ className='iblock font20 control-icon'
120
+ onClick={openSettingSync}
121
+ spin={isSyncingSetting}
122
+ />
123
+ </SideIcon>
124
+ ),
125
+ widgets: (
126
+ <SideIcon
127
+ key='widgets'
128
+ title={e('widgets')}
129
+ active={widgetsActive}
130
+ >
131
+ <Badge
132
+ count={widgetInstancesLength}
133
+ size='small'
134
+ offset={[0, 0]}
135
+ color='green'
136
+ overflowCount={99}
137
+ >
138
+ <AppstoreOutlined
139
+ className='iblock font20 control-icon'
140
+ onClick={openWidgetsModal}
141
+ />
142
+ </Badge>
143
+ </SideIcon>
144
+ )
145
+ }
146
+
147
+ return ids.map(id => registry[id] || null)
148
+ }
@@ -57,6 +57,12 @@
57
57
  top 0
58
58
  z-index 10
59
59
  background var(--main)
60
+ .history-sort
61
+ display flex
62
+ align-items center
63
+ gap 6px
64
+ .history-sort-label
65
+ user-select none
60
66
  .history-clear-icon
61
67
  margin-left auto
62
68
  margin-right 0
@@ -25,7 +25,7 @@ export default memo(function TransferList (props) {
25
25
  const bdProps = {
26
26
  count: len,
27
27
  size: 'small',
28
- offset: [-10, -5],
28
+ offset: [0, 0],
29
29
  color,
30
30
  overflowCount: 99
31
31
  }
@@ -10,11 +10,11 @@ import {
10
10
  ReloadOutlined
11
11
  } from '@ant-design/icons'
12
12
  import {
13
- Spin,
14
- Switch
13
+ Spin
15
14
  } from 'antd'
16
15
  import * as ls from '../../common/safe-local-storage'
17
16
  import RemoteFloatControl from '../common/remote-float-control'
17
+ import SwitchLabel from '../common/switch'
18
18
  import './spice.styl'
19
19
 
20
20
  async function loadSpiceModule () {
@@ -132,8 +132,7 @@ export default class SpiceSession extends PureComponent {
132
132
  const scaleProps = {
133
133
  checked: this.state.scaleViewport,
134
134
  onChange: this.handleScaleViewChange,
135
- unCheckedChildren: window.translate('scaleViewport'),
136
- checkedChildren: window.translate('scaleViewport'),
135
+ label: window.translate('scaleViewport'),
137
136
  className: 'mg1l'
138
137
  }
139
138
  return (
@@ -144,7 +143,7 @@ export default class SpiceSession extends PureComponent {
144
143
  className='mg2r mg1l pointer'
145
144
  />
146
145
  {this.renderInfo()}
147
- <Switch
146
+ <SwitchLabel
148
147
  {...scaleProps}
149
148
  />
150
149
  </div>
@@ -51,7 +51,7 @@ export default function NoSessionPanel ({ height, onNewTab, onNewSsh, batch }) {
51
51
  )}
52
52
  <QuickConnect batch={batch} />
53
53
  </div>
54
- <div className='pd3'>
54
+ <div className='no-session-logo'>
55
55
  <LogoElem />
56
56
  </div>
57
57
  <div className='no-session-history' onClick={handleClick}>
@@ -1,28 +1,51 @@
1
1
  .no-sessions
2
2
  background var(--main)
3
3
  text-align center
4
- padding 50px 0
4
+ // stack buttons / logo / history; the history flexes to fill the remaining
5
+ // height, so the layout adapts to logo size and button wrapping without any
6
+ // fixed pixel offsets.
7
+ display flex
8
+ flex-direction column
5
9
  position absolute
6
10
  top 36px
7
11
  left 0
8
12
  right 0
9
13
  bottom 0
14
+ padding 50px 0
10
15
  z-index 11
11
16
  .no-session-btns
17
+ flex-shrink 0
12
18
  display flex
13
19
  flex-wrap wrap
14
20
  justify-content center
15
21
  align-items center
16
22
  gap 8px
17
23
  padding-bottom 8px
24
+ .no-session-logo
25
+ flex-shrink 0
26
+ padding 32px 0
18
27
  .no-session-history
19
- position absolute
20
- top 320px
21
- bottom 80px
22
- left 0
23
- right 0
28
+ flex 1
29
+ min-height 0
30
+ display flex
31
+ flex-direction column
32
+ overflow hidden
24
33
  .sidebar-panel-history
25
- overflow-y auto
34
+ // override the global sidebar absolute positioning (top:112px) so the
35
+ // history fills this flex item instead.
36
+ position static
37
+ flex 1
38
+ min-height 0
26
39
  .pd2x
27
40
  width 400px
28
- margin 0 auto
41
+ margin 0 auto
42
+ .is-mobile
43
+ .no-sessions
44
+ padding 20px 0
45
+ .no-session-logo
46
+ padding 8px 0
47
+ .no-session-history
48
+ .sidebar-panel-history
49
+ .pd2x
50
+ width auto
51
+ max-width 400px
@@ -0,0 +1,131 @@
1
+ /**
2
+ * shortcut-bar data: the candidate button library, the default selection, and
3
+ * localStorage persistence. Pure data (no React) so it can be shared by the
4
+ * bar (shortcut-bar.jsx) and the edit modal (shortcut-bar-edit.jsx).
5
+ */
6
+
7
+ import * as ls from '../../common/safe-local-storage'
8
+ import { shortcutBarLsKey } from '../../common/constants'
9
+
10
+ // build control bytes at runtime — keeps source free of literal control chars
11
+ const ESC = String.fromCharCode(0x1b)
12
+ const DEL = String.fromCharCode(0x7f)
13
+
14
+ // --- candidate library -----------------------------------------------------
15
+
16
+ const ctrlChars = 'abcdefghijklmnopqrstuvwxyz'.split('')
17
+ const ctrlButtons = ctrlChars.map(ch => ({
18
+ id: 'ctrl+' + ch,
19
+ label: 'Ctrl+' + ch.toUpperCase(),
20
+ data: String.fromCharCode(ch.charCodeAt(0) - 96)
21
+ }))
22
+
23
+ const fKeys = [
24
+ ['f1', 'F1', ESC + 'OP'],
25
+ ['f2', 'F2', ESC + 'OQ'],
26
+ ['f3', 'F3', ESC + 'OR'],
27
+ ['f4', 'F4', ESC + 'OS'],
28
+ ['f5', 'F5', ESC + '[15~'],
29
+ ['f6', 'F6', ESC + '[17~'],
30
+ ['f7', 'F7', ESC + '[18~'],
31
+ ['f8', 'F8', ESC + '[19~'],
32
+ ['f9', 'F9', ESC + '[20~'],
33
+ ['f10', 'F10', ESC + '[21~'],
34
+ ['f11', 'F11', ESC + '[23~'],
35
+ ['f12', 'F12', ESC + '[24~']
36
+ ].map(([id, label, data]) => ({ id, label, data }))
37
+
38
+ const navButtons = [
39
+ { id: 'esc', label: 'Esc', data: ESC },
40
+ { id: 'tab', label: 'Tab', data: '\t' },
41
+ { id: 'enter', label: 'Enter', data: '\r' },
42
+ { id: 'space', label: 'Space', data: ' ' },
43
+ { id: 'backspace', label: 'Bksp', data: DEL },
44
+ { id: 'arrow-up', label: '↑', data: ESC + '[A' },
45
+ { id: 'arrow-down', label: '↓', data: ESC + '[B' },
46
+ { id: 'arrow-right', label: '→', data: ESC + '[C' },
47
+ { id: 'arrow-left', label: '←', data: ESC + '[D' },
48
+ { id: 'home', label: 'Home', data: ESC + '[H' },
49
+ { id: 'end', label: 'End', data: ESC + '[F' },
50
+ { id: 'pageup', label: 'PgUp', data: ESC + '[5~' },
51
+ { id: 'pagedown', label: 'PgDn', data: ESC + '[6~' },
52
+ { id: 'insert', label: 'Ins', data: ESC + '[2~' },
53
+ { id: 'delete', label: 'Del', data: ESC + '[3~' }
54
+ ]
55
+
56
+ const punctChars = ':;/~|\\`\'".,(){}[]<>=+-_*&^%$#@!?'
57
+ const punctButtons = punctChars.split('').map(ch => ({
58
+ id: 'char-' + ch.charCodeAt(0),
59
+ label: ch,
60
+ data: ch
61
+ }))
62
+
63
+ // full library, de-duplicated by id
64
+ const candidates = []
65
+ const seenIds = new Set()
66
+ ;[...navButtons, ...ctrlButtons, ...fKeys, ...punctButtons].forEach(b => {
67
+ if (!seenIds.has(b.id)) {
68
+ seenIds.add(b.id)
69
+ candidates.push(b)
70
+ }
71
+ })
72
+
73
+ // ids selected by default — mirrors the keys requested in the feature spec
74
+ const DEFAULT_ACTIVE_IDS = [
75
+ 'esc',
76
+ 'tab',
77
+ 'ctrl+c',
78
+ 'ctrl+v',
79
+ 'ctrl+z',
80
+ 'ctrl+y',
81
+ 'ctrl+a',
82
+ 'ctrl+x',
83
+ 'ctrl+s',
84
+ 'ctrl+f',
85
+ 'ctrl+r',
86
+ 'ctrl+g',
87
+ 'ctrl+h',
88
+ 'ctrl+n',
89
+ 'ctrl+p',
90
+ 'ctrl+l',
91
+ 'enter',
92
+ 'char-58',
93
+ 'arrow-up',
94
+ 'arrow-down',
95
+ 'arrow-left',
96
+ 'arrow-right',
97
+ 'f1',
98
+ 'f2',
99
+ 'f3',
100
+ 'f4',
101
+ 'f5',
102
+ 'f6',
103
+ 'f7',
104
+ 'f8',
105
+ 'f9',
106
+ 'f10',
107
+ 'f11',
108
+ 'f12'
109
+ ]
110
+
111
+ export function defaultActive () {
112
+ const byId = new Map(candidates.map(c => [c.id, c]))
113
+ return DEFAULT_ACTIVE_IDS
114
+ .map(id => byId.get(id))
115
+ .filter(Boolean)
116
+ .map(b => ({ ...b }))
117
+ }
118
+
119
+ export function loadActive () {
120
+ const saved = ls.getItemJSON(shortcutBarLsKey, null)
121
+ if (Array.isArray(saved) && saved.length) {
122
+ return saved
123
+ }
124
+ return defaultActive()
125
+ }
126
+
127
+ export function saveActive (arr) {
128
+ ls.setItemJSON(shortcutBarLsKey, arr)
129
+ }
130
+
131
+ export { candidates }