letmecook 0.0.13 → 0.0.14
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 +1 -1
- package/src/flows/add-repos.ts +52 -16
- package/src/flows/edit-session.ts +104 -46
- package/src/flows/new-session.ts +81 -76
- package/src/ui/add-repos.ts +184 -180
- package/src/ui/common/command-runner.ts +249 -0
- package/src/ui/common/footer.ts +105 -0
- package/src/ui/common/keyboard.ts +95 -0
- package/src/ui/confirm-delete.ts +10 -9
- package/src/ui/conflict.ts +10 -1
- package/src/ui/exit.ts +79 -47
- package/src/ui/list.ts +23 -14
- package/src/ui/main-menu.ts +25 -46
- package/src/ui/reclone-prompt.ts +11 -9
- package/src/ui/renderer.ts +16 -4
- package/src/ui/session-details.ts +43 -18
- package/src/ui/session-settings.ts +91 -71
- package/src/ui/skills.ts +44 -100
package/src/ui/exit.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type CliRenderer,
|
|
3
|
+
TextRenderable,
|
|
4
|
+
SelectRenderable,
|
|
5
|
+
SelectRenderableEvents,
|
|
6
|
+
type KeyEvent,
|
|
7
|
+
} from "@opentui/core";
|
|
2
8
|
import { createBaseLayout, clearLayout } from "./renderer";
|
|
3
9
|
import type { Session, ExitChoice, RepoSpec } from "../types";
|
|
4
10
|
import { sessionHasUncommittedChanges } from "../git";
|
|
11
|
+
import { showFooter, hideFooter } from "./common/footer";
|
|
12
|
+
import { isEscape } from "./common/keyboard";
|
|
5
13
|
|
|
6
14
|
export function showExitPrompt(renderer: CliRenderer, session: Session): Promise<ExitChoice> {
|
|
7
15
|
return new Promise((resolve) => {
|
|
@@ -23,47 +31,59 @@ export function showExitPrompt(renderer: CliRenderer, session: Session): Promise
|
|
|
23
31
|
id: "question",
|
|
24
32
|
content: "What would you like to do?",
|
|
25
33
|
fg: "#e2e8f0",
|
|
34
|
+
marginBottom: 1,
|
|
26
35
|
});
|
|
27
36
|
content.add(question);
|
|
28
37
|
|
|
29
|
-
const
|
|
30
|
-
id: "
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
const select = new SelectRenderable(renderer, {
|
|
39
|
+
id: "exit-select",
|
|
40
|
+
width: 40,
|
|
41
|
+
height: 4,
|
|
42
|
+
options: [
|
|
43
|
+
{ name: "Resume session", description: "", value: "resume" },
|
|
44
|
+
{ name: "Edit session", description: "", value: "edit" },
|
|
45
|
+
{ name: "Delete session", description: "", value: "delete" },
|
|
46
|
+
{ name: "Back to home", description: "", value: "home" },
|
|
47
|
+
],
|
|
48
|
+
showDescription: false,
|
|
49
|
+
backgroundColor: "transparent",
|
|
50
|
+
focusedBackgroundColor: "transparent",
|
|
51
|
+
selectedBackgroundColor: "#334155",
|
|
52
|
+
textColor: "#e2e8f0",
|
|
53
|
+
selectedTextColor: "#38bdf8",
|
|
33
54
|
marginTop: 1,
|
|
34
55
|
});
|
|
35
|
-
content.add(
|
|
56
|
+
content.add(select);
|
|
36
57
|
|
|
37
|
-
|
|
38
|
-
if (key.name === "return" || key.name === "enter") {
|
|
39
|
-
cleanup();
|
|
40
|
-
resolve("resume");
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (key.name === "e") {
|
|
45
|
-
cleanup();
|
|
46
|
-
resolve("edit");
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
58
|
+
select.focus();
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
60
|
+
const handleSelect = (_index: number, option: { value: string }) => {
|
|
61
|
+
cleanup();
|
|
62
|
+
resolve(option.value as ExitChoice);
|
|
63
|
+
};
|
|
55
64
|
|
|
56
|
-
|
|
65
|
+
const handleKeypress = (key: KeyEvent) => {
|
|
66
|
+
if (isEscape(key)) {
|
|
57
67
|
cleanup();
|
|
58
68
|
resolve("home");
|
|
59
69
|
}
|
|
60
70
|
};
|
|
61
71
|
|
|
62
72
|
const cleanup = () => {
|
|
73
|
+
select.off(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
63
74
|
renderer.keyInput.off("keypress", handleKeypress);
|
|
75
|
+
select.blur();
|
|
76
|
+
hideFooter(renderer);
|
|
64
77
|
clearLayout(renderer);
|
|
65
78
|
};
|
|
66
79
|
|
|
80
|
+
showFooter(renderer, content, {
|
|
81
|
+
navigate: true,
|
|
82
|
+
select: true,
|
|
83
|
+
back: true,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
select.on(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
67
87
|
renderer.keyInput.on("keypress", handleKeypress);
|
|
68
88
|
});
|
|
69
89
|
}
|
|
@@ -109,47 +129,59 @@ export function showExitPromptWithChanges(
|
|
|
109
129
|
id: "question",
|
|
110
130
|
content: "What would you like to do?",
|
|
111
131
|
fg: "#e2e8f0",
|
|
132
|
+
marginBottom: 1,
|
|
112
133
|
});
|
|
113
134
|
content.add(question);
|
|
114
135
|
|
|
115
|
-
const
|
|
116
|
-
id: "
|
|
117
|
-
|
|
118
|
-
|
|
136
|
+
const select = new SelectRenderable(renderer, {
|
|
137
|
+
id: "exit-select",
|
|
138
|
+
width: 40,
|
|
139
|
+
height: 4,
|
|
140
|
+
options: [
|
|
141
|
+
{ name: "Resume session", description: "", value: "resume" },
|
|
142
|
+
{ name: "Edit session", description: "", value: "edit" },
|
|
143
|
+
{ name: "Delete session", description: "", value: "delete" },
|
|
144
|
+
{ name: "Back to home", description: "", value: "home" },
|
|
145
|
+
],
|
|
146
|
+
showDescription: false,
|
|
147
|
+
backgroundColor: "transparent",
|
|
148
|
+
focusedBackgroundColor: "transparent",
|
|
149
|
+
selectedBackgroundColor: "#334155",
|
|
150
|
+
textColor: "#e2e8f0",
|
|
151
|
+
selectedTextColor: "#38bdf8",
|
|
119
152
|
marginTop: 1,
|
|
120
153
|
});
|
|
121
|
-
content.add(
|
|
154
|
+
content.add(select);
|
|
122
155
|
|
|
123
|
-
|
|
124
|
-
if (key.name === "return" || key.name === "enter") {
|
|
125
|
-
cleanup();
|
|
126
|
-
resolve("resume");
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (key.name === "e") {
|
|
131
|
-
cleanup();
|
|
132
|
-
resolve("edit");
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
156
|
+
select.focus();
|
|
135
157
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
158
|
+
const handleSelect = (_index: number, option: { value: string }) => {
|
|
159
|
+
cleanup();
|
|
160
|
+
resolve(option.value as ExitChoice);
|
|
161
|
+
};
|
|
141
162
|
|
|
142
|
-
|
|
163
|
+
const handleKeypress = (key: KeyEvent) => {
|
|
164
|
+
if (isEscape(key)) {
|
|
143
165
|
cleanup();
|
|
144
166
|
resolve("home");
|
|
145
167
|
}
|
|
146
168
|
};
|
|
147
169
|
|
|
148
170
|
const cleanup = () => {
|
|
171
|
+
select.off(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
149
172
|
renderer.keyInput.off("keypress", handleKeypress);
|
|
173
|
+
select.blur();
|
|
174
|
+
hideFooter(renderer);
|
|
150
175
|
clearLayout(renderer);
|
|
151
176
|
};
|
|
152
177
|
|
|
178
|
+
showFooter(renderer, content, {
|
|
179
|
+
navigate: true,
|
|
180
|
+
select: true,
|
|
181
|
+
back: true,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
select.on(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
153
185
|
renderer.keyInput.on("keypress", handleKeypress);
|
|
154
186
|
});
|
|
155
187
|
}
|
package/src/ui/list.ts
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
import { createBaseLayout, clearLayout } from "./renderer";
|
|
9
9
|
import { buildSessionOptions } from "./session-options";
|
|
10
10
|
import type { Session } from "../types";
|
|
11
|
+
import { showFooter, hideFooter } from "./common/footer";
|
|
12
|
+
import { isEscape, isArrowUp, isArrowDown } from "./common/keyboard";
|
|
11
13
|
|
|
12
14
|
export type ListAction =
|
|
13
15
|
| { type: "resume"; session: Session }
|
|
@@ -19,7 +21,7 @@ export function showSessionList(renderer: CliRenderer, sessions: Session[]): Pro
|
|
|
19
21
|
return new Promise((resolve) => {
|
|
20
22
|
clearLayout(renderer);
|
|
21
23
|
|
|
22
|
-
const { content } = createBaseLayout(renderer,
|
|
24
|
+
const { content } = createBaseLayout(renderer, `Sessions (${sessions.length})`);
|
|
23
25
|
|
|
24
26
|
if (sessions.length === 0) {
|
|
25
27
|
const emptyText = new TextRenderable(renderer, {
|
|
@@ -30,13 +32,21 @@ export function showSessionList(renderer: CliRenderer, sessions: Session[]): Pro
|
|
|
30
32
|
content.add(emptyText);
|
|
31
33
|
|
|
32
34
|
const handleKeypress = (key: KeyEvent) => {
|
|
33
|
-
if (key.name === "
|
|
35
|
+
if (key.name === "q" || isEscape(key)) {
|
|
34
36
|
renderer.keyInput.off("keypress", handleKeypress);
|
|
37
|
+
hideFooter(renderer);
|
|
35
38
|
clearLayout(renderer);
|
|
36
39
|
resolve({ type: "quit" });
|
|
37
40
|
}
|
|
38
41
|
};
|
|
39
42
|
|
|
43
|
+
showFooter(renderer, content, {
|
|
44
|
+
navigate: false,
|
|
45
|
+
select: false,
|
|
46
|
+
back: false,
|
|
47
|
+
quit: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
40
50
|
renderer.keyInput.on("keypress", handleKeypress);
|
|
41
51
|
return;
|
|
42
52
|
}
|
|
@@ -60,15 +70,6 @@ export function showSessionList(renderer: CliRenderer, sessions: Session[]): Pro
|
|
|
60
70
|
});
|
|
61
71
|
content.add(select);
|
|
62
72
|
|
|
63
|
-
// Instructions
|
|
64
|
-
const instructions = new TextRenderable(renderer, {
|
|
65
|
-
id: "instructions",
|
|
66
|
-
content: "\n[Enter] Resume [d] Delete [a] Nuke All [Esc] Quit",
|
|
67
|
-
fg: "#64748b",
|
|
68
|
-
marginTop: 1,
|
|
69
|
-
});
|
|
70
|
-
content.add(instructions);
|
|
71
|
-
|
|
72
73
|
select.focus();
|
|
73
74
|
|
|
74
75
|
let selectedIndex = 0;
|
|
@@ -80,9 +81,9 @@ export function showSessionList(renderer: CliRenderer, sessions: Session[]): Pro
|
|
|
80
81
|
|
|
81
82
|
const handleKeypress = (key: KeyEvent) => {
|
|
82
83
|
// Track selection for delete
|
|
83
|
-
if (key
|
|
84
|
+
if (isArrowUp(key)) {
|
|
84
85
|
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
85
|
-
} else if (key
|
|
86
|
+
} else if (isArrowDown(key)) {
|
|
86
87
|
selectedIndex = Math.min(sessions.length - 1, selectedIndex + 1);
|
|
87
88
|
} else if (key.name === "d") {
|
|
88
89
|
const session = sessions[selectedIndex];
|
|
@@ -93,7 +94,7 @@ export function showSessionList(renderer: CliRenderer, sessions: Session[]): Pro
|
|
|
93
94
|
} else if (key.name === "a") {
|
|
94
95
|
cleanup();
|
|
95
96
|
resolve({ type: "nuke-all" });
|
|
96
|
-
} else if (key.name === "
|
|
97
|
+
} else if (key.name === "q" || isEscape(key)) {
|
|
97
98
|
cleanup();
|
|
98
99
|
resolve({ type: "quit" });
|
|
99
100
|
}
|
|
@@ -103,9 +104,17 @@ export function showSessionList(renderer: CliRenderer, sessions: Session[]): Pro
|
|
|
103
104
|
select.off(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
104
105
|
renderer.keyInput.off("keypress", handleKeypress);
|
|
105
106
|
select.blur();
|
|
107
|
+
hideFooter(renderer);
|
|
106
108
|
clearLayout(renderer);
|
|
107
109
|
};
|
|
108
110
|
|
|
111
|
+
showFooter(renderer, content, {
|
|
112
|
+
navigate: true,
|
|
113
|
+
select: false,
|
|
114
|
+
back: false,
|
|
115
|
+
custom: ["Enter Resume", "d Delete", "a Nuke All", "q Quit"],
|
|
116
|
+
});
|
|
117
|
+
|
|
109
118
|
select.on(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
110
119
|
renderer.keyInput.on("keypress", handleKeypress);
|
|
111
120
|
});
|
package/src/ui/main-menu.ts
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
import { createBaseLayout, clearLayout } from "./renderer";
|
|
9
9
|
import { buildSessionOptions } from "./session-options";
|
|
10
10
|
import type { Session } from "../types";
|
|
11
|
+
import { showFooter, hideFooter } from "./common/footer";
|
|
12
|
+
import { isEscape, isArrowUp, isArrowDown } from "./common/keyboard";
|
|
11
13
|
|
|
12
14
|
export type MainMenuAction =
|
|
13
15
|
| { type: "new-session" }
|
|
@@ -19,23 +21,13 @@ export function showMainMenu(renderer: CliRenderer, sessions: Session[]): Promis
|
|
|
19
21
|
return new Promise((resolve) => {
|
|
20
22
|
clearLayout(renderer);
|
|
21
23
|
|
|
22
|
-
const { content } = createBaseLayout(renderer
|
|
24
|
+
const { content } = createBaseLayout(renderer);
|
|
23
25
|
|
|
24
|
-
//
|
|
25
|
-
const welcome = new TextRenderable(renderer, {
|
|
26
|
-
id: "welcome",
|
|
27
|
-
content: "Multi-repo workspace manager for AI coding sessions",
|
|
28
|
-
fg: "#94a3b8",
|
|
29
|
-
marginBottom: 2,
|
|
30
|
-
});
|
|
31
|
-
content.add(welcome);
|
|
32
|
-
|
|
33
|
-
// Sessions section
|
|
26
|
+
// Sessions section with count
|
|
34
27
|
const sessionsHeader = new TextRenderable(renderer, {
|
|
35
28
|
id: "sessions-header",
|
|
36
|
-
content:
|
|
29
|
+
content: `Sessions (${sessions.length})`,
|
|
37
30
|
fg: "#e2e8f0",
|
|
38
|
-
marginTop: 1,
|
|
39
31
|
marginBottom: 1,
|
|
40
32
|
});
|
|
41
33
|
content.add(sessionsHeader);
|
|
@@ -71,36 +63,6 @@ export function showMainMenu(renderer: CliRenderer, sessions: Session[]): Promis
|
|
|
71
63
|
content.add(emptyText);
|
|
72
64
|
}
|
|
73
65
|
|
|
74
|
-
// Actions section
|
|
75
|
-
const actionsHeader = new TextRenderable(renderer, {
|
|
76
|
-
id: "actions-header",
|
|
77
|
-
content: "Actions",
|
|
78
|
-
fg: "#e2e8f0",
|
|
79
|
-
marginTop: 1,
|
|
80
|
-
marginBottom: 1,
|
|
81
|
-
});
|
|
82
|
-
content.add(actionsHeader);
|
|
83
|
-
|
|
84
|
-
const actionsText = new TextRenderable(renderer, {
|
|
85
|
-
id: "actions",
|
|
86
|
-
content:
|
|
87
|
-
sessions.length > 0
|
|
88
|
-
? "[n] New session\n[d] Delete session\n[Esc] Quit"
|
|
89
|
-
: "[n] New session\n[Esc] Quit",
|
|
90
|
-
fg: "#94a3b8",
|
|
91
|
-
});
|
|
92
|
-
content.add(actionsText);
|
|
93
|
-
|
|
94
|
-
if (sessions.length > 0) {
|
|
95
|
-
const instructions = new TextRenderable(renderer, {
|
|
96
|
-
id: "instructions",
|
|
97
|
-
content: "\n[Enter] Resume session",
|
|
98
|
-
fg: "#64748b",
|
|
99
|
-
marginTop: 1,
|
|
100
|
-
});
|
|
101
|
-
content.add(instructions);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
66
|
if (select) {
|
|
105
67
|
select.focus();
|
|
106
68
|
}
|
|
@@ -113,9 +75,9 @@ export function showMainMenu(renderer: CliRenderer, sessions: Session[]): Promis
|
|
|
113
75
|
};
|
|
114
76
|
|
|
115
77
|
const handleKeypress = (key: KeyEvent) => {
|
|
116
|
-
if (sessions.length > 0 && (key
|
|
78
|
+
if (sessions.length > 0 && isArrowUp(key)) {
|
|
117
79
|
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
118
|
-
} else if (sessions.length > 0 && (key
|
|
80
|
+
} else if (sessions.length > 0 && isArrowDown(key)) {
|
|
119
81
|
selectedIndex = Math.min(sessions.length - 1, selectedIndex + 1);
|
|
120
82
|
} else if (key.name === "d" && sessions.length > 0) {
|
|
121
83
|
const session = sessions[selectedIndex];
|
|
@@ -132,7 +94,7 @@ export function showMainMenu(renderer: CliRenderer, sessions: Session[]): Promis
|
|
|
132
94
|
return;
|
|
133
95
|
}
|
|
134
96
|
|
|
135
|
-
if (key.name === "
|
|
97
|
+
if (key.name === "q" || isEscape(key)) {
|
|
136
98
|
cleanup();
|
|
137
99
|
resolve({ type: "quit" });
|
|
138
100
|
}
|
|
@@ -144,9 +106,26 @@ export function showMainMenu(renderer: CliRenderer, sessions: Session[]): Promis
|
|
|
144
106
|
select.blur();
|
|
145
107
|
}
|
|
146
108
|
renderer.keyInput.off("keypress", handleKeypress);
|
|
109
|
+
hideFooter(renderer);
|
|
147
110
|
clearLayout(renderer);
|
|
148
111
|
};
|
|
149
112
|
|
|
113
|
+
// Show footer with context-aware actions
|
|
114
|
+
const footerActions: string[] = [];
|
|
115
|
+
if (sessions.length > 0) {
|
|
116
|
+
footerActions.push("Enter Open", "n New", "d Delete");
|
|
117
|
+
} else {
|
|
118
|
+
footerActions.push("n New");
|
|
119
|
+
}
|
|
120
|
+
footerActions.push("q Quit");
|
|
121
|
+
|
|
122
|
+
showFooter(renderer, content, {
|
|
123
|
+
navigate: sessions.length > 0,
|
|
124
|
+
select: false,
|
|
125
|
+
back: false,
|
|
126
|
+
custom: footerActions,
|
|
127
|
+
});
|
|
128
|
+
|
|
150
129
|
if (select) {
|
|
151
130
|
select.on(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
152
131
|
}
|
package/src/ui/reclone-prompt.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
} from "@opentui/core";
|
|
8
8
|
import { createBaseLayout, clearLayout } from "./renderer";
|
|
9
9
|
import type { RepoSpec } from "../types";
|
|
10
|
+
import { showFooter, hideFooter } from "./common/footer";
|
|
11
|
+
import { isEscape } from "./common/keyboard";
|
|
10
12
|
|
|
11
13
|
export type RecloneChoice = "reclone" | "skip";
|
|
12
14
|
|
|
@@ -58,14 +60,6 @@ export function showReclonePrompt(renderer: CliRenderer, repo: RepoSpec): Promis
|
|
|
58
60
|
});
|
|
59
61
|
content.add(select);
|
|
60
62
|
|
|
61
|
-
const instructions = new TextRenderable(renderer, {
|
|
62
|
-
id: "instructions",
|
|
63
|
-
content: "\n[Enter] Select [Esc] Skip",
|
|
64
|
-
fg: "#64748b",
|
|
65
|
-
marginTop: 1,
|
|
66
|
-
});
|
|
67
|
-
content.add(instructions);
|
|
68
|
-
|
|
69
63
|
select.focus();
|
|
70
64
|
|
|
71
65
|
const handleSelect = (_index: number, option: { value: string }) => {
|
|
@@ -74,7 +68,7 @@ export function showReclonePrompt(renderer: CliRenderer, repo: RepoSpec): Promis
|
|
|
74
68
|
};
|
|
75
69
|
|
|
76
70
|
const handleKeypress = (key: KeyEvent) => {
|
|
77
|
-
if (key
|
|
71
|
+
if (isEscape(key)) {
|
|
78
72
|
cleanup();
|
|
79
73
|
resolve("skip");
|
|
80
74
|
}
|
|
@@ -84,9 +78,17 @@ export function showReclonePrompt(renderer: CliRenderer, repo: RepoSpec): Promis
|
|
|
84
78
|
select.off(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
85
79
|
renderer.keyInput.off("keypress", handleKeypress);
|
|
86
80
|
select.blur();
|
|
81
|
+
hideFooter(renderer);
|
|
87
82
|
clearLayout(renderer);
|
|
88
83
|
};
|
|
89
84
|
|
|
85
|
+
showFooter(renderer, content, {
|
|
86
|
+
navigate: true,
|
|
87
|
+
select: true,
|
|
88
|
+
back: true,
|
|
89
|
+
custom: ["Esc Skip"],
|
|
90
|
+
});
|
|
91
|
+
|
|
90
92
|
select.on(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
91
93
|
renderer.keyInput.on("keypress", handleKeypress);
|
|
92
94
|
});
|
package/src/ui/renderer.ts
CHANGED
|
@@ -101,8 +101,20 @@ export function createBaseLayout(r: CliRenderer, subtitle?: string): LayoutEleme
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export function clearLayout(r: CliRenderer): void {
|
|
104
|
-
// Remove known elements
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
// Remove known elements (ignore if they don't exist)
|
|
105
|
+
try {
|
|
106
|
+
r.root.remove("main-container");
|
|
107
|
+
} catch {
|
|
108
|
+
// Element doesn't exist
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
r.root.remove("title");
|
|
112
|
+
} catch {
|
|
113
|
+
// Element doesn't exist
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
r.root.remove("content");
|
|
117
|
+
} catch {
|
|
118
|
+
// Element doesn't exist
|
|
119
|
+
}
|
|
108
120
|
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type CliRenderer,
|
|
3
|
+
TextRenderable,
|
|
4
|
+
SelectRenderable,
|
|
5
|
+
SelectRenderableEvents,
|
|
6
|
+
type KeyEvent,
|
|
7
|
+
} from "@opentui/core";
|
|
2
8
|
import { createBaseLayout, clearLayout } from "./renderer";
|
|
3
9
|
import type { Session } from "../types";
|
|
4
10
|
import { formatRepoList } from "./common/repo-formatter";
|
|
11
|
+
import { showFooter, hideFooter } from "./common/footer";
|
|
12
|
+
import { isEscape } from "./common/keyboard";
|
|
5
13
|
|
|
6
14
|
export type SessionDetailsAction = "resume" | "edit" | "add-repos" | "back";
|
|
7
15
|
|
|
@@ -41,37 +49,54 @@ export function showSessionDetails(
|
|
|
41
49
|
});
|
|
42
50
|
content.add(reposInfo);
|
|
43
51
|
|
|
44
|
-
const
|
|
45
|
-
id: "
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
const select = new SelectRenderable(renderer, {
|
|
53
|
+
id: "session-details-select",
|
|
54
|
+
width: 40,
|
|
55
|
+
height: 3,
|
|
56
|
+
options: [
|
|
57
|
+
{ name: "Resume session", description: "", value: "resume" },
|
|
58
|
+
{ name: "Edit settings", description: "", value: "edit" },
|
|
59
|
+
{ name: "Back", description: "", value: "back" },
|
|
60
|
+
],
|
|
61
|
+
showDescription: false,
|
|
62
|
+
backgroundColor: "transparent",
|
|
63
|
+
focusedBackgroundColor: "transparent",
|
|
64
|
+
selectedBackgroundColor: "#334155",
|
|
65
|
+
textColor: "#e2e8f0",
|
|
66
|
+
selectedTextColor: "#38bdf8",
|
|
48
67
|
marginTop: 1,
|
|
49
68
|
});
|
|
50
|
-
content.add(
|
|
69
|
+
content.add(select);
|
|
51
70
|
|
|
52
|
-
|
|
53
|
-
if (key.name === "return" || key.name === "enter") {
|
|
54
|
-
cleanup();
|
|
55
|
-
resolve("resume");
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
71
|
+
select.focus();
|
|
58
72
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
73
|
+
const handleSelect = (_index: number, option: { value: string }) => {
|
|
74
|
+
cleanup();
|
|
75
|
+
resolve(option.value as SessionDetailsAction);
|
|
76
|
+
};
|
|
64
77
|
|
|
65
|
-
|
|
78
|
+
const handleKeypress = (key: KeyEvent) => {
|
|
79
|
+
if (isEscape(key)) {
|
|
66
80
|
cleanup();
|
|
67
81
|
resolve("back");
|
|
68
82
|
}
|
|
69
83
|
};
|
|
70
84
|
|
|
71
85
|
const cleanup = () => {
|
|
86
|
+
select.off(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
72
87
|
renderer.keyInput.off("keypress", handleKeypress);
|
|
88
|
+
select.blur();
|
|
89
|
+
hideFooter(renderer);
|
|
73
90
|
clearLayout(renderer);
|
|
74
91
|
};
|
|
92
|
+
|
|
93
|
+
showFooter(renderer, content, {
|
|
94
|
+
navigate: true,
|
|
95
|
+
select: true,
|
|
96
|
+
back: true,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
select.on(SelectRenderableEvents.ITEM_SELECTED, handleSelect);
|
|
75
100
|
renderer.keyInput.on("keypress", handleKeypress);
|
|
76
101
|
});
|
|
77
102
|
}
|