@brimveyn/aimux 1.7.1 → 1.7.2
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/package.json +3 -4
- package/src/app-runtime/pty-write.ts +8 -3
- package/src/app-runtime/side-effects.ts +10 -2
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app.tsx +5 -8
- package/src/config.ts +2 -10
- package/src/input/modes/bridge.ts +1 -1
- package/src/input/modes/types.ts +6 -1
- package/src/input/raw-input-handler.ts +12 -3
- package/src/pty/terminal-snapshot.ts +4 -4
- package/src/state/reducers/modal-state.ts +3 -2
- package/src/state/reducers/session-state.ts +0 -7
- package/src/state/types.ts +7 -1
- package/src/ui/components/bare-input.tsx +5 -5
- package/src/ui/components/context-menu-box.tsx +21 -0
- package/src/ui/components/context-menu-overlay.tsx +114 -0
- package/src/ui/components/create-session-modal.tsx +12 -41
- package/src/ui/components/diff-renderer/fold-strip.tsx +7 -7
- package/src/ui/components/diff-renderer/highlight.ts +6 -10
- package/src/ui/components/diff-renderer/pierre-diff.tsx +3 -3
- package/src/ui/components/diff-renderer/prepare-diff.ts +2 -3
- package/src/ui/components/diff-renderer/split-view.tsx +22 -29
- package/src/ui/components/diff-renderer/stacked-view.tsx +18 -31
- package/src/ui/components/diff-renderer/use-diff-prefetch.ts +0 -1
- package/src/ui/components/diff-renderer/use-diff-preparation.ts +1 -1
- package/src/ui/components/git-commit-modal.tsx +4 -16
- package/src/ui/components/git-panel.tsx +37 -73
- package/src/ui/components/git-view.tsx +17 -19
- package/src/ui/components/help-modal.tsx +5 -13
- package/src/ui/components/input-field.tsx +5 -7
- package/src/ui/components/list-item.tsx +3 -11
- package/src/ui/components/modal-keybinds-overlay.tsx +4 -4
- package/src/ui/components/modal-shell.tsx +6 -11
- package/src/ui/components/new-tab-modal.tsx +7 -15
- package/src/ui/components/pending-chord-overlay.tsx +5 -5
- package/src/ui/components/picker.tsx +6 -6
- package/src/ui/components/session-bar.tsx +34 -20
- package/src/ui/components/session-picker-modal.tsx +7 -20
- package/src/ui/components/sidebar.tsx +36 -35
- package/src/ui/components/snippet-editor-modal.tsx +4 -18
- package/src/ui/components/snippet-picker-modal.tsx +5 -9
- package/src/ui/components/split-layout.tsx +3 -3
- package/src/ui/components/status-bar.tsx +12 -13
- package/src/ui/components/surface.tsx +8 -11
- package/src/ui/components/tab-item.tsx +54 -29
- package/src/ui/components/terminal-pane.tsx +59 -25
- package/src/ui/components/theme-picker-modal.tsx +7 -21
- package/src/ui/components/update-available-modal.tsx +3 -13
- package/src/ui/context-menu/controller.ts +34 -0
- package/src/ui/root.tsx +5 -2
- package/src/ui/shiki.ts +29 -21
- package/src/ui/theme-store.ts +110 -21
- package/src/ui/theme.ts +6 -3
- package/src/ui/themes.ts +18 -13
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import type { Theme } from '@brimveyn/aimux-config'
|
|
2
|
-
|
|
3
1
|
import { type ScrollBoxRenderable } from '@opentui/core'
|
|
4
2
|
import { memo, useMemo, useRef } from 'react'
|
|
5
3
|
|
|
6
4
|
import { useAppStore } from '../../state/app-store'
|
|
7
5
|
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
8
|
-
import {
|
|
6
|
+
import { getCurrentTokens, type ThemeTokens, useBg, useTokens } from '../theme'
|
|
7
|
+
import { ContextMenuBox } from './context-menu-box'
|
|
9
8
|
import { GitPaneWidget } from './git-pane-widget'
|
|
10
9
|
import { buildTabGroupInfo } from './sidebar-group-metadata'
|
|
11
10
|
import { TabItem } from './tab-item'
|
|
@@ -24,19 +23,19 @@ const GUTTER_PAD = '│'
|
|
|
24
23
|
function getRowBackground({
|
|
25
24
|
alternate,
|
|
26
25
|
isActive,
|
|
27
|
-
|
|
26
|
+
tokens,
|
|
28
27
|
}: {
|
|
29
28
|
isActive: boolean
|
|
30
29
|
alternate: boolean
|
|
31
|
-
|
|
30
|
+
tokens: ThemeTokens
|
|
32
31
|
}): string | undefined {
|
|
33
|
-
if (isActive) return
|
|
34
|
-
if (alternate) return
|
|
32
|
+
if (isActive) return tokens.selected
|
|
33
|
+
if (alternate) return tokens.hover
|
|
35
34
|
return undefined
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
const SidebarTop = memo(function SidebarTop() {
|
|
39
|
-
const
|
|
38
|
+
const tokens = useTokens()
|
|
40
39
|
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
41
40
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
42
41
|
const sessions = useAppStore((s) => s.sessions)
|
|
@@ -48,22 +47,20 @@ const SidebarTop = memo(function SidebarTop() {
|
|
|
48
47
|
|
|
49
48
|
return (
|
|
50
49
|
<box flexDirection="column" flexShrink={0} gap={0}>
|
|
51
|
-
<text fg={
|
|
50
|
+
<text fg={tokens.palette.primary}>
|
|
52
51
|
<strong>aimux</strong>
|
|
53
52
|
</text>
|
|
54
|
-
<text fg={
|
|
55
|
-
{currentSession ? currentSession.name : 'No session selected'}
|
|
56
|
-
</text>
|
|
53
|
+
<text fg={tokens.accent}>{currentSession ? currentSession.name : 'No session selected'}</text>
|
|
57
54
|
{branch ? (
|
|
58
55
|
<box flexDirection="row">
|
|
59
|
-
<text fg={
|
|
60
|
-
<text fg={
|
|
56
|
+
<text fg={tokens.palette.primary}>{'\u{e702}'} </text>
|
|
57
|
+
<text fg={tokens.muted}>{branch}</text>
|
|
61
58
|
</box>
|
|
62
59
|
) : null}
|
|
63
60
|
<box
|
|
64
61
|
flexDirection="row"
|
|
65
62
|
paddingY={1}
|
|
66
|
-
backgroundColor={
|
|
63
|
+
backgroundColor={tokens.selected}
|
|
67
64
|
justifyContent="center"
|
|
68
65
|
marginTop={1}
|
|
69
66
|
onMouseDown={(e) => {
|
|
@@ -71,25 +68,22 @@ const SidebarTop = memo(function SidebarTop() {
|
|
|
71
68
|
dispatchGlobal({ type: 'open-new-tab-modal' })
|
|
72
69
|
}}
|
|
73
70
|
>
|
|
74
|
-
<text fg={
|
|
71
|
+
<text fg={tokens.palette.ink}>+ New assistant</text>
|
|
75
72
|
</box>
|
|
76
|
-
<text fg={
|
|
77
|
-
{'·'.repeat(Math.max(0, sidebarWidth - 2))}
|
|
78
|
-
</text>
|
|
73
|
+
<text fg={tokens.hover}>{'·'.repeat(Math.max(0, sidebarWidth - 2))}</text>
|
|
79
74
|
</box>
|
|
80
75
|
)
|
|
81
76
|
})
|
|
82
77
|
|
|
83
78
|
function renderGroupGutter(isGroupStart: boolean, isGroupMiddle: boolean, isGroupEnd: boolean) {
|
|
79
|
+
const t = getCurrentTokens()
|
|
84
80
|
return (
|
|
85
81
|
<box flexDirection="column" width={1} overflow="hidden">
|
|
86
|
-
<text fg={
|
|
82
|
+
<text fg={t.accent}>
|
|
87
83
|
{/* oxlint-disable-next-line no-nested-ternary */}
|
|
88
84
|
{isGroupStart ? GUTTER_START : isGroupMiddle ? GUTTER_MIDDLE : GUTTER_PAD}
|
|
89
85
|
</text>
|
|
90
|
-
<text fg={
|
|
91
|
-
{isGroupEnd ? GUTTER_END : GUTTER_PAD}
|
|
92
|
-
</text>
|
|
86
|
+
<text fg={t.accent}>{isGroupEnd ? GUTTER_END : GUTTER_PAD}</text>
|
|
93
87
|
</box>
|
|
94
88
|
)
|
|
95
89
|
}
|
|
@@ -99,7 +93,7 @@ interface TabsBodyProps {
|
|
|
99
93
|
}
|
|
100
94
|
|
|
101
95
|
const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
102
|
-
const
|
|
96
|
+
const tokens = useTokens()
|
|
103
97
|
const tabs = useAppStore((s) => s.tabs)
|
|
104
98
|
const activeTabId = useAppStore((s) => s.activeTabId)
|
|
105
99
|
const focusMode = useAppStore((s) => s.focusMode)
|
|
@@ -129,7 +123,7 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
129
123
|
>
|
|
130
124
|
{tabs.length === 0 ? (
|
|
131
125
|
<box paddingTop={1}>
|
|
132
|
-
<text fg={
|
|
126
|
+
<text fg={tokens.muted}>No tabs yet. Press Ctrl+n.</text>
|
|
133
127
|
</box>
|
|
134
128
|
) : (
|
|
135
129
|
tabs.map((tab, index) => {
|
|
@@ -145,7 +139,7 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
145
139
|
return (
|
|
146
140
|
<box
|
|
147
141
|
key={tab.id}
|
|
148
|
-
backgroundColor={getRowBackground({ alternate, isActive,
|
|
142
|
+
backgroundColor={getRowBackground({ alternate, isActive, tokens })}
|
|
149
143
|
flexDirection="row"
|
|
150
144
|
onMouseDown={(event) => {
|
|
151
145
|
event.stopPropagation()
|
|
@@ -171,8 +165,8 @@ const TabsBody = memo(function TabsBody({ onTabActivate }: TabsBodyProps) {
|
|
|
171
165
|
})
|
|
172
166
|
|
|
173
167
|
export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
174
|
-
const
|
|
175
|
-
const sidebarBg = useBg('
|
|
168
|
+
const tokens = useTokens()
|
|
169
|
+
const sidebarBg = useBg('elevated')
|
|
176
170
|
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
177
171
|
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
178
172
|
const gitPane = useAppStore((s) => s.gitPane)
|
|
@@ -190,11 +184,7 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
190
184
|
const tabsGrow = gitEmbedded ? Math.max(1, Math.round((1 - gitPane.ratio) * 100)) : 1
|
|
191
185
|
const gitGrow = gitEmbedded ? Math.max(1, Math.round(gitPane.ratio * 100)) : 0
|
|
192
186
|
|
|
193
|
-
const separator = (
|
|
194
|
-
<text fg={theme.colors['editor.lineHighlightBackground']}>
|
|
195
|
-
{'·'.repeat(Math.max(0, sidebarWidth - 2))}
|
|
196
|
-
</text>
|
|
197
|
-
)
|
|
187
|
+
const separator = <text fg={tokens.hover}>{'·'.repeat(Math.max(0, sidebarWidth - 2))}</text>
|
|
198
188
|
const gitBody = gitEmbedded ? (
|
|
199
189
|
<box flexDirection="column" flexGrow={gitGrow} flexShrink={1} flexBasis={0} overflow="hidden">
|
|
200
190
|
<GitPaneWidget pollingEnabled={gitPane.visible} />
|
|
@@ -202,12 +192,23 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
202
192
|
) : null
|
|
203
193
|
|
|
204
194
|
return (
|
|
205
|
-
<
|
|
195
|
+
<ContextMenuBox
|
|
206
196
|
width={sidebarWidth}
|
|
207
197
|
padding={0}
|
|
208
198
|
flexDirection="column"
|
|
209
199
|
backgroundColor={sidebarBg}
|
|
210
200
|
gap={0}
|
|
201
|
+
rightClickMenu={[
|
|
202
|
+
['Hide sidebar', () => dispatchGlobal({ type: 'toggle-sidebar' })],
|
|
203
|
+
['Toggle git pane', () => dispatchGlobal({ type: 'toggle-git-pane' })],
|
|
204
|
+
[
|
|
205
|
+
'Toggle diff mode',
|
|
206
|
+
() => {
|
|
207
|
+
dispatchGlobal({ type: 'enter-git-mode' })
|
|
208
|
+
dispatchGlobal({ type: 'git-mode-toggle-diff-view' })
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
]}
|
|
211
212
|
onMouseDown={() => {
|
|
212
213
|
if (focusMode === 'terminal-input') {
|
|
213
214
|
dispatchGlobal({ focusMode: 'navigation', type: 'set-focus-mode' })
|
|
@@ -236,6 +237,6 @@ export function Sidebar({ onTabActivate }: SidebarProps) {
|
|
|
236
237
|
{gitBody}
|
|
237
238
|
</>
|
|
238
239
|
) : null}
|
|
239
|
-
</
|
|
240
|
+
</ContextMenuBox>
|
|
240
241
|
)
|
|
241
242
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTokens } from '../theme'
|
|
2
2
|
import { uiTokens } from '../ui-tokens'
|
|
3
3
|
import { InputField } from './input-field'
|
|
4
4
|
import { ModalShell } from './modal-shell'
|
|
@@ -16,7 +16,7 @@ export function SnippetEditorModal({
|
|
|
16
16
|
snippetContent,
|
|
17
17
|
snippetName,
|
|
18
18
|
}: SnippetEditorModalProps) {
|
|
19
|
-
const
|
|
19
|
+
const t = useTokens()
|
|
20
20
|
const nameActive = activeField === 'name'
|
|
21
21
|
const contentActive = activeField === 'content'
|
|
22
22
|
|
|
@@ -27,26 +27,12 @@ export function SnippetEditorModal({
|
|
|
27
27
|
width={uiTokens.modalWidth.xl}
|
|
28
28
|
>
|
|
29
29
|
<box flexDirection="column">
|
|
30
|
-
<text
|
|
31
|
-
fg={
|
|
32
|
-
nameActive ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']
|
|
33
|
-
}
|
|
34
|
-
>
|
|
35
|
-
Name
|
|
36
|
-
</text>
|
|
30
|
+
<text fg={nameActive ? t.palette.ink : t.muted}>Name</text>
|
|
37
31
|
<InputField active={nameActive} value={snippetName} />
|
|
38
32
|
</box>
|
|
39
33
|
|
|
40
34
|
<box flexDirection="column">
|
|
41
|
-
<text
|
|
42
|
-
fg={
|
|
43
|
-
contentActive
|
|
44
|
-
? theme.colors['editor.foreground']
|
|
45
|
-
: theme.colors['descriptionForeground']
|
|
46
|
-
}
|
|
47
|
-
>
|
|
48
|
-
Content
|
|
49
|
-
</text>
|
|
35
|
+
<text fg={contentActive ? t.palette.ink : t.muted}>Content</text>
|
|
50
36
|
<InputField active={contentActive} value={snippetContent} />
|
|
51
37
|
</box>
|
|
52
38
|
</ModalShell>
|
|
@@ -2,7 +2,7 @@ import type { SnippetRecord } from '../../state/types'
|
|
|
2
2
|
|
|
3
3
|
import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
4
4
|
import { filterSnippets } from '../../state/selectors'
|
|
5
|
-
import {
|
|
5
|
+
import { useTokens } from '../theme'
|
|
6
6
|
import { uiTokens } from '../ui-tokens'
|
|
7
7
|
import { Picker, type PickerItem } from './picker'
|
|
8
8
|
|
|
@@ -27,7 +27,7 @@ export function SnippetPickerModal({
|
|
|
27
27
|
selectedIndex,
|
|
28
28
|
snippets,
|
|
29
29
|
}: SnippetPickerModalProps) {
|
|
30
|
-
const
|
|
30
|
+
const t = useTokens()
|
|
31
31
|
const filtered = filterSnippets(snippets, filter)
|
|
32
32
|
|
|
33
33
|
const items: PickerItem[] = filtered.map((snippet, index) => {
|
|
@@ -40,13 +40,9 @@ export function SnippetPickerModal({
|
|
|
40
40
|
},
|
|
41
41
|
onDelete: () => runSideEffectGlobal({ type: 'delete-selected-snippet' }),
|
|
42
42
|
onEdit: () => runSideEffectGlobal({ type: 'edit-selected-snippet' }),
|
|
43
|
-
subtitle: (
|
|
44
|
-
<text fg={theme.colors['descriptionForeground']}>{truncateContent(snippet.content)}</text>
|
|
45
|
-
),
|
|
43
|
+
subtitle: <text fg={t.muted}>{truncateContent(snippet.content)}</text>,
|
|
46
44
|
title: (
|
|
47
|
-
<text
|
|
48
|
-
fg={active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']}
|
|
49
|
-
>
|
|
45
|
+
<text fg={active ? t.palette.ink : t.muted}>
|
|
50
46
|
<strong>{snippet.name}</strong>
|
|
51
47
|
</text>
|
|
52
48
|
),
|
|
@@ -64,7 +60,7 @@ export function SnippetPickerModal({
|
|
|
64
60
|
items={items}
|
|
65
61
|
selectedIndex={selectedIndex}
|
|
66
62
|
emptyState={
|
|
67
|
-
<text fg={
|
|
63
|
+
<text fg={t.muted}>
|
|
68
64
|
{filter ? 'No matching snippets.' : 'No snippets yet. Press n to create one.'}
|
|
69
65
|
</text>
|
|
70
66
|
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
type PaneRect,
|
|
12
12
|
type SplitDirection,
|
|
13
13
|
} from '../../state/layout-tree'
|
|
14
|
-
import {
|
|
14
|
+
import { useTokens } from '../theme'
|
|
15
15
|
import { TerminalPane } from './terminal-pane'
|
|
16
16
|
|
|
17
17
|
const PANE_CHROME = PANE_BORDER
|
|
@@ -58,7 +58,7 @@ export function SplitLayout({
|
|
|
58
58
|
onTerminalScrollEvent,
|
|
59
59
|
tabs,
|
|
60
60
|
}: SplitLayoutProps) {
|
|
61
|
-
const
|
|
61
|
+
const t = useTokens()
|
|
62
62
|
if (node.type === 'leaf') {
|
|
63
63
|
const tab = tabs.find((t) => t.id === node.tabId)
|
|
64
64
|
const isActive = node.tabId === activeTabId
|
|
@@ -135,7 +135,7 @@ export function SplitLayout({
|
|
|
135
135
|
<box
|
|
136
136
|
minWidth={node.direction === 'vertical' ? 1 : undefined}
|
|
137
137
|
minHeight={node.direction === 'horizontal' ? 1 : undefined}
|
|
138
|
-
backgroundColor={
|
|
138
|
+
backgroundColor={t.border}
|
|
139
139
|
onMouseDown={(e: OtuiMouseEvent) => {
|
|
140
140
|
e.preventDefault()
|
|
141
141
|
if (!onSeparatorDragStart) return
|
|
@@ -4,22 +4,21 @@ import { version as APP_VERSION } from '../../../package.json'
|
|
|
4
4
|
import { useAppStore } from '../../state/app-store'
|
|
5
5
|
import { useKeymap } from '../keymap-context'
|
|
6
6
|
import { getStatusBarModel } from '../status-bar-model'
|
|
7
|
-
import {
|
|
7
|
+
import { getCurrentTokens, useBg, useTokens } from '../theme'
|
|
8
8
|
|
|
9
9
|
function getModeColor(focusMode: AppState['focusMode']): string {
|
|
10
|
-
const t =
|
|
10
|
+
const t = getCurrentTokens()
|
|
11
11
|
switch (focusMode) {
|
|
12
12
|
case 'terminal-input':
|
|
13
|
-
return t.
|
|
13
|
+
return t.palette.primary
|
|
14
14
|
case 'modal':
|
|
15
|
-
return t.colors['editorWarning.foreground']
|
|
16
15
|
case 'command-edit':
|
|
17
|
-
return t.
|
|
16
|
+
return t.palette.warning
|
|
18
17
|
case 'git':
|
|
19
|
-
return t.
|
|
18
|
+
return t.palette.success
|
|
20
19
|
case 'navigation':
|
|
21
20
|
default:
|
|
22
|
-
return t.
|
|
21
|
+
return t.accent
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
|
|
@@ -40,8 +39,8 @@ function getModeLabel(focusMode: AppState['focusMode']): string {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
export function StatusBar() {
|
|
43
|
-
const
|
|
44
|
-
const headerBg = useBg('
|
|
42
|
+
const t = useTokens()
|
|
43
|
+
const headerBg = useBg('elevated')
|
|
45
44
|
const state = useAppStore((s) => s)
|
|
46
45
|
const activeTab = state.tabs.find((tab) => tab.id === state.activeTabId)
|
|
47
46
|
const config = useKeymap()
|
|
@@ -60,13 +59,13 @@ export function StatusBar() {
|
|
|
60
59
|
<box width="100%" flexDirection="row">
|
|
61
60
|
<text fg={getModeColor(state.focusMode)}>[{getModeLabel(state.focusMode)}]</text>
|
|
62
61
|
<text> </text>
|
|
63
|
-
<text fg={
|
|
62
|
+
<text fg={t.palette.ink}>{model.left}</text>
|
|
64
63
|
</box>
|
|
65
64
|
<box width="100%" flexDirection="row" justifyContent="space-between">
|
|
66
|
-
<text fg={
|
|
65
|
+
<text fg={t.muted}>{model.right}</text>
|
|
67
66
|
<box flexDirection="row" gap={2}>
|
|
68
|
-
{model.help ? <text fg={
|
|
69
|
-
<text fg={
|
|
67
|
+
{model.help ? <text fg={t.muted}>{model.help}</text> : null}
|
|
68
|
+
<text fg={t.hover}>v{APP_VERSION}</text>
|
|
70
69
|
</box>
|
|
71
70
|
</box>
|
|
72
71
|
</box>
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
-
import type
|
|
4
|
-
|
|
5
|
-
import { useBg } from '../theme'
|
|
3
|
+
import { type SurfaceToken, useBg } from '../theme'
|
|
6
4
|
|
|
7
5
|
type SurfaceTone = 'muted' | 'elevated' | 'selected' | 'input' | 'inputActive'
|
|
8
6
|
|
|
9
|
-
function
|
|
7
|
+
function toneToToken(tone: SurfaceTone): SurfaceToken {
|
|
10
8
|
switch (tone) {
|
|
11
9
|
case 'elevated':
|
|
12
|
-
return '
|
|
10
|
+
return 'elevated'
|
|
13
11
|
case 'selected':
|
|
14
|
-
return 'list.activeSelectionBackground'
|
|
15
|
-
case 'input':
|
|
16
|
-
return 'editor.background'
|
|
17
12
|
case 'inputActive':
|
|
18
|
-
return '
|
|
13
|
+
return 'selected'
|
|
14
|
+
case 'input':
|
|
15
|
+
return 'base'
|
|
19
16
|
case 'muted':
|
|
20
17
|
default:
|
|
21
|
-
return '
|
|
18
|
+
return 'elevated'
|
|
22
19
|
}
|
|
23
20
|
}
|
|
24
21
|
|
|
@@ -47,7 +44,7 @@ export function Surface({
|
|
|
47
44
|
tone = 'muted',
|
|
48
45
|
width,
|
|
49
46
|
}: SurfaceProps) {
|
|
50
|
-
const bg = useBg(
|
|
47
|
+
const bg = useBg(toneToToken(tone))
|
|
51
48
|
return (
|
|
52
49
|
<box
|
|
53
50
|
backgroundColor={bg}
|
|
@@ -4,7 +4,8 @@ import type { TabSession } from '../../state/types'
|
|
|
4
4
|
|
|
5
5
|
import { dispatchGlobal, runSideEffectGlobal } from '../../state/dispatch-ref'
|
|
6
6
|
import { useBusySpinner } from '../hooks/use-busy-spinner'
|
|
7
|
-
import {
|
|
7
|
+
import { getCurrentTokens, useTokens } from '../theme'
|
|
8
|
+
import { ContextMenuBox } from './context-menu-box'
|
|
8
9
|
|
|
9
10
|
interface TabItemProps {
|
|
10
11
|
id?: string
|
|
@@ -15,15 +16,16 @@ interface TabItemProps {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
function getStatusColor(status: TabSession['status']): string {
|
|
19
|
+
const t = getCurrentTokens()
|
|
18
20
|
switch (status) {
|
|
19
21
|
case 'running':
|
|
20
|
-
return
|
|
22
|
+
return t.palette.success
|
|
21
23
|
case 'disconnected':
|
|
22
|
-
return
|
|
24
|
+
return t.palette.warning
|
|
23
25
|
case 'error':
|
|
24
|
-
return
|
|
26
|
+
return t.palette.error
|
|
25
27
|
default:
|
|
26
|
-
return
|
|
28
|
+
return t.muted
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -36,36 +38,33 @@ function getIndicator(active: boolean, focused: boolean, inLayout: boolean): str
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
function getIndicatorColor(active: boolean, focused: boolean, inLayout: boolean): string {
|
|
41
|
+
const t = getCurrentTokens()
|
|
39
42
|
if (active) {
|
|
40
|
-
return focused
|
|
41
|
-
? getCurrentTheme().colors['textLink.foreground']
|
|
42
|
-
: getCurrentTheme().colors['terminal.ansiMagenta']
|
|
43
|
+
return focused ? t.palette.primary : t.accent
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
return inLayout
|
|
46
|
-
? getCurrentTheme().colors['descriptionForeground']
|
|
47
|
-
: getCurrentTheme().colors['editor.lineHighlightBackground']
|
|
46
|
+
return inLayout ? t.muted : t.hover
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
function BusyIndicator() {
|
|
51
|
-
const
|
|
50
|
+
const t = useTokens()
|
|
52
51
|
const frame = useBusySpinner()
|
|
53
|
-
return <text fg={
|
|
52
|
+
return <text fg={t.palette.primary}>{frame} working</text>
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
function WaitingIndicator() {
|
|
57
|
-
const
|
|
58
|
-
return <text fg={
|
|
56
|
+
const t = useTokens()
|
|
57
|
+
return <text fg={t.palette.warning}>? waiting</text>
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
function ActivityIndicator({ tab }: { tab: TabSession }) {
|
|
62
|
-
const
|
|
61
|
+
const t = useTokens()
|
|
63
62
|
if (tab.status === 'error') {
|
|
64
|
-
return <text fg={
|
|
63
|
+
return <text fg={t.palette.error}>✗ error</text>
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
if (tab.status === 'disconnected') {
|
|
68
|
-
return <text fg={
|
|
67
|
+
return <text fg={t.palette.warning}>⏸ restore</text>
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
if (tab.activity === 'working') {
|
|
@@ -77,14 +76,14 @@ function ActivityIndicator({ tab }: { tab: TabSession }) {
|
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
if (tab.activity === 'idle') {
|
|
80
|
-
return <text fg={
|
|
79
|
+
return <text fg={t.palette.success}>● idle</text>
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
return <text fg={getStatusColor(tab.status)}>{tab.status}</text>
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
export function TabItem({ active, focused, id, inLayout, tab }: TabItemProps) {
|
|
87
|
-
const
|
|
86
|
+
const t = useTokens()
|
|
88
87
|
const label = tab.command.split(' ')[0]
|
|
89
88
|
const isInLayout = inLayout ?? false
|
|
90
89
|
const indicator = getIndicator(active, focused, isInLayout)
|
|
@@ -92,7 +91,7 @@ export function TabItem({ active, focused, id, inLayout, tab }: TabItemProps) {
|
|
|
92
91
|
const [hovered, setHovered] = useState(false)
|
|
93
92
|
|
|
94
93
|
return (
|
|
95
|
-
<
|
|
94
|
+
<ContextMenuBox
|
|
96
95
|
id={id}
|
|
97
96
|
paddingLeft={1}
|
|
98
97
|
paddingRight={1}
|
|
@@ -100,17 +99,43 @@ export function TabItem({ active, focused, id, inLayout, tab }: TabItemProps) {
|
|
|
100
99
|
paddingBottom={0}
|
|
101
100
|
flexDirection="column"
|
|
102
101
|
gap={0}
|
|
102
|
+
rightClickMenu={[
|
|
103
|
+
[
|
|
104
|
+
'Rename',
|
|
105
|
+
() => {
|
|
106
|
+
dispatchGlobal({ tabId: tab.id, type: 'set-active-tab' })
|
|
107
|
+
dispatchGlobal({ type: 'open-rename-tab-modal' })
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
'Close',
|
|
112
|
+
() => {
|
|
113
|
+
dispatchGlobal({ tabId: tab.id, type: 'close-tab' })
|
|
114
|
+
runSideEffectGlobal({ tabId: tab.id, type: 'close-tab' })
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
[
|
|
118
|
+
'Move up',
|
|
119
|
+
() => {
|
|
120
|
+
dispatchGlobal({ tabId: tab.id, type: 'set-active-tab' })
|
|
121
|
+
dispatchGlobal({ delta: -1, type: 'reorder-active-tab' })
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
[
|
|
125
|
+
'Move down',
|
|
126
|
+
() => {
|
|
127
|
+
dispatchGlobal({ tabId: tab.id, type: 'set-active-tab' })
|
|
128
|
+
dispatchGlobal({ delta: 1, type: 'reorder-active-tab' })
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
]}
|
|
103
132
|
onMouseOver={() => setHovered(true)}
|
|
104
133
|
onMouseOut={() => setHovered(false)}
|
|
105
134
|
>
|
|
106
135
|
<box flexDirection="row" alignItems="center">
|
|
107
136
|
<text fg={indicatorColor}>{indicator} </text>
|
|
108
137
|
<box flexGrow={1}>
|
|
109
|
-
<text
|
|
110
|
-
fg={active ? theme.colors['editor.foreground'] : theme.colors['descriptionForeground']}
|
|
111
|
-
>
|
|
112
|
-
{tab.title}
|
|
113
|
-
</text>
|
|
138
|
+
<text fg={active ? t.palette.ink : t.muted}>{tab.title}</text>
|
|
114
139
|
</box>
|
|
115
140
|
{hovered ? (
|
|
116
141
|
<box
|
|
@@ -120,14 +145,14 @@ export function TabItem({ active, focused, id, inLayout, tab }: TabItemProps) {
|
|
|
120
145
|
runSideEffectGlobal({ tabId: tab.id, type: 'close-tab' })
|
|
121
146
|
}}
|
|
122
147
|
>
|
|
123
|
-
<text fg={
|
|
148
|
+
<text fg={t.muted}>×</text>
|
|
124
149
|
</box>
|
|
125
150
|
) : null}
|
|
126
151
|
</box>
|
|
127
152
|
<box flexDirection="row">
|
|
128
|
-
<text fg={
|
|
153
|
+
<text fg={t.muted}> {label} </text>
|
|
129
154
|
<ActivityIndicator tab={tab} />
|
|
130
155
|
</box>
|
|
131
|
-
</
|
|
156
|
+
</ContextMenuBox>
|
|
132
157
|
)
|
|
133
158
|
}
|