@electerm/electerm-react 5.5.15 → 5.5.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/client/common/constants.js +14 -0
- package/client/common/default-setting.js +2 -2
- package/client/common/parse-vv.js +313 -0
- package/client/common/theme-defaults.js +5 -2
- package/client/common/update-check.js +5 -4
- package/client/components/ai/ai-presets.js +13 -2
- package/client/components/bg/css-overwrite.jsx +16 -1
- package/client/components/bookmark-form/common/fields.jsx +3 -0
- package/client/components/bookmark-form/common/vv-file-field.jsx +137 -0
- package/client/components/bookmark-form/config/spice.js +1 -0
- package/client/components/footer/footer-entry.jsx +39 -14
- package/client/components/icons/ai-icon.styl +4 -1
- package/client/components/layout/layout.jsx +7 -1
- package/client/components/main/main.jsx +9 -2
- package/client/components/main/term-fullscreen.styl +7 -0
- package/client/components/quick-commands/quick-command-ai-editor.jsx +255 -0
- package/client/components/quick-commands/quick-command-ai-editor.styl +13 -0
- package/client/components/quick-commands/quick-commands-box.jsx +23 -5
- package/client/components/quick-commands/quick-commands-form-elem.jsx +129 -65
- package/client/components/setting-panel/list.jsx +2 -2
- package/client/components/side-panel-r/right-side-panel.styl +11 -1
- package/client/components/side-panel-r/side-panel-r.jsx +45 -9
- package/client/components/sidebar/bookmark-select.jsx +3 -1
- package/client/components/sidebar/index.jsx +13 -4
- package/client/components/sidebar/side-panel.jsx +8 -3
- package/client/components/sidebar/sidebar-panel.jsx +20 -7
- package/client/components/terminal/shortcut-bar.styl +14 -0
- package/client/components/terminal/unix-timestamp-tooltip.jsx +27 -1
- package/client/components/terminal-info/terminal-info.styl +3 -0
- package/client/components/theme/terminal-theme-list.styl +3 -0
- package/client/components/theme/theme-ai-editor.jsx +2 -2
- package/client/components/theme/theme-form.jsx +2 -2
- package/client/components/theme/theme-list-item.jsx +59 -25
- package/client/css/mobile.styl +28 -0
- package/client/store/common.js +44 -0
- package/client/store/init-state.js +2 -1
- package/client/store/sidebar.js +5 -1
- package/client/store/store.js +9 -2
- package/package.json +1 -1
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* theme list render
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { useState } from 'react'
|
|
5
|
+
import { useEffect, useState } from 'react'
|
|
6
6
|
import {
|
|
7
7
|
CheckCircleOutlined,
|
|
8
|
+
CloseOutlined,
|
|
8
9
|
PlusOutlined,
|
|
9
10
|
SunOutlined,
|
|
10
|
-
MoonOutlined
|
|
11
|
-
EyeOutlined
|
|
11
|
+
MoonOutlined
|
|
12
12
|
} from '@ant-design/icons'
|
|
13
13
|
import { Tag, Tooltip, Button, Space } from 'antd'
|
|
14
14
|
import classnames from 'classnames'
|
|
@@ -30,46 +30,72 @@ export default function ThemeListItem (props) {
|
|
|
30
30
|
const [tooltipVisible, setTooltipVisible] = useState(false)
|
|
31
31
|
const [isPreviewing, setIsPreviewing] = useState(false)
|
|
32
32
|
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
function startPreview () {
|
|
34
|
+
// keep the very first original, hopping between themes must never
|
|
35
|
+
// overwrite it with a theme that is itself only a preview
|
|
36
|
+
if (!window.originalTheme) {
|
|
37
|
+
window.originalTheme = store.config.theme
|
|
38
|
+
}
|
|
37
39
|
store.setTheme(item.id)
|
|
40
|
+
setIsPreviewing(true)
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
function
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
window.originalTheme = currentTheme
|
|
45
|
-
// Apply the preview theme
|
|
46
|
-
store.setTheme(item.id)
|
|
47
|
-
setIsPreviewing(true)
|
|
43
|
+
function cancelPreview () {
|
|
44
|
+
if (window.originalTheme) {
|
|
45
|
+
store.setTheme(window.originalTheme)
|
|
46
|
+
delete window.originalTheme
|
|
48
47
|
}
|
|
48
|
+
setIsPreviewing(false)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
//
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
return () => {
|
|
53
|
+
// leaving the list mid preview (switching settings tab, filtering the
|
|
54
|
+
// list, ...) must not leave the user stuck on a theme never applied
|
|
55
55
|
if (window.originalTheme) {
|
|
56
56
|
store.setTheme(window.originalTheme)
|
|
57
57
|
delete window.originalTheme
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
}
|
|
60
|
+
}, [])
|
|
61
|
+
|
|
62
|
+
function handleClickApply () {
|
|
63
|
+
delete window.originalTheme
|
|
64
|
+
setIsPreviewing(false)
|
|
65
|
+
setTooltipVisible(false)
|
|
66
|
+
store.setTheme(item.id)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handleClickCancel () {
|
|
70
|
+
cancelPreview()
|
|
71
|
+
setTooltipVisible(false)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleTooltipVisibleChange (visible) {
|
|
75
|
+
setTooltipVisible(visible)
|
|
76
|
+
// opening the popup is the preview itself; every other way of closing it
|
|
77
|
+
// (click elsewhere, esc, clicking the icon again) rolls the preview back
|
|
78
|
+
if (visible) {
|
|
79
|
+
startPreview()
|
|
80
|
+
} else {
|
|
81
|
+
cancelPreview()
|
|
60
82
|
}
|
|
61
83
|
}
|
|
62
84
|
|
|
85
|
+
function handleClickApplyIcon (e) {
|
|
86
|
+
// previewing must not also load the theme into the edit form
|
|
87
|
+
e.stopPropagation()
|
|
88
|
+
}
|
|
89
|
+
|
|
63
90
|
function renderTooltipContent () {
|
|
64
91
|
return (
|
|
65
92
|
<Space.Compact>
|
|
66
93
|
<Button
|
|
67
94
|
size='small'
|
|
68
|
-
icon={<
|
|
69
|
-
onClick={
|
|
70
|
-
type={isPreviewing ? 'primary' : 'default'}
|
|
95
|
+
icon={<CloseOutlined />}
|
|
96
|
+
onClick={handleClickCancel}
|
|
71
97
|
>
|
|
72
|
-
{e('
|
|
98
|
+
{e('cancel')}
|
|
73
99
|
</Button>
|
|
74
100
|
<Button
|
|
75
101
|
size='small'
|
|
@@ -96,7 +122,15 @@ export default function ThemeListItem (props) {
|
|
|
96
122
|
placement='top'
|
|
97
123
|
>
|
|
98
124
|
<CheckCircleOutlined
|
|
99
|
-
className=
|
|
125
|
+
className={
|
|
126
|
+
classnames(
|
|
127
|
+
'pointer list-item-apply',
|
|
128
|
+
{
|
|
129
|
+
'list-item-apply-previewing': isPreviewing
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
onClick={handleClickApplyIcon}
|
|
100
134
|
/>
|
|
101
135
|
</Tooltip>
|
|
102
136
|
)
|
package/client/css/mobile.styl
CHANGED
|
@@ -11,6 +11,34 @@
|
|
|
11
11
|
.remote-monitor-bar
|
|
12
12
|
position fixed
|
|
13
13
|
|
|
14
|
+
// Side panels are full-screen drawers on mobile: they start at the viewport
|
|
15
|
+
// edge (the left one covers the icon bar too, so no strip of terminal is left
|
|
16
|
+
// showing) and run to the bottom edge — footer included. On desktop the
|
|
17
|
+
// footer is reserved *beside* the panel because it holds the panel's own
|
|
18
|
+
// trigger; on mobile the drawer is dismissed from its header (✕ / the
|
|
19
|
+
// bookmark/history tabs) instead, so there is nothing to keep clear. The
|
|
20
|
+
// inline width already comes from store.leftSidePanelWidth / rightPanelWidth,
|
|
21
|
+
// which are the viewport width here.
|
|
22
|
+
// Both are on the fixed frame (the right panel explicitly: its bottom edge
|
|
23
|
+
// must not drift from the fixed footer when the visual viewport changes), and
|
|
24
|
+
// both have to outrank the footer's 200 — it comes later in the DOM, so at an
|
|
25
|
+
// equal rank it would paint over the drawer. The left drawer lives inside
|
|
26
|
+
// .sidebar, which is its own stacking context (z-index 100), so lifting the
|
|
27
|
+
// drawer alone is not enough: the whole sidebar column has to clear the
|
|
28
|
+
// footer. The right panel is a root-level child, so its own z-index suffices.
|
|
29
|
+
// The icon bar takes no extra room from this: it is 43px wide and the footer
|
|
30
|
+
// starts at 44px.
|
|
31
|
+
.sidebar
|
|
32
|
+
z-index 250
|
|
33
|
+
.sidebar-list
|
|
34
|
+
left 0
|
|
35
|
+
bottom 0
|
|
36
|
+
z-index 250
|
|
37
|
+
.right-side-panel
|
|
38
|
+
position fixed
|
|
39
|
+
bottom 0
|
|
40
|
+
z-index 250
|
|
41
|
+
|
|
14
42
|
// ---- 1. Tabs: show only current tab + dropdown, close icon always visible
|
|
15
43
|
.tabs.mobile-tabs
|
|
16
44
|
display flex
|
package/client/store/common.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
leftSidePanelWidthKey,
|
|
11
11
|
leftSideBarOpenKey,
|
|
12
12
|
rightSidebarWidthKey,
|
|
13
|
+
rightPanelPinnedKey,
|
|
13
14
|
addPanelWidthLsKey,
|
|
14
15
|
connectionMap,
|
|
15
16
|
lastAiChatSessionIdKey,
|
|
@@ -42,6 +43,18 @@ export default Store => {
|
|
|
42
43
|
window.store.setConfig(ext)
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// The footer info icon is a toggle: the same click opens and closes the
|
|
47
|
+
// panel, so the trigger never has to be hunted down in the panel header.
|
|
48
|
+
Store.prototype.toggleInfoPanel = action(function () {
|
|
49
|
+
const { store } = window
|
|
50
|
+
const isOpen = store.rightPanelVisible && store.rightPanelTab === 'info'
|
|
51
|
+
store.rightPanelVisible = !isOpen
|
|
52
|
+
store.rightPanelTab = 'info'
|
|
53
|
+
if (!isOpen) {
|
|
54
|
+
store.openInfoPanelAction()
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
45
58
|
Store.prototype.openInfoPanel = action(function () {
|
|
46
59
|
const { store } = window
|
|
47
60
|
store.rightPanelVisible = true
|
|
@@ -122,7 +135,13 @@ export default Store => {
|
|
|
122
135
|
Object.assign(window.store._termSearchOptions, update)
|
|
123
136
|
}
|
|
124
137
|
|
|
138
|
+
// Both panel widths are desktop-only preferences: on mobile the panel is a
|
|
139
|
+
// full-width drawer whose width is fixed, so a drag (or any other caller)
|
|
140
|
+
// must not overwrite the desktop value that is still in localStorage.
|
|
125
141
|
Store.prototype.setLeftSidePanelWidth = function (v) {
|
|
142
|
+
if (window.store.isMobile) {
|
|
143
|
+
return
|
|
144
|
+
}
|
|
126
145
|
ls.setItem(leftSidePanelWidthKey, v)
|
|
127
146
|
window.store._leftSidePanelWidth = v
|
|
128
147
|
}
|
|
@@ -144,9 +163,22 @@ export default Store => {
|
|
|
144
163
|
}
|
|
145
164
|
|
|
146
165
|
Store.prototype.setRightSidePanelWidth = function (v) {
|
|
166
|
+
if (window.store.isMobile) {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
147
169
|
ls.setItem(rightSidebarWidthKey, v)
|
|
148
170
|
window.store._rightPanelWidth = v
|
|
149
171
|
}
|
|
172
|
+
|
|
173
|
+
// Persist the pin the same way the left sidebar does (sidebarPinnedKey):
|
|
174
|
+
// pinned is a durable layout preference, not a per-session toggle. It matters
|
|
175
|
+
// more here because pinned and unpinned are visibly different modes — the
|
|
176
|
+
// unpinned panel is an overlay that clears the footer, the pinned one is a
|
|
177
|
+
// full-height dock.
|
|
178
|
+
Store.prototype.setRightPanelPinned = function (v) {
|
|
179
|
+
ls.setItem(rightPanelPinnedKey, v + '')
|
|
180
|
+
window.store.rightPanelPinned = v
|
|
181
|
+
}
|
|
150
182
|
Store.prototype.beforeExit = function (evt) {
|
|
151
183
|
const { confirmBeforeExit } = window.store.config
|
|
152
184
|
if (
|
|
@@ -286,6 +318,18 @@ export default Store => {
|
|
|
286
318
|
}
|
|
287
319
|
}
|
|
288
320
|
|
|
321
|
+
// Same toggle contract as toggleInfoPanel: the footer AI button opens and
|
|
322
|
+
// closes its own panel. The open path must go through handleOpenAIPanel so
|
|
323
|
+
// the missing-config prompt still fires; closing must not touch config.
|
|
324
|
+
Store.prototype.toggleAIPanel = function () {
|
|
325
|
+
const { store } = window
|
|
326
|
+
if (store.rightPanelVisible && store.rightPanelTab === 'ai') {
|
|
327
|
+
store.rightPanelVisible = false
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
store.handleOpenAIPanel()
|
|
331
|
+
}
|
|
332
|
+
|
|
289
333
|
Store.prototype.explainWithAi = function (txt) {
|
|
290
334
|
const { store } = window
|
|
291
335
|
store.handleOpenAIPanel()
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
localAddrBookmarkLsKey,
|
|
19
19
|
leftSidePanelWidthKey,
|
|
20
20
|
rightSidebarWidthKey,
|
|
21
|
+
rightPanelPinnedKey,
|
|
21
22
|
addPanelWidthLsKey,
|
|
22
23
|
qmSortByFrequencyKey,
|
|
23
24
|
resolutionsLsKey,
|
|
@@ -125,7 +126,7 @@ export default () => {
|
|
|
125
126
|
terminalInfoProps: {},
|
|
126
127
|
rightPanelVisible: false,
|
|
127
128
|
rightPanelTab: 'info',
|
|
128
|
-
rightPanelPinned:
|
|
129
|
+
rightPanelPinned: ls.getItem(rightPanelPinnedKey) === 'true',
|
|
129
130
|
_rightPanelWidth: parseInt(ls.getItem(rightSidebarWidthKey), 10) || 500,
|
|
130
131
|
showAIConfigModal: false,
|
|
131
132
|
|
package/client/store/sidebar.js
CHANGED
|
@@ -25,7 +25,11 @@ export default Store => {
|
|
|
25
25
|
// through this one method.
|
|
26
26
|
Store.prototype.openLeftSidePanel = function (field = 'bookmarks') {
|
|
27
27
|
const { store } = window
|
|
28
|
-
|
|
28
|
+
// A pinned panel is already open, so there is nothing to toggle — except
|
|
29
|
+
// on mobile, where pinning is not a reachable mode (no pin control) and
|
|
30
|
+
// the panel is a full-width overlay. Honouring a stale desktop pin here
|
|
31
|
+
// would leave the bookmark button dead on arrival.
|
|
32
|
+
if (store.pinned && !store.isMobile) {
|
|
29
33
|
return
|
|
30
34
|
}
|
|
31
35
|
store.setOpenedSideBar(store.openedSideBar ? '' : field)
|
package/client/store/store.js
CHANGED
|
@@ -61,10 +61,15 @@ class Store {
|
|
|
61
61
|
return window.store.innerWidth
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// Mobile turns a side panel into a full-screen drawer: it spans the whole
|
|
65
|
+
// viewport and its width is not user-adjustable. So the stored desktop width
|
|
66
|
+
// is ignored here, and so is the icon bar (the drawer covers it) — and the
|
|
67
|
+
// old 500px cap, which left a strip of terminal showing on windows between
|
|
68
|
+
// 500 and 600px, i.e. the top of the mobile range.
|
|
64
69
|
get leftSidePanelWidth () {
|
|
65
70
|
const { store } = window
|
|
66
71
|
if (store.isMobile) {
|
|
67
|
-
return
|
|
72
|
+
return store.innerWidth
|
|
68
73
|
}
|
|
69
74
|
return store._leftSidePanelWidth
|
|
70
75
|
}
|
|
@@ -78,10 +83,12 @@ class Store {
|
|
|
78
83
|
return store._leftSideBarOpen ? sidebarWidth : 0
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
// Same as leftSidePanelWidth: full-screen drawer on mobile, width not
|
|
87
|
+
// user-adjustable.
|
|
81
88
|
get rightPanelWidth () {
|
|
82
89
|
const { store } = window
|
|
83
90
|
if (store.isMobile) {
|
|
84
|
-
return
|
|
91
|
+
return store.innerWidth
|
|
85
92
|
}
|
|
86
93
|
return store._rightPanelWidth
|
|
87
94
|
}
|