@brimveyn/aimux 1.20.3 → 1.21.0
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 +2 -2
- package/src/app-runtime/side-effects.ts +3 -48
- package/src/app-runtime/split-drag-controller.ts +4 -8
- package/src/app-runtime/use-mouse-handlers.ts +27 -42
- package/src/app-runtime/use-terminal-resize.ts +11 -20
- package/src/app.tsx +9 -37
- package/src/config.ts +98 -8
- package/src/git/pr-merge.ts +64 -0
- package/src/git/pr-status-poller.ts +57 -0
- package/src/git/pr-status.ts +227 -0
- package/src/index.tsx +7 -2
- package/src/platform/clipboard.ts +140 -4
- package/src/platform/open-url.ts +39 -0
- package/src/services/ai-usage/spawn.ts +3 -1
- package/src/state/bars.ts +75 -0
- package/src/state/git-pane-sizing.ts +0 -9
- package/src/state/pr-status-store.ts +39 -0
- package/src/state/reducers/git-panel-state.ts +0 -47
- package/src/state/reducers/ui-state.ts +83 -13
- package/src/state/session-persistence.ts +5 -7
- package/src/state/store.ts +28 -35
- package/src/state/types.ts +24 -19
- package/src/state/workspace-save.ts +5 -7
- package/src/ui/components/git/diff-renderer/pierre-diff.tsx +2 -1
- package/src/ui/components/git/pane/git-pane-header.tsx +105 -39
- package/src/ui/components/git/pane/git-pane-widget.tsx +30 -16
- package/src/ui/components/git/pane/pr-checks-panel.tsx +197 -0
- package/src/ui/components/git/pane/pr-state-row.tsx +103 -0
- package/src/ui/components/layout/bar.tsx +191 -0
- package/src/ui/components/layout/top-tab-bar.tsx +3 -2
- package/src/ui/root.tsx +25 -109
- package/src/ui/widgets/registry.tsx +18 -0
- package/src/ui/widgets/widget-context-menu.ts +69 -0
- package/src/ui/components/git/pane/git-pane-context-menu.ts +0 -26
- package/src/ui/components/layout/sidebar/sidebar.tsx +0 -163
package/src/ui/root.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import type { MeasuredPaneRect } from '../app-runtime/use-pane-size-report'
|
|
|
6
6
|
import type { WorktreeTemplate } from '../config'
|
|
7
7
|
import type { TerminalContentOrigin } from '../input/raw-input-handler'
|
|
8
8
|
import type {
|
|
9
|
+
BarSide,
|
|
9
10
|
FocusMode,
|
|
10
11
|
ModalState,
|
|
11
12
|
SessionRecord,
|
|
@@ -16,13 +17,10 @@ import type { ThemeId } from './themes'
|
|
|
16
17
|
|
|
17
18
|
import { useWorktreeBranchPolling } from '../git/worktree-branch-poller'
|
|
18
19
|
import { useAppStore } from '../state/app-store'
|
|
19
|
-
import {
|
|
20
|
-
import { getGitPaneWidthFromRatio } from '../state/git-pane-sizing'
|
|
20
|
+
import { getBarWidth } from '../state/bars'
|
|
21
21
|
import { getTreeForTab, PANE_BORDER, type SplitDirection } from '../state/layout-tree'
|
|
22
22
|
import { GitView } from './components/git/git-view'
|
|
23
|
-
import {
|
|
24
|
-
import { GitPaneWidget } from './components/git/pane/git-pane-widget'
|
|
25
|
-
import { Sidebar } from './components/layout/sidebar/sidebar'
|
|
23
|
+
import { Bar, type BarBoundaryResizeInfo } from './components/layout/bar'
|
|
26
24
|
import { SplitLayout } from './components/layout/split-layout'
|
|
27
25
|
import { StatusBar } from './components/layout/status-bar'
|
|
28
26
|
import { TerminalPane } from './components/layout/terminal-pane'
|
|
@@ -41,7 +39,6 @@ import { NewTabModal } from './components/modals/tabs/new-tab-modal'
|
|
|
41
39
|
import { ThemePickerModal } from './components/modals/themes/theme-picker-modal'
|
|
42
40
|
import { WorktreeMoveConfirmModal } from './components/modals/worktree/worktree-move-confirm-modal'
|
|
43
41
|
import { WorktreeMoveModal } from './components/modals/worktree/worktree-move-modal'
|
|
44
|
-
import { ContextMenuBox } from './components/overlays/context-menu/context-menu-box'
|
|
45
42
|
import { ContextMenuOverlay } from './components/overlays/context-menu/context-menu-overlay'
|
|
46
43
|
import { PendingChordOverlay } from './components/overlays/pending-chord-overlay'
|
|
47
44
|
import { ToastViewport } from './components/overlays/toast/toast-viewport'
|
|
@@ -289,17 +286,8 @@ interface RootViewProps {
|
|
|
289
286
|
onTerminalMouseUp?: (event: MouseEvent) => boolean
|
|
290
287
|
onPaneActivate?: (tabId: string) => void
|
|
291
288
|
onSplitResize?: (tabId: string, ratio: number, axis: SplitDirection) => void
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
initialWidth: number
|
|
295
|
-
screenStart: number
|
|
296
|
-
side: 'left' | 'right'
|
|
297
|
-
}) => void
|
|
298
|
-
onEmbeddedGitResizeStart?: (info: {
|
|
299
|
-
containerStart: number
|
|
300
|
-
position: 'top' | 'bottom'
|
|
301
|
-
totalSize: number
|
|
302
|
-
}) => void
|
|
289
|
+
onBarResizeStart?: (info: { initialWidth: number; screenStart: number; side: BarSide }) => void
|
|
290
|
+
onBarBoundaryResizeStart?: (info: BarBoundaryResizeInfo) => void
|
|
303
291
|
onSeparatorDragStart?: (info: {
|
|
304
292
|
tabId: string
|
|
305
293
|
direction: SplitDirection
|
|
@@ -317,14 +305,13 @@ export function RootView({
|
|
|
317
305
|
contentOrigin,
|
|
318
306
|
localScrollbackEnabled,
|
|
319
307
|
mouseForwardingEnabled,
|
|
320
|
-
|
|
321
|
-
|
|
308
|
+
onBarBoundaryResizeStart,
|
|
309
|
+
onBarResizeStart,
|
|
322
310
|
onMeasure,
|
|
323
311
|
onPaneActivate,
|
|
324
312
|
onSeparatorDrag,
|
|
325
313
|
onSeparatorDragEnd,
|
|
326
314
|
onSeparatorDragStart,
|
|
327
|
-
onSidebarResizeStart,
|
|
328
315
|
onSplitResize,
|
|
329
316
|
onTerminalClick,
|
|
330
317
|
onTerminalDrag,
|
|
@@ -349,31 +336,25 @@ export function RootView({
|
|
|
349
336
|
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
350
337
|
const worktreeDivergence = useAppStore((s) => s.worktreeDivergence)
|
|
351
338
|
const worktreeTemplates = useAppStore((s) => s.worktreeTemplates)
|
|
352
|
-
const
|
|
353
|
-
const gitPaneVisible = useAppStore((s) => s.gitPane.visible)
|
|
354
|
-
const gitPanePosition = useAppStore((s) => s.gitPane.position)
|
|
355
|
-
const gitPaneRatio = useAppStore((s) => s.gitPane.paneRatio)
|
|
356
|
-
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
357
|
-
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
339
|
+
const leftBarWidth = useAppStore((s) => getBarWidth(s.bars.left))
|
|
358
340
|
|
|
359
|
-
const gitPaneInPaneOnLeft = gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left'
|
|
360
341
|
const splitChrome = PANE_BORDER * 2
|
|
361
342
|
|
|
362
343
|
// Keep every worktree's `branch` in state synchronized with the on-disk
|
|
363
344
|
// HEAD so the sidebar (and divergence calc) never reads a stale value.
|
|
364
345
|
useWorktreeBranchPolling(true)
|
|
365
346
|
|
|
366
|
-
|
|
347
|
+
// The terminal's own left edge stays a drag target for the left bar, so the
|
|
348
|
+
// resize gesture works from either side of the seam.
|
|
349
|
+
const handleLeftBarEdgeResize = useCallback(
|
|
367
350
|
(event: MouseEvent): boolean => {
|
|
368
|
-
|
|
351
|
+
onBarResizeStart?.({ initialWidth: leftBarWidth, screenStart: event.x, side: 'left' })
|
|
369
352
|
return true
|
|
370
353
|
},
|
|
371
|
-
[
|
|
354
|
+
[leftBarWidth, onBarResizeStart]
|
|
372
355
|
)
|
|
373
356
|
const handleTerminalLeftEdgeMouseDown =
|
|
374
|
-
|
|
375
|
-
? handleSidebarEdgeResize
|
|
376
|
-
: undefined
|
|
357
|
+
leftBarWidth > 0 && onBarResizeStart ? handleLeftBarEdgeResize : undefined
|
|
377
358
|
|
|
378
359
|
const handleRootMouseDrag = useCallback(
|
|
379
360
|
(event: MouseEvent) => {
|
|
@@ -460,21 +441,16 @@ export function RootView({
|
|
|
460
441
|
onMouseUp={handleRootMouseUp}
|
|
461
442
|
>
|
|
462
443
|
<box flexDirection="row" gap={0} padding={0} flexGrow={1}>
|
|
463
|
-
<
|
|
464
|
-
|
|
444
|
+
<Bar
|
|
445
|
+
side="left"
|
|
446
|
+
onBoundaryResizeStart={onBarBoundaryResizeStart}
|
|
447
|
+
onEdgeResizeStart={onBarResizeStart}
|
|
465
448
|
onResizeDrag={onSeparatorDrag}
|
|
466
449
|
onResizeDragEnd={onSeparatorDragEnd}
|
|
467
450
|
/>
|
|
468
451
|
<box flexDirection="column" flexGrow={1}>
|
|
469
452
|
<TopTabBar />
|
|
470
453
|
<box flexDirection="row" gap={0} padding={0} flexGrow={1}>
|
|
471
|
-
{gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'left' ? (
|
|
472
|
-
<GitPaneInPaneMode
|
|
473
|
-
position="left"
|
|
474
|
-
ratio={gitPaneRatio}
|
|
475
|
-
onGitPaneResizeStart={onGitPaneResizeStart}
|
|
476
|
-
/>
|
|
477
|
-
) : null}
|
|
478
454
|
{activeTree && activeTree.type === 'split' ? (
|
|
479
455
|
<SplitLayout
|
|
480
456
|
node={activeTree}
|
|
@@ -517,15 +493,15 @@ export function RootView({
|
|
|
517
493
|
onMeasure={onMeasure}
|
|
518
494
|
/>
|
|
519
495
|
)}
|
|
520
|
-
{gitPaneMode === 'pane' && gitPaneVisible && gitPanePosition === 'right' ? (
|
|
521
|
-
<GitPaneInPaneMode
|
|
522
|
-
position="right"
|
|
523
|
-
ratio={gitPaneRatio}
|
|
524
|
-
onGitPaneResizeStart={onGitPaneResizeStart}
|
|
525
|
-
/>
|
|
526
|
-
) : null}
|
|
527
496
|
</box>
|
|
528
497
|
</box>
|
|
498
|
+
<Bar
|
|
499
|
+
side="right"
|
|
500
|
+
onBoundaryResizeStart={onBarBoundaryResizeStart}
|
|
501
|
+
onEdgeResizeStart={onBarResizeStart}
|
|
502
|
+
onResizeDrag={onSeparatorDrag}
|
|
503
|
+
onResizeDragEnd={onSeparatorDragEnd}
|
|
504
|
+
/>
|
|
529
505
|
</box>
|
|
530
506
|
<StatusBar />
|
|
531
507
|
<PendingChordOverlay />
|
|
@@ -547,63 +523,3 @@ export function RootView({
|
|
|
547
523
|
</box>
|
|
548
524
|
)
|
|
549
525
|
}
|
|
550
|
-
|
|
551
|
-
function GitPaneInPaneMode({
|
|
552
|
-
onGitPaneResizeStart,
|
|
553
|
-
position,
|
|
554
|
-
ratio,
|
|
555
|
-
}: {
|
|
556
|
-
ratio: number
|
|
557
|
-
position: 'left' | 'right'
|
|
558
|
-
onGitPaneResizeStart?: (info: {
|
|
559
|
-
initialWidth: number
|
|
560
|
-
screenStart: number
|
|
561
|
-
side: 'left' | 'right'
|
|
562
|
-
}) => void
|
|
563
|
-
}) {
|
|
564
|
-
const tokens = useTheme()
|
|
565
|
-
const bg = tokens.backgroundPanel
|
|
566
|
-
const gitPane = useAppStore((s) => s.gitPane)
|
|
567
|
-
const width = getGitPaneWidthFromRatio(ratio)
|
|
568
|
-
const contentWidth = Math.max(1, width - 1)
|
|
569
|
-
const gitPaneMenu = buildGitPaneContextMenu(
|
|
570
|
-
gitPane,
|
|
571
|
-
() => dispatchGlobal({ type: 'toggle-git-pane' }),
|
|
572
|
-
(mode, nextPosition) => {
|
|
573
|
-
dispatchGlobal({ mode, type: 'set-git-pane-mode' })
|
|
574
|
-
dispatchGlobal({ position: nextPosition, type: 'set-git-pane-position' })
|
|
575
|
-
}
|
|
576
|
-
)
|
|
577
|
-
const handleResizeMouseDown = useCallback(
|
|
578
|
-
(event: MouseEvent) => {
|
|
579
|
-
event.preventDefault()
|
|
580
|
-
event.stopPropagation()
|
|
581
|
-
onGitPaneResizeStart?.({ initialWidth: width, screenStart: event.x, side: position })
|
|
582
|
-
},
|
|
583
|
-
[onGitPaneResizeStart, position, width]
|
|
584
|
-
)
|
|
585
|
-
const handle = (
|
|
586
|
-
<box
|
|
587
|
-
width={1}
|
|
588
|
-
flexShrink={0}
|
|
589
|
-
backgroundColor={tokens.border}
|
|
590
|
-
onMouseDown={handleResizeMouseDown}
|
|
591
|
-
/>
|
|
592
|
-
)
|
|
593
|
-
return (
|
|
594
|
-
<box flexDirection="column" width={width} flexShrink={0} backgroundColor={bg} overflow="hidden">
|
|
595
|
-
<box flexDirection="row" flexGrow={1} overflow="hidden">
|
|
596
|
-
{position === 'right' ? handle : null}
|
|
597
|
-
<ContextMenuBox
|
|
598
|
-
width={contentWidth}
|
|
599
|
-
flexGrow={1}
|
|
600
|
-
overflow="hidden"
|
|
601
|
-
rightClickMenu={gitPaneMenu}
|
|
602
|
-
>
|
|
603
|
-
<GitPaneWidget pollingEnabled />
|
|
604
|
-
</ContextMenuBox>
|
|
605
|
-
{position === 'left' ? handle : null}
|
|
606
|
-
</box>
|
|
607
|
-
</box>
|
|
608
|
-
)
|
|
609
|
-
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import { GitPaneWidget } from '../components/git/pane/git-pane-widget'
|
|
4
|
+
import { WorkspaceList } from '../components/layout/sidebar/workspace-list'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Everything a bar can host. Adding a widget = one entry here plus its id in
|
|
8
|
+
* `KNOWN_WIDGET_IDS` (src/state/bars.ts) and in the default layout.
|
|
9
|
+
*/
|
|
10
|
+
export const WIDGET_RENDERERS: Record<string, (contentWidth: number) => ReactNode> = {
|
|
11
|
+
git: (contentWidth) => <GitPaneWidget contentWidth={contentWidth} pollingEnabled />,
|
|
12
|
+
workspaces: (contentWidth) => <WorkspaceList contentWidth={contentWidth} />,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const WIDGET_LABELS: Record<string, string> = {
|
|
16
|
+
git: 'Git',
|
|
17
|
+
workspaces: 'Workspaces',
|
|
18
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { BarSide, BarsState } from '../../state/types'
|
|
2
|
+
import type { ContextMenuItem } from '../context-menu/controller'
|
|
3
|
+
|
|
4
|
+
import { visibleWidgets } from '../../state/bars'
|
|
5
|
+
import { dispatchGlobal } from '../../state/dispatch-ref'
|
|
6
|
+
import { WIDGET_LABELS } from './registry'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Right-click menu for a widget hosted in a bar: move it across, reorder it
|
|
10
|
+
* within its bar, or hide it. Generic — a new widget gets this for free.
|
|
11
|
+
*/
|
|
12
|
+
export function buildWidgetContextMenu(
|
|
13
|
+
bars: BarsState,
|
|
14
|
+
side: BarSide,
|
|
15
|
+
widgetId: string
|
|
16
|
+
): ContextMenuItem[] {
|
|
17
|
+
const widgets = bars[side].widgets
|
|
18
|
+
const index = widgets.findIndex((widget) => widget.id === widgetId)
|
|
19
|
+
if (index === -1) return []
|
|
20
|
+
|
|
21
|
+
const other: BarSide = side === 'left' ? 'right' : 'left'
|
|
22
|
+
const items: ContextMenuItem[] = [
|
|
23
|
+
[
|
|
24
|
+
`Move to ${other} bar`,
|
|
25
|
+
() =>
|
|
26
|
+
dispatchGlobal({
|
|
27
|
+
index: bars[other].widgets.length,
|
|
28
|
+
side: other,
|
|
29
|
+
type: 'move-widget',
|
|
30
|
+
widgetId,
|
|
31
|
+
}),
|
|
32
|
+
],
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
if (index > 0) {
|
|
36
|
+
items.push([
|
|
37
|
+
'Move up',
|
|
38
|
+
() => dispatchGlobal({ index: index - 1, side, type: 'move-widget', widgetId }),
|
|
39
|
+
])
|
|
40
|
+
}
|
|
41
|
+
if (index < widgets.length - 1) {
|
|
42
|
+
items.push([
|
|
43
|
+
'Move down',
|
|
44
|
+
() => dispatchGlobal({ index: index + 1, side, type: 'move-widget', widgetId }),
|
|
45
|
+
])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Hiding the last visible widget would collapse the bar to zero width and
|
|
49
|
+
// take its own context menu with it — leaving no way back.
|
|
50
|
+
if (visibleWidgets(bars[side]).length > 1) {
|
|
51
|
+
items.push(['Hide', () => dispatchGlobal({ type: 'toggle-widget', widgetId })])
|
|
52
|
+
}
|
|
53
|
+
return items
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Menu for the bar itself. Offers a way back for every hidden widget. */
|
|
57
|
+
export function buildBarContextMenu(bars: BarsState, side: BarSide): ContextMenuItem[] {
|
|
58
|
+
const items: ContextMenuItem[] = [
|
|
59
|
+
[`Hide ${side} bar`, () => dispatchGlobal({ side, type: 'toggle-bar' })],
|
|
60
|
+
]
|
|
61
|
+
for (const widget of bars[side].widgets) {
|
|
62
|
+
if (widget.visible) continue
|
|
63
|
+
items.push([
|
|
64
|
+
`Show ${WIDGET_LABELS[widget.id] ?? widget.id}`,
|
|
65
|
+
() => dispatchGlobal({ type: 'toggle-widget', widgetId: widget.id }),
|
|
66
|
+
])
|
|
67
|
+
}
|
|
68
|
+
return items
|
|
69
|
+
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { GitPaneMode, GitPanePosition, GitPaneState } from '../../../../state/types'
|
|
2
|
-
import type { ContextMenuItem } from '../../../context-menu/controller'
|
|
3
|
-
|
|
4
|
-
const GIT_PANE_MENU_OPTIONS: {
|
|
5
|
-
label: string
|
|
6
|
-
mode: GitPaneMode
|
|
7
|
-
position: GitPanePosition
|
|
8
|
-
}[] = [
|
|
9
|
-
{ label: 'Move to top', mode: 'embedded', position: 'top' },
|
|
10
|
-
{ label: 'Move to bottom', mode: 'embedded', position: 'bottom' },
|
|
11
|
-
{ label: 'Move to left', mode: 'pane', position: 'left' },
|
|
12
|
-
{ label: 'Move to right', mode: 'pane', position: 'right' },
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
export function buildGitPaneContextMenu(
|
|
16
|
-
gitPane: Pick<GitPaneState, 'mode' | 'position'>,
|
|
17
|
-
onToggle: () => void,
|
|
18
|
-
onMove: (mode: GitPaneMode, position: GitPanePosition) => void
|
|
19
|
-
): ContextMenuItem[] {
|
|
20
|
-
return [
|
|
21
|
-
['Toggle', onToggle],
|
|
22
|
-
...GIT_PANE_MENU_OPTIONS.filter(
|
|
23
|
-
({ mode, position }) => !(gitPane.mode === mode && gitPane.position === position)
|
|
24
|
-
).map(({ label, mode, position }) => [label, () => onMove(mode, position)] as ContextMenuItem),
|
|
25
|
-
]
|
|
26
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import type { BoxRenderable, MouseEvent as OtuiMouseEvent } from '@opentui/core'
|
|
2
|
-
|
|
3
|
-
import { useCallback, useMemo, useRef } from 'react'
|
|
4
|
-
|
|
5
|
-
import { useAppStore } from '../../../../state/app-store'
|
|
6
|
-
import { dispatchGlobal } from '../../../../state/dispatch-ref'
|
|
7
|
-
import { useTheme } from '../../../theme'
|
|
8
|
-
import { buildGitPaneContextMenu } from '../../git/pane/git-pane-context-menu'
|
|
9
|
-
import { GitPaneWidget } from '../../git/pane/git-pane-widget'
|
|
10
|
-
import { ContextMenuBox } from '../../overlays/context-menu/context-menu-box'
|
|
11
|
-
import { WorkspaceList } from './workspace-list'
|
|
12
|
-
|
|
13
|
-
interface SidebarProps {
|
|
14
|
-
onResizeDrag?: (event: OtuiMouseEvent) => boolean
|
|
15
|
-
onResizeDragEnd?: () => void
|
|
16
|
-
onEmbeddedGitResizeStart?: (info: {
|
|
17
|
-
containerStart: number
|
|
18
|
-
position: 'top' | 'bottom'
|
|
19
|
-
totalSize: number
|
|
20
|
-
}) => void
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const RESIZE_HANDLE = '─'
|
|
24
|
-
|
|
25
|
-
export function Sidebar({ onEmbeddedGitResizeStart, onResizeDrag, onResizeDragEnd }: SidebarProps) {
|
|
26
|
-
const t = useTheme()
|
|
27
|
-
const sidebarBg = t.background
|
|
28
|
-
const sidebarVisible = useAppStore((s) => s.sidebar.visible)
|
|
29
|
-
const sidebarWidth = useAppStore((s) => s.sidebar.width)
|
|
30
|
-
const gitPane = useAppStore((s) => s.gitPane)
|
|
31
|
-
const focusMode = useAppStore((s) => s.focusMode)
|
|
32
|
-
const bodyRef = useRef<BoxRenderable | null>(null)
|
|
33
|
-
|
|
34
|
-
const handleSidebarMouseDown = useCallback(() => {
|
|
35
|
-
if (focusMode === 'terminal-input') {
|
|
36
|
-
dispatchGlobal({ focusMode: 'navigation', type: 'set-focus-mode' })
|
|
37
|
-
}
|
|
38
|
-
}, [focusMode])
|
|
39
|
-
const handleSidebarMouseDrag = useCallback(
|
|
40
|
-
(event: OtuiMouseEvent) => {
|
|
41
|
-
if (onResizeDrag?.(event) === true) {
|
|
42
|
-
event.preventDefault()
|
|
43
|
-
event.stopPropagation()
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
[onResizeDrag]
|
|
47
|
-
)
|
|
48
|
-
const handleSidebarMouseUp = useCallback(() => {
|
|
49
|
-
onResizeDragEnd?.()
|
|
50
|
-
}, [onResizeDragEnd])
|
|
51
|
-
const handleEmbeddedHandleMouseDown = useCallback(
|
|
52
|
-
(event: OtuiMouseEvent) => {
|
|
53
|
-
const body = bodyRef.current
|
|
54
|
-
if (!body) return
|
|
55
|
-
event.preventDefault()
|
|
56
|
-
event.stopPropagation()
|
|
57
|
-
onEmbeddedGitResizeStart?.({
|
|
58
|
-
containerStart: body.y,
|
|
59
|
-
position: gitPane.position === 'top' ? 'top' : 'bottom',
|
|
60
|
-
totalSize: Math.max(1, body.height),
|
|
61
|
-
})
|
|
62
|
-
},
|
|
63
|
-
[gitPane.position, onEmbeddedGitResizeStart]
|
|
64
|
-
)
|
|
65
|
-
const sidebarMenu = useMemo<[string, () => void][]>(
|
|
66
|
-
() => [
|
|
67
|
-
['Hide sidebar', () => dispatchGlobal({ type: 'toggle-sidebar' })],
|
|
68
|
-
['Toggle git pane', () => dispatchGlobal({ type: 'toggle-git-pane' })],
|
|
69
|
-
[
|
|
70
|
-
'Toggle diff mode',
|
|
71
|
-
() => {
|
|
72
|
-
dispatchGlobal({ type: 'enter-git-mode' })
|
|
73
|
-
dispatchGlobal({ type: 'git-mode-toggle-diff-view' })
|
|
74
|
-
},
|
|
75
|
-
],
|
|
76
|
-
],
|
|
77
|
-
[]
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
if (!sidebarVisible) {
|
|
81
|
-
return null
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const gitEmbedded = gitPane.mode === 'embedded' && gitPane.visible
|
|
85
|
-
const gitOnTop = gitEmbedded && gitPane.position === 'top'
|
|
86
|
-
const gitOnBottom = gitEmbedded && gitPane.position === 'bottom'
|
|
87
|
-
const contentWidth = Math.max(1, sidebarWidth - 1)
|
|
88
|
-
const gitPaneMenu = buildGitPaneContextMenu(
|
|
89
|
-
gitPane,
|
|
90
|
-
() => dispatchGlobal({ type: 'toggle-git-pane' }),
|
|
91
|
-
(mode, position) => {
|
|
92
|
-
dispatchGlobal({ mode, type: 'set-git-pane-mode' })
|
|
93
|
-
dispatchGlobal({ position, type: 'set-git-pane-position' })
|
|
94
|
-
}
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
// flex-grow scaled by 100 (integer preferred); workspace list gets (1-ratio), git gets ratio.
|
|
98
|
-
const listGrow = gitEmbedded ? Math.max(1, Math.round((1 - gitPane.embeddedRatio) * 100)) : 1
|
|
99
|
-
const gitGrow = gitEmbedded ? Math.max(1, Math.round(gitPane.embeddedRatio * 100)) : 0
|
|
100
|
-
|
|
101
|
-
const gitBody = gitEmbedded ? (
|
|
102
|
-
<ContextMenuBox
|
|
103
|
-
flexDirection="column"
|
|
104
|
-
flexGrow={gitGrow}
|
|
105
|
-
flexShrink={1}
|
|
106
|
-
flexBasis={0}
|
|
107
|
-
overflow="hidden"
|
|
108
|
-
rightClickMenu={gitPaneMenu}
|
|
109
|
-
>
|
|
110
|
-
<GitPaneWidget pollingEnabled={gitPane.visible} />
|
|
111
|
-
</ContextMenuBox>
|
|
112
|
-
) : null
|
|
113
|
-
const embeddedHandle = gitEmbedded ? (
|
|
114
|
-
<box minHeight={1} flexShrink={0} onMouseDown={handleEmbeddedHandleMouseDown}>
|
|
115
|
-
<text fg={t.border} selectable={false}>
|
|
116
|
-
{RESIZE_HANDLE.repeat(Math.max(1, contentWidth))}
|
|
117
|
-
</text>
|
|
118
|
-
</box>
|
|
119
|
-
) : null
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<ContextMenuBox
|
|
123
|
-
width={sidebarWidth}
|
|
124
|
-
padding={0}
|
|
125
|
-
flexDirection="column"
|
|
126
|
-
backgroundColor={sidebarBg}
|
|
127
|
-
gap={0}
|
|
128
|
-
overflow="hidden"
|
|
129
|
-
rightClickMenu={sidebarMenu}
|
|
130
|
-
onMouseDown={handleSidebarMouseDown}
|
|
131
|
-
onMouseDrag={handleSidebarMouseDrag}
|
|
132
|
-
onMouseUp={handleSidebarMouseUp}
|
|
133
|
-
>
|
|
134
|
-
<box flexDirection="row" width={sidebarWidth} flexGrow={1} overflow="hidden">
|
|
135
|
-
<box width={contentWidth} flexGrow={1} flexDirection="column" overflow="hidden">
|
|
136
|
-
<box ref={bodyRef} flexDirection="column" flexGrow={1} overflow="hidden">
|
|
137
|
-
{gitOnTop ? (
|
|
138
|
-
<>
|
|
139
|
-
{gitBody}
|
|
140
|
-
{embeddedHandle}
|
|
141
|
-
</>
|
|
142
|
-
) : null}
|
|
143
|
-
<box
|
|
144
|
-
flexDirection="column"
|
|
145
|
-
flexGrow={listGrow}
|
|
146
|
-
flexShrink={1}
|
|
147
|
-
flexBasis={0}
|
|
148
|
-
overflow="hidden"
|
|
149
|
-
>
|
|
150
|
-
<WorkspaceList contentWidth={contentWidth} />
|
|
151
|
-
</box>
|
|
152
|
-
{gitOnBottom ? (
|
|
153
|
-
<>
|
|
154
|
-
{embeddedHandle}
|
|
155
|
-
{gitBody}
|
|
156
|
-
</>
|
|
157
|
-
) : null}
|
|
158
|
-
</box>
|
|
159
|
-
</box>
|
|
160
|
-
</box>
|
|
161
|
-
</ContextMenuBox>
|
|
162
|
-
)
|
|
163
|
-
}
|