ttrak 0.1.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/.claude/settings.local.json +7 -0
- package/.github/workflows/publish.yml +24 -0
- package/AGENTS.md +151 -0
- package/README.md +15 -0
- package/config.schema.json +103 -0
- package/data.schema.json +144 -0
- package/package.json +37 -0
- package/scripts/generate-schema.ts +10 -0
- package/src/data/store.ts +86 -0
- package/src/index.ts +27 -0
- package/src/schema.ts +65 -0
- package/src/ui/components/TaskItem.ts +149 -0
- package/src/ui/components/TaskModal.ts +295 -0
- package/src/ui/theme.ts +87 -0
- package/src/ui/views/TaskListView.ts +427 -0
- package/test-store.ts +6 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { BoxRenderable, TextRenderable, type CliRenderer } from "@opentui/core";
|
|
2
|
+
import type { Task } from "../../schema";
|
|
3
|
+
import type { Theme } from "../theme";
|
|
4
|
+
|
|
5
|
+
function adjustColorForSelection(color: string): string {
|
|
6
|
+
const hex = color.replace("#", "");
|
|
7
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
8
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
9
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
10
|
+
|
|
11
|
+
const factor = 1.5;
|
|
12
|
+
const newR = Math.min(255, Math.floor(r * factor));
|
|
13
|
+
const newG = Math.min(255, Math.floor(g * factor));
|
|
14
|
+
const newB = Math.min(255, Math.floor(b * factor));
|
|
15
|
+
|
|
16
|
+
return `#${newR.toString(16).padStart(2, "0")}${newG.toString(16).padStart(2, "0")}${newB.toString(16).padStart(2, "0")}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class TaskItem {
|
|
20
|
+
private container: BoxRenderable;
|
|
21
|
+
private statusIcon: TextRenderable;
|
|
22
|
+
private idText: TextRenderable;
|
|
23
|
+
private titleText: TextRenderable;
|
|
24
|
+
private metaText: TextRenderable;
|
|
25
|
+
private task: Task;
|
|
26
|
+
private theme: Theme;
|
|
27
|
+
|
|
28
|
+
constructor(renderer: CliRenderer, task: Task, theme: Theme, index: number) {
|
|
29
|
+
this.task = task;
|
|
30
|
+
this.theme = theme;
|
|
31
|
+
|
|
32
|
+
this.container = new BoxRenderable(renderer, {
|
|
33
|
+
id: `task-${index}`,
|
|
34
|
+
height: 1,
|
|
35
|
+
flexDirection: "row",
|
|
36
|
+
backgroundColor: "transparent",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const icon =
|
|
40
|
+
task.status === "done"
|
|
41
|
+
? "✓"
|
|
42
|
+
: task.status === "inProgress"
|
|
43
|
+
? "▶"
|
|
44
|
+
: task.status === "cancelled"
|
|
45
|
+
? "✗"
|
|
46
|
+
: "○";
|
|
47
|
+
const iconColor =
|
|
48
|
+
task.status === "done"
|
|
49
|
+
? theme.success
|
|
50
|
+
: task.status === "inProgress"
|
|
51
|
+
? theme.warning
|
|
52
|
+
: task.status === "cancelled"
|
|
53
|
+
? theme.error
|
|
54
|
+
: theme.fg;
|
|
55
|
+
this.statusIcon = new TextRenderable(renderer, {
|
|
56
|
+
id: `status-${index}`,
|
|
57
|
+
content: icon,
|
|
58
|
+
fg: iconColor,
|
|
59
|
+
width: 2,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.idText = new TextRenderable(renderer, {
|
|
63
|
+
id: `id-${index}`,
|
|
64
|
+
content: task.id.padEnd(10),
|
|
65
|
+
fg: theme.muted,
|
|
66
|
+
width: 10,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.titleText = new TextRenderable(renderer, {
|
|
70
|
+
id: `title-${index}`,
|
|
71
|
+
content: task.title,
|
|
72
|
+
fg: theme.fg,
|
|
73
|
+
flexGrow: 1,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const priorityLabel =
|
|
77
|
+
task.priority === "none"
|
|
78
|
+
? ""
|
|
79
|
+
: `[${task.priority.toUpperCase().slice(0, 3)}]`;
|
|
80
|
+
const priorityColor =
|
|
81
|
+
task.priority === "urgent"
|
|
82
|
+
? theme.error
|
|
83
|
+
: task.priority === "high"
|
|
84
|
+
? theme.warning
|
|
85
|
+
: task.priority === "medium"
|
|
86
|
+
? theme.accent
|
|
87
|
+
: task.priority === "low"
|
|
88
|
+
? theme.muted
|
|
89
|
+
: theme.muted;
|
|
90
|
+
const source = task.linear ? "L" : task.github ? "G" : "";
|
|
91
|
+
this.metaText = new TextRenderable(renderer, {
|
|
92
|
+
id: `meta-${index}`,
|
|
93
|
+
content: `${priorityLabel} ${source}`.trim(),
|
|
94
|
+
fg: priorityColor,
|
|
95
|
+
width: 10,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
this.container.add(this.statusIcon);
|
|
99
|
+
this.container.add(this.idText);
|
|
100
|
+
this.container.add(this.titleText);
|
|
101
|
+
this.container.add(this.metaText);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setSelected(selected: boolean) {
|
|
105
|
+
const statusColor =
|
|
106
|
+
this.task.status === "done"
|
|
107
|
+
? this.theme.success
|
|
108
|
+
: this.task.status === "inProgress"
|
|
109
|
+
? this.theme.warning
|
|
110
|
+
: this.task.status === "cancelled"
|
|
111
|
+
? this.theme.error
|
|
112
|
+
: this.theme.fg;
|
|
113
|
+
|
|
114
|
+
const priorityColor =
|
|
115
|
+
this.task.priority === "urgent"
|
|
116
|
+
? this.theme.error
|
|
117
|
+
: this.task.priority === "high"
|
|
118
|
+
? this.theme.warning
|
|
119
|
+
: this.task.priority === "medium"
|
|
120
|
+
? this.theme.accent
|
|
121
|
+
: this.theme.muted;
|
|
122
|
+
|
|
123
|
+
if (selected) {
|
|
124
|
+
this.container.backgroundColor = this.theme.selectedBg;
|
|
125
|
+
this.titleText.fg = this.theme.selectedFg;
|
|
126
|
+
this.idText.fg = this.theme.selectedFg;
|
|
127
|
+
this.statusIcon.fg = this.theme.selectedFg;
|
|
128
|
+
this.metaText.fg = this.theme.selectedFg;
|
|
129
|
+
} else {
|
|
130
|
+
this.container.backgroundColor = "transparent";
|
|
131
|
+
this.titleText.fg = this.theme.fg;
|
|
132
|
+
this.idText.fg = this.theme.muted;
|
|
133
|
+
this.statusIcon.fg = statusColor;
|
|
134
|
+
this.metaText.fg = priorityColor;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getRenderable(): BoxRenderable {
|
|
139
|
+
return this.container;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getTask(): Task {
|
|
143
|
+
return this.task;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
destroy() {
|
|
147
|
+
this.container.destroy();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BoxRenderable,
|
|
3
|
+
TextRenderable,
|
|
4
|
+
InputRenderable,
|
|
5
|
+
TabSelectRenderable,
|
|
6
|
+
type CliRenderer,
|
|
7
|
+
type KeyEvent,
|
|
8
|
+
} from "@opentui/core";
|
|
9
|
+
import type { Task } from "../../schema";
|
|
10
|
+
import type { Theme } from "../theme";
|
|
11
|
+
|
|
12
|
+
type ModalMode = "create" | "edit";
|
|
13
|
+
|
|
14
|
+
export interface TaskModalResult {
|
|
15
|
+
title: string;
|
|
16
|
+
priority: Task["priority"];
|
|
17
|
+
status: Task["status"];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class TaskModal {
|
|
21
|
+
private renderer: CliRenderer;
|
|
22
|
+
private theme: Theme;
|
|
23
|
+
private overlay: BoxRenderable;
|
|
24
|
+
private modal: BoxRenderable;
|
|
25
|
+
private titleInput: InputRenderable;
|
|
26
|
+
private prioritySelect: TabSelectRenderable;
|
|
27
|
+
private statusSelect: TabSelectRenderable;
|
|
28
|
+
private boundKeyHandler: (key: KeyEvent) => void;
|
|
29
|
+
private onSubmit: ((result: TaskModalResult) => void) | null = null;
|
|
30
|
+
private onCancel: (() => void) | null = null;
|
|
31
|
+
private focusIndex = 0;
|
|
32
|
+
private mode: ModalMode;
|
|
33
|
+
private initialTask?: Task;
|
|
34
|
+
|
|
35
|
+
constructor(renderer: CliRenderer, theme: Theme) {
|
|
36
|
+
this.renderer = renderer;
|
|
37
|
+
this.theme = theme;
|
|
38
|
+
this.mode = "create";
|
|
39
|
+
this.boundKeyHandler = this.handleKeyPress.bind(this);
|
|
40
|
+
|
|
41
|
+
const termWidth = renderer.terminalWidth;
|
|
42
|
+
const termHeight = renderer.terminalHeight;
|
|
43
|
+
const modalWidth = 50;
|
|
44
|
+
const modalHeight = 14;
|
|
45
|
+
const modalLeft = Math.floor((termWidth - modalWidth) / 2);
|
|
46
|
+
const modalTop = Math.floor((termHeight - modalHeight) / 2);
|
|
47
|
+
|
|
48
|
+
this.overlay = new BoxRenderable(renderer, {
|
|
49
|
+
id: "modal-overlay",
|
|
50
|
+
position: "absolute",
|
|
51
|
+
left: 0,
|
|
52
|
+
top: 0,
|
|
53
|
+
width: termWidth,
|
|
54
|
+
height: termHeight,
|
|
55
|
+
backgroundColor: "#00000088",
|
|
56
|
+
zIndex: 100,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
this.modal = new BoxRenderable(renderer, {
|
|
60
|
+
id: "task-modal",
|
|
61
|
+
position: "absolute",
|
|
62
|
+
left: modalLeft,
|
|
63
|
+
top: modalTop,
|
|
64
|
+
width: modalWidth,
|
|
65
|
+
height: modalHeight,
|
|
66
|
+
backgroundColor: theme.bg,
|
|
67
|
+
borderStyle: "single",
|
|
68
|
+
borderColor: theme.accent,
|
|
69
|
+
border: true,
|
|
70
|
+
flexDirection: "column",
|
|
71
|
+
zIndex: 101,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const titleLabel = new TextRenderable(renderer, {
|
|
75
|
+
id: "modal-title-label",
|
|
76
|
+
content: "Title:",
|
|
77
|
+
fg: theme.fg,
|
|
78
|
+
height: 1,
|
|
79
|
+
marginLeft: 1,
|
|
80
|
+
marginTop: 1,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.titleInput = new InputRenderable(renderer, {
|
|
84
|
+
id: "modal-title-input",
|
|
85
|
+
width: modalWidth - 4,
|
|
86
|
+
height: 1,
|
|
87
|
+
placeholder: "Enter task title...",
|
|
88
|
+
backgroundColor: theme.border,
|
|
89
|
+
focusedBackgroundColor: theme.selectedBg,
|
|
90
|
+
textColor: theme.fg,
|
|
91
|
+
focusedTextColor: theme.selectedFg,
|
|
92
|
+
placeholderColor: theme.muted,
|
|
93
|
+
cursorColor: theme.accent,
|
|
94
|
+
marginLeft: 1,
|
|
95
|
+
marginTop: 1,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const priorityLabel = new TextRenderable(renderer, {
|
|
99
|
+
id: "modal-priority-label",
|
|
100
|
+
content: "Priority:",
|
|
101
|
+
fg: theme.fg,
|
|
102
|
+
height: 1,
|
|
103
|
+
marginLeft: 1,
|
|
104
|
+
marginTop: 1,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
this.prioritySelect = new TabSelectRenderable(renderer, {
|
|
108
|
+
id: "modal-priority-select",
|
|
109
|
+
width: modalWidth - 4,
|
|
110
|
+
height: 3,
|
|
111
|
+
options: [
|
|
112
|
+
{ name: "None", description: "", value: "none" },
|
|
113
|
+
{ name: "Low", description: "", value: "low" },
|
|
114
|
+
{ name: "Med", description: "", value: "medium" },
|
|
115
|
+
{ name: "High", description: "", value: "high" },
|
|
116
|
+
{ name: "Urgent", description: "", value: "urgent" },
|
|
117
|
+
],
|
|
118
|
+
tabWidth: 8,
|
|
119
|
+
showDescription: false,
|
|
120
|
+
backgroundColor: theme.bg,
|
|
121
|
+
focusedBackgroundColor: theme.bg,
|
|
122
|
+
textColor: theme.muted,
|
|
123
|
+
focusedTextColor: theme.fg,
|
|
124
|
+
selectedBackgroundColor: theme.bg,
|
|
125
|
+
selectedTextColor: theme.accent,
|
|
126
|
+
showUnderline: true,
|
|
127
|
+
showScrollArrows: false,
|
|
128
|
+
marginLeft: 1,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const statusLabel = new TextRenderable(renderer, {
|
|
132
|
+
id: "modal-status-label",
|
|
133
|
+
content: "Status:",
|
|
134
|
+
fg: theme.fg,
|
|
135
|
+
height: 1,
|
|
136
|
+
marginLeft: 1,
|
|
137
|
+
marginTop: 1,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
this.statusSelect = new TabSelectRenderable(renderer, {
|
|
141
|
+
id: "modal-status-select",
|
|
142
|
+
width: modalWidth - 4,
|
|
143
|
+
height: 3,
|
|
144
|
+
options: [
|
|
145
|
+
{ name: "Todo", description: "", value: "todo" },
|
|
146
|
+
{ name: "In Progress", description: "", value: "inProgress" },
|
|
147
|
+
{ name: "Done", description: "", value: "done" },
|
|
148
|
+
{ name: "Cancelled", description: "", value: "cancelled" },
|
|
149
|
+
],
|
|
150
|
+
tabWidth: 10,
|
|
151
|
+
showDescription: false,
|
|
152
|
+
backgroundColor: theme.bg,
|
|
153
|
+
focusedBackgroundColor: theme.bg,
|
|
154
|
+
textColor: theme.muted,
|
|
155
|
+
focusedTextColor: theme.fg,
|
|
156
|
+
selectedBackgroundColor: theme.bg,
|
|
157
|
+
selectedTextColor: theme.accent,
|
|
158
|
+
showUnderline: true,
|
|
159
|
+
showScrollArrows: false,
|
|
160
|
+
marginLeft: 1,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const footer = new TextRenderable(renderer, {
|
|
164
|
+
id: "modal-footer",
|
|
165
|
+
content: "Tab:switch fields Enter:save Esc:cancel",
|
|
166
|
+
fg: theme.muted,
|
|
167
|
+
height: 1,
|
|
168
|
+
marginLeft: 1,
|
|
169
|
+
marginTop: 1,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
this.modal.add(titleLabel);
|
|
173
|
+
this.modal.add(this.titleInput);
|
|
174
|
+
this.modal.add(priorityLabel);
|
|
175
|
+
this.modal.add(this.prioritySelect);
|
|
176
|
+
this.modal.add(statusLabel);
|
|
177
|
+
this.modal.add(this.statusSelect);
|
|
178
|
+
this.modal.add(footer);
|
|
179
|
+
|
|
180
|
+
this.overlay.visible = false;
|
|
181
|
+
this.modal.visible = false;
|
|
182
|
+
|
|
183
|
+
renderer.root.add(this.overlay);
|
|
184
|
+
renderer.root.add(this.modal);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
show(mode: ModalMode, task?: Task): Promise<TaskModalResult | null> {
|
|
188
|
+
return new Promise((resolve) => {
|
|
189
|
+
this.mode = mode;
|
|
190
|
+
this.initialTask = task;
|
|
191
|
+
|
|
192
|
+
if (task) {
|
|
193
|
+
this.titleInput.value = task.title;
|
|
194
|
+
const priorityIndex = [
|
|
195
|
+
"none",
|
|
196
|
+
"low",
|
|
197
|
+
"medium",
|
|
198
|
+
"high",
|
|
199
|
+
"urgent",
|
|
200
|
+
].indexOf(task.priority);
|
|
201
|
+
if (priorityIndex >= 0) {
|
|
202
|
+
this.prioritySelect.setSelectedIndex(priorityIndex);
|
|
203
|
+
}
|
|
204
|
+
const statusIndex = ["todo", "inProgress", "done", "cancelled"].indexOf(
|
|
205
|
+
task.status,
|
|
206
|
+
);
|
|
207
|
+
if (statusIndex >= 0) {
|
|
208
|
+
this.statusSelect.setSelectedIndex(statusIndex);
|
|
209
|
+
}
|
|
210
|
+
this.titleInput.cursorPosition = task.title.length;
|
|
211
|
+
} else {
|
|
212
|
+
this.titleInput.value = "";
|
|
213
|
+
this.prioritySelect.setSelectedIndex(0);
|
|
214
|
+
this.statusSelect.setSelectedIndex(0);
|
|
215
|
+
this.titleInput.cursorPosition = 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
this.overlay.visible = true;
|
|
219
|
+
this.modal.visible = true;
|
|
220
|
+
this.focusIndex = 0;
|
|
221
|
+
|
|
222
|
+
queueMicrotask(() => {
|
|
223
|
+
this.updateFocus();
|
|
224
|
+
this.renderer.keyInput.on("keypress", this.boundKeyHandler);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
this.onSubmit = (result) => {
|
|
228
|
+
this.hide();
|
|
229
|
+
resolve(result);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
this.onCancel = () => {
|
|
233
|
+
this.hide();
|
|
234
|
+
resolve(null);
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private hide() {
|
|
240
|
+
this.renderer.keyInput.off("keypress", this.boundKeyHandler);
|
|
241
|
+
this.overlay.visible = false;
|
|
242
|
+
this.modal.visible = false;
|
|
243
|
+
this.titleInput.blur();
|
|
244
|
+
this.prioritySelect.blur();
|
|
245
|
+
this.statusSelect.blur();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private updateFocus() {
|
|
249
|
+
if (this.focusIndex === 0) {
|
|
250
|
+
this.titleInput.focus();
|
|
251
|
+
this.prioritySelect.blur();
|
|
252
|
+
this.statusSelect.blur();
|
|
253
|
+
} else if (this.focusIndex === 1) {
|
|
254
|
+
this.titleInput.blur();
|
|
255
|
+
this.prioritySelect.focus();
|
|
256
|
+
this.statusSelect.blur();
|
|
257
|
+
} else {
|
|
258
|
+
this.titleInput.blur();
|
|
259
|
+
this.prioritySelect.blur();
|
|
260
|
+
this.statusSelect.focus();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private handleKeyPress(key: KeyEvent) {
|
|
265
|
+
if (key.name === "escape") {
|
|
266
|
+
this.onCancel?.();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (key.name === "tab") {
|
|
271
|
+
this.focusIndex = (this.focusIndex + 1) % 3;
|
|
272
|
+
this.updateFocus();
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (key.name === "return" || key.name === "enter") {
|
|
277
|
+
const title = this.titleInput.value.trim();
|
|
278
|
+
if (!title) return;
|
|
279
|
+
|
|
280
|
+
const priorityOption = this.prioritySelect.getSelectedOption();
|
|
281
|
+
const priority = (priorityOption?.value as Task["priority"]) || "none";
|
|
282
|
+
|
|
283
|
+
const statusOption = this.statusSelect.getSelectedOption();
|
|
284
|
+
const status = (statusOption?.value as Task["status"]) || "todo";
|
|
285
|
+
|
|
286
|
+
this.onSubmit?.({ title, priority, status });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
destroy() {
|
|
291
|
+
this.renderer.keyInput.off("keypress", this.boundKeyHandler);
|
|
292
|
+
this.overlay.destroy();
|
|
293
|
+
this.modal.destroy();
|
|
294
|
+
}
|
|
295
|
+
}
|
package/src/ui/theme.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { CliRenderer } from "@opentui/core";
|
|
2
|
+
import { flavors } from "@catppuccin/palette";
|
|
3
|
+
import type { ThemeConfig } from "../schema";
|
|
4
|
+
|
|
5
|
+
export interface Theme {
|
|
6
|
+
bg: string;
|
|
7
|
+
fg: string;
|
|
8
|
+
accent: string;
|
|
9
|
+
muted: string;
|
|
10
|
+
error: string;
|
|
11
|
+
success: string;
|
|
12
|
+
warning: string;
|
|
13
|
+
border: string;
|
|
14
|
+
selectedBg: string;
|
|
15
|
+
selectedFg: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function luminance(hex: string): number {
|
|
19
|
+
const rgb = parseInt(hex.slice(1), 16);
|
|
20
|
+
const r = ((rgb >> 16) & 0xff) / 255;
|
|
21
|
+
const g = ((rgb >> 8) & 0xff) / 255;
|
|
22
|
+
const b = (rgb & 0xff) / 255;
|
|
23
|
+
return 0.299 * r + 0.587 * g + 0.114 * b;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function detectDarkMode(renderer: CliRenderer): Promise<boolean> {
|
|
27
|
+
try {
|
|
28
|
+
const palette = await renderer.getPalette();
|
|
29
|
+
const bg = (palette as any)?.defaultBackground;
|
|
30
|
+
if (!bg) return true;
|
|
31
|
+
return luminance(bg) < 0.5;
|
|
32
|
+
} catch {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function getTheme(renderer: CliRenderer, config: ThemeConfig): Promise<Theme> {
|
|
38
|
+
if (config.mode === "catppuccin") {
|
|
39
|
+
const flavor = flavors[config.flavor];
|
|
40
|
+
return {
|
|
41
|
+
bg: flavor.colors.base.hex,
|
|
42
|
+
fg: flavor.colors.text.hex,
|
|
43
|
+
accent: flavor.colors.blue.hex,
|
|
44
|
+
muted: flavor.colors.overlay0.hex,
|
|
45
|
+
error: flavor.colors.red.hex,
|
|
46
|
+
success: flavor.colors.green.hex,
|
|
47
|
+
warning: flavor.colors.yellow.hex,
|
|
48
|
+
border: flavor.colors.surface1.hex,
|
|
49
|
+
selectedBg: flavor.colors.blue.hex,
|
|
50
|
+
selectedFg: flavor.colors.base.hex,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (config.mode === "system") {
|
|
55
|
+
const palette = await renderer.getPalette();
|
|
56
|
+
const bg = (palette as any)?.defaultBackground || "#000000";
|
|
57
|
+
const fg = (palette as any)?.defaultForeground || "#ffffff";
|
|
58
|
+
return {
|
|
59
|
+
bg,
|
|
60
|
+
fg,
|
|
61
|
+
accent: "#00aaff",
|
|
62
|
+
muted: "#888888",
|
|
63
|
+
error: "#ff5555",
|
|
64
|
+
success: "#50fa7b",
|
|
65
|
+
warning: "#ffb86c",
|
|
66
|
+
border: "#44475a",
|
|
67
|
+
selectedBg: "#00aaff",
|
|
68
|
+
selectedFg: "#000000",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const isDark = await detectDarkMode(renderer);
|
|
73
|
+
const flavor = isDark ? flavors.mocha : flavors.latte;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
bg: flavor.colors.base.hex,
|
|
77
|
+
fg: flavor.colors.text.hex,
|
|
78
|
+
accent: flavor.colors.blue.hex,
|
|
79
|
+
muted: flavor.colors.overlay0.hex,
|
|
80
|
+
error: flavor.colors.red.hex,
|
|
81
|
+
success: flavor.colors.green.hex,
|
|
82
|
+
warning: flavor.colors.yellow.hex,
|
|
83
|
+
border: flavor.colors.surface1.hex,
|
|
84
|
+
selectedBg: flavor.colors.blue.hex,
|
|
85
|
+
selectedFg: flavor.colors.base.hex,
|
|
86
|
+
};
|
|
87
|
+
}
|