@ohzw/worktree-command-tui 0.1.6 → 0.1.7
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/dist/app.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Alert, Spinner } from '@inkjs/ui';
|
|
|
3
3
|
import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { Box, Text, useApp, useInput, useStdin, useStdout } from 'ink';
|
|
5
5
|
import { ActionPanel } from './components/ActionPanel.js';
|
|
6
|
-
import {
|
|
6
|
+
import { ShortcutBar } from './components/ShortcutBar.js';
|
|
7
7
|
import { Header } from './components/Header.js';
|
|
8
8
|
import { HelpWindow } from './components/HelpWindow.js';
|
|
9
9
|
import { FloatingLogWindow } from './components/FloatingLogWindow.js';
|
|
@@ -50,14 +50,14 @@ export function shouldUseCompactLayout(_columns, _rows, _worktreeCount = 0) {
|
|
|
50
50
|
export function shouldUseMinimalLayout(columns, rows) {
|
|
51
51
|
return columns < 20 || rows < 10;
|
|
52
52
|
}
|
|
53
|
-
const STACKED_LAYOUT_FRAME_ROWS =
|
|
54
|
-
const HEADER_HEIGHT =
|
|
55
|
-
const
|
|
53
|
+
const STACKED_LAYOUT_FRAME_ROWS = 5;
|
|
54
|
+
const HEADER_HEIGHT = 4;
|
|
55
|
+
const SHORTCUT_BAR_HEIGHT = 1;
|
|
56
56
|
const PANE_GAP_WIDTH = 1;
|
|
57
57
|
const MIN_STACKED_PANE_HEIGHT = 9;
|
|
58
58
|
export const RESIZE_DEBOUNCE_MS = 100;
|
|
59
59
|
export function shouldStackPanes(columns, rows, _worktreeCount = 0) {
|
|
60
|
-
// Header +
|
|
60
|
+
// Header + shortcut bar consume the fixed chrome; each stacked pane still keeps ~6 visible content lines at the minimum height.
|
|
61
61
|
return columns < 96 && rows >= STACKED_LAYOUT_FRAME_ROWS + (MIN_STACKED_PANE_HEIGHT * 2);
|
|
62
62
|
}
|
|
63
63
|
function getLogPaneHeight(_rootHeight) {
|
|
@@ -579,8 +579,8 @@ function AppShellContent({ initialModel, actions, windowSize, }) {
|
|
|
579
579
|
const stackedWorktreePaneBottom = stackedLayout && stackedWorktreePaneTop !== undefined && paneHeight !== undefined ? stackedWorktreePaneTop + paneHeight - 1 : undefined;
|
|
580
580
|
const stackedSelectionPaneTop = stackedLayout && stackedWorktreePaneBottom !== undefined ? stackedWorktreePaneBottom + 1 : undefined;
|
|
581
581
|
const stackedSelectionPaneBottom = stackedLayout && stackedSelectionPaneTop !== undefined && paneHeight !== undefined ? stackedSelectionPaneTop + paneHeight - 1 : undefined;
|
|
582
|
-
const logPaneTop = showLogPanel ? rootHeight -
|
|
583
|
-
const logPaneBottom = showLogPanel ? rootHeight -
|
|
582
|
+
const logPaneTop = showLogPanel ? rootHeight - SHORTCUT_BAR_HEIGHT - logPaneHeight + 1 : undefined;
|
|
583
|
+
const logPaneBottom = showLogPanel ? rootHeight - SHORTCUT_BAR_HEIGHT : undefined;
|
|
584
584
|
useEffect(() => {
|
|
585
585
|
const onData = (data) => {
|
|
586
586
|
if (isHelpOverlayOpen) {
|
|
@@ -712,6 +712,6 @@ function AppShellContent({ initialModel, actions, windowSize, }) {
|
|
|
712
712
|
? 'Keys: d/y confirm | Esc/n/q cancel'
|
|
713
713
|
: `Keys: ↑↓/jk g/G / Filter ↵${model.setupAvailable ? ' i' : ''}${model.editorAvailable ? ' e' : ''} o d L s r q · Resize terminal for split view` })] }));
|
|
714
714
|
}
|
|
715
|
-
return (_jsxs(Box, { width: rootWidth, height: rootHeight, flexDirection: "column", children: [_jsx(Header, { repoName: model.repoName,
|
|
715
|
+
return (_jsxs(Box, { width: rootWidth, height: rootHeight, flexDirection: "column", children: [_jsx(Header, { repoName: model.repoName, activeBranch: model.activeBranch, status: visibleStatus }), _jsxs(Box, { flexDirection: stackedLayout ? 'column' : 'row', flexGrow: stackedLayout ? 0 : 1, flexShrink: 1, children: [_jsx(WorktreeList, { rows: visibleRows, selectedIndex: selectedIndex, width: stackedLayout ? bodyWidth : listWidth, height: paneHeight, stacked: stackedLayout, scrollOffset: worktreeScrollOffset, filterQuery: filterQuery, isFilterInputOpen: isFilterInputOpen, totalRowCount: model.rows.length }), _jsx(ActionPanel, { selectedRow: selected, activePath: model.activePath, setupAvailable: model.setupAvailable, stacked: stackedLayout, width: stackedLayout ? bodyWidth : actionWidth, height: paneHeight, compactDetails: compactDetailPane, scrollOffset: selectionScrollOffset })] }), showLogPanel ? _jsx(LogPanel, { logs: model.logs, width: bodyWidth, height: logPaneHeight, scrollOffset: logScrollOffset }) : null, _jsx(ShortcutBar, { setupAvailable: model.setupAvailable, editorAvailable: model.editorAvailable, confirmationOpen: confirmationOpen }), safeCompletedAlert ? (_jsx(Box, { position: "absolute", top: 1, right: 2, children: _jsx(Alert, { variant: "success", children: safeCompletedAlert }) })) : null] }));
|
|
716
716
|
}
|
|
717
717
|
const MemoizedAppShell = memo(AppShell);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
export declare function Header({ repoName,
|
|
1
|
+
import type { AppStatus } from '../core/runtime.js';
|
|
2
|
+
export declare function Header({ repoName, activeBranch, status, }: {
|
|
3
3
|
repoName: string;
|
|
4
|
-
namespace: string;
|
|
5
4
|
activeBranch: string | null;
|
|
6
|
-
|
|
5
|
+
status: AppStatus;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
@@ -1,9 +1,49 @@
|
|
|
1
1
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { sanitizeInlineText } from '../core/worktree-projection.js';
|
|
4
|
-
|
|
4
|
+
const KIND_TO_ICON = {
|
|
5
|
+
idle: 'ℹ',
|
|
6
|
+
starting: '⚠',
|
|
7
|
+
'setting-up': '⚠',
|
|
8
|
+
running: '✓',
|
|
9
|
+
stopping: '⚠',
|
|
10
|
+
error: '✘',
|
|
11
|
+
};
|
|
12
|
+
const KIND_TO_COLOR = {
|
|
13
|
+
idle: 'blue',
|
|
14
|
+
starting: 'yellow',
|
|
15
|
+
'setting-up': 'yellow',
|
|
16
|
+
running: 'green',
|
|
17
|
+
stopping: 'yellow',
|
|
18
|
+
error: 'red',
|
|
19
|
+
};
|
|
20
|
+
const KIND_TO_LABEL = {
|
|
21
|
+
idle: 'Idle',
|
|
22
|
+
starting: 'Starting',
|
|
23
|
+
'setting-up': 'Setting up',
|
|
24
|
+
running: 'Running',
|
|
25
|
+
stopping: 'Stopping',
|
|
26
|
+
error: 'Error',
|
|
27
|
+
};
|
|
28
|
+
export function Header({ repoName, activeBranch, status, }) {
|
|
5
29
|
const safeRepoName = sanitizeInlineText(repoName);
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
30
|
+
const safeActiveBranch = activeBranch === null ? null : sanitizeInlineText(activeBranch);
|
|
31
|
+
const safeStatusMessage = sanitizeInlineText(status.message);
|
|
32
|
+
const branchSummary = safeActiveBranch === null
|
|
33
|
+
? ''
|
|
34
|
+
: status.kind === 'running' ? `: ${safeActiveBranch}` : ` · ${safeActiveBranch}`;
|
|
35
|
+
let statusMessageSummary = safeStatusMessage;
|
|
36
|
+
if (status.kind === 'running' && safeActiveBranch !== null) {
|
|
37
|
+
if (safeStatusMessage === 'running' || safeStatusMessage === `Active: ${safeActiveBranch}`) {
|
|
38
|
+
statusMessageSummary = '';
|
|
39
|
+
}
|
|
40
|
+
else if (safeStatusMessage === `started ${safeActiveBranch}`) {
|
|
41
|
+
statusMessageSummary = 'started';
|
|
42
|
+
}
|
|
43
|
+
else if (safeStatusMessage === `restarted ${safeActiveBranch}`) {
|
|
44
|
+
statusMessageSummary = 'restarted';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const messageSummary = statusMessageSummary === '' ? '' : ` — ${statusMessageSummary}`;
|
|
48
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: "blue", flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { bold: true, color: "blue", wrap: "truncate-end", children: ["Repo: ", safeRepoName] }), _jsxs(Text, { color: KIND_TO_COLOR[status.kind], wrap: "truncate-end", children: [KIND_TO_ICON[status.kind], " ", KIND_TO_LABEL[status.kind], branchSummary, messageSummary] })] }));
|
|
9
49
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
function buildKeyHints(setupAvailable, editorAvailable, confirmationOpen) {
|
|
5
|
+
if (confirmationOpen) {
|
|
6
|
+
return [
|
|
7
|
+
{ binding: 'd/y', label: 'Confirm' },
|
|
8
|
+
{ binding: 'Esc/n/q', label: 'Cancel' },
|
|
9
|
+
];
|
|
10
|
+
}
|
|
11
|
+
const hints = [
|
|
12
|
+
{ binding: '↑↓/jk', label: 'Move' },
|
|
13
|
+
{ binding: 'Enter', label: 'Switch' },
|
|
14
|
+
];
|
|
15
|
+
hints.push({ binding: '/', label: 'Filter' });
|
|
16
|
+
if (setupAvailable) {
|
|
17
|
+
hints.push({ binding: 'i', label: 'Setup' });
|
|
18
|
+
}
|
|
19
|
+
if (editorAvailable) {
|
|
20
|
+
hints.push({ binding: 'e', label: 'Editor' });
|
|
21
|
+
}
|
|
22
|
+
hints.push({ binding: 'o', label: 'Open PR' }, { binding: 'd', label: 'Delete' }, { binding: 'L', label: 'Logs' }, { binding: 's', label: 'Stop' }, { binding: 'r', label: 'Refresh' }, { binding: '?', label: 'Help' }, { binding: 'q', label: 'Quit' });
|
|
23
|
+
return hints;
|
|
24
|
+
}
|
|
25
|
+
export function ShortcutBar({ setupAvailable, editorAvailable, confirmationOpen, }) {
|
|
26
|
+
const keyHints = buildKeyHints(setupAvailable, editorAvailable, confirmationOpen);
|
|
27
|
+
return (_jsx(Box, { flexShrink: 0, paddingX: 1, children: _jsx(Text, { wrap: "truncate-end", children: keyHints.map((hint, hintIndex) => (_jsxs(React.Fragment, { children: [hintIndex === 0 ? null : _jsx(Text, { dimColor: true, children: " | " }), _jsx(Text, { color: "white", children: hint.binding }), _jsxs(Text, { dimColor: true, children: [" ", hint.label] })] }, hint.binding))) }) }));
|
|
28
|
+
}
|