@eiei114/pi-sub-bar 1.5.1
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/CHANGELOG.md +201 -0
- package/README.md +200 -0
- package/index.ts +1103 -0
- package/package.json +39 -0
- package/src/core-settings.ts +25 -0
- package/src/dividers.ts +48 -0
- package/src/errors.ts +71 -0
- package/src/formatting.ts +937 -0
- package/src/paths.ts +21 -0
- package/src/providers/extras.ts +21 -0
- package/src/providers/metadata.ts +199 -0
- package/src/providers/settings.ts +359 -0
- package/src/providers/windows.ts +23 -0
- package/src/settings/display.ts +786 -0
- package/src/settings/menu.ts +183 -0
- package/src/settings/themes.ts +378 -0
- package/src/settings/ui.ts +1388 -0
- package/src/settings-types.ts +651 -0
- package/src/settings-ui.ts +5 -0
- package/src/settings.ts +176 -0
- package/src/share.ts +75 -0
- package/src/status.ts +103 -0
- package/src/storage.ts +61 -0
- package/src/types.ts +25 -0
- package/src/ui/keybindings.ts +92 -0
- package/src/ui/settings-list.ts +304 -0
- package/src/usage/types.ts +5 -0
- package/src/utils.ts +42 -0
- package/test/all.test.ts +6 -0
- package/test/dividers.test.ts +34 -0
- package/test/formatting.test.ts +437 -0
- package/test/keybindings.test.ts +59 -0
- package/test/providers.test.ts +42 -0
- package/test/settings.test.ts +336 -0
- package/test/status.test.ts +27 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display settings UI helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { SettingItem } from "@mariozechner/pi-tui";
|
|
6
|
+
import type {
|
|
7
|
+
Settings,
|
|
8
|
+
BarStyle,
|
|
9
|
+
BarType,
|
|
10
|
+
ColorScheme,
|
|
11
|
+
BarCharacter,
|
|
12
|
+
DividerCharacter,
|
|
13
|
+
WidgetWrapping,
|
|
14
|
+
DisplayAlignment,
|
|
15
|
+
BarWidth,
|
|
16
|
+
DividerBlanks,
|
|
17
|
+
ProviderLabel,
|
|
18
|
+
BaseTextColor,
|
|
19
|
+
WidgetBackgroundColor,
|
|
20
|
+
ResetTimeFormat,
|
|
21
|
+
ResetTimerContainment,
|
|
22
|
+
StatusIndicatorMode,
|
|
23
|
+
StatusIconPack,
|
|
24
|
+
DividerColor,
|
|
25
|
+
UsageColorTargets,
|
|
26
|
+
WidgetPlacement,
|
|
27
|
+
} from "../settings-types.js";
|
|
28
|
+
import {
|
|
29
|
+
BASE_COLOR_OPTIONS,
|
|
30
|
+
WIDGET_BACKGROUND_OPTIONS,
|
|
31
|
+
DIVIDER_COLOR_OPTIONS,
|
|
32
|
+
normalizeBaseTextColor,
|
|
33
|
+
normalizeBackgroundColor,
|
|
34
|
+
normalizeDividerColor,
|
|
35
|
+
} from "../settings-types.js";
|
|
36
|
+
import { CUSTOM_OPTION } from "../ui/settings-list.js";
|
|
37
|
+
|
|
38
|
+
function isStatusLinePlacement(settings: Settings): boolean {
|
|
39
|
+
return (settings.display.widgetPlacement ?? "belowEditor") === "status";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildDisplayLayoutItems(settings: Settings): SettingItem[] {
|
|
43
|
+
const statusPlacement = isStatusLinePlacement(settings);
|
|
44
|
+
const items: SettingItem[] = [
|
|
45
|
+
{
|
|
46
|
+
id: "widgetPlacement",
|
|
47
|
+
label: "Widget Placement",
|
|
48
|
+
currentValue: settings.display.widgetPlacement ?? "belowEditor",
|
|
49
|
+
values: ["aboveEditor", "belowEditor", "status"] as WidgetPlacement[],
|
|
50
|
+
description: "Show as widget above/below editor (3 lines) or compact in footer status line (1 line).",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "showContextBar",
|
|
54
|
+
label: "Show Context Bar",
|
|
55
|
+
currentValue: settings.display.showContextBar ? "on" : "off",
|
|
56
|
+
values: ["on", "off"],
|
|
57
|
+
description: "Show context window usage as leftmost progress bar.",
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
if (!statusPlacement) {
|
|
62
|
+
items.push(
|
|
63
|
+
{
|
|
64
|
+
id: "alignment",
|
|
65
|
+
label: "Alignment",
|
|
66
|
+
currentValue: settings.display.alignment,
|
|
67
|
+
values: ["left", "center", "right", "split"] as DisplayAlignment[],
|
|
68
|
+
description: "Align the usage line inside the widget.",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "overflow",
|
|
72
|
+
label: "Overflow",
|
|
73
|
+
currentValue: settings.display.overflow,
|
|
74
|
+
values: ["truncate", "wrap"] as WidgetWrapping[],
|
|
75
|
+
description: "Wrap the usage line or truncate with ellipsis (requires bar width ≠ fill and alignment ≠ split).",
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
items.push({
|
|
81
|
+
id: "paddingLeft",
|
|
82
|
+
label: "Padding Left",
|
|
83
|
+
currentValue: String(settings.display.paddingLeft ?? 0),
|
|
84
|
+
values: ["0", "1", "2", "3", "4", CUSTOM_OPTION],
|
|
85
|
+
description: statusPlacement
|
|
86
|
+
? "Add left padding inside the compact status line."
|
|
87
|
+
: "Add left padding inside the widget.",
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (!statusPlacement) {
|
|
91
|
+
items.push({
|
|
92
|
+
id: "paddingRight",
|
|
93
|
+
label: "Padding Right",
|
|
94
|
+
currentValue: String(settings.display.paddingRight ?? 0),
|
|
95
|
+
values: ["0", "1", "2", "3", "4", CUSTOM_OPTION],
|
|
96
|
+
description: "Add right padding inside the widget.",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return items;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function buildDisplayResetItems(settings: Settings): SettingItem[] {
|
|
104
|
+
return [
|
|
105
|
+
{
|
|
106
|
+
id: "resetTimePosition",
|
|
107
|
+
label: "Reset Timer",
|
|
108
|
+
currentValue: settings.display.resetTimePosition,
|
|
109
|
+
values: ["off", "front", "back", "integrated"],
|
|
110
|
+
description: "Where to show the reset timer in each window.",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "resetTimeFormat",
|
|
114
|
+
label: "Reset Timer Format",
|
|
115
|
+
currentValue: settings.display.resetTimeFormat ?? "relative",
|
|
116
|
+
values: ["relative", "datetime"] as ResetTimeFormat[],
|
|
117
|
+
description: "Show relative countdown or reset datetime.",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "resetTimeContainment",
|
|
121
|
+
label: "Reset Timer Containment",
|
|
122
|
+
currentValue: settings.display.resetTimeContainment ?? "()",
|
|
123
|
+
values: ["none", "blank", "()", "[]", "<>", CUSTOM_OPTION] as ResetTimerContainment[],
|
|
124
|
+
description: "Wrapping characters for the reset timer (custom supported).",
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function resolveUsageColorTargets(targets?: UsageColorTargets): UsageColorTargets {
|
|
130
|
+
return {
|
|
131
|
+
title: targets?.title ?? true,
|
|
132
|
+
timer: targets?.timer ?? true,
|
|
133
|
+
bar: targets?.bar ?? true,
|
|
134
|
+
usageLabel: targets?.usageLabel ?? true,
|
|
135
|
+
status: targets?.status ?? true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function formatUsageColorTargetsSummary(targets?: UsageColorTargets): string {
|
|
140
|
+
const resolved = resolveUsageColorTargets(targets);
|
|
141
|
+
const enabled = [
|
|
142
|
+
resolved.title ? "Title" : null,
|
|
143
|
+
resolved.timer ? "Timer" : null,
|
|
144
|
+
resolved.bar ? "Bar" : null,
|
|
145
|
+
resolved.usageLabel ? "Usage label" : null,
|
|
146
|
+
resolved.status ? "Status" : null,
|
|
147
|
+
].filter(Boolean) as string[];
|
|
148
|
+
if (enabled.length === 0) return "off";
|
|
149
|
+
if (enabled.length === 5) return "all";
|
|
150
|
+
return enabled.join(", ");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function buildUsageColorTargetItems(settings: Settings): SettingItem[] {
|
|
154
|
+
const targets = resolveUsageColorTargets(settings.display.usageColorTargets);
|
|
155
|
+
return [
|
|
156
|
+
{
|
|
157
|
+
id: "usageColorTitle",
|
|
158
|
+
label: "Title",
|
|
159
|
+
currentValue: targets.title ? "on" : "off",
|
|
160
|
+
values: ["on", "off"],
|
|
161
|
+
description: "Color the window title by usage.",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "usageColorTimer",
|
|
165
|
+
label: "Timer",
|
|
166
|
+
currentValue: targets.timer ? "on" : "off",
|
|
167
|
+
values: ["on", "off"],
|
|
168
|
+
description: "Color the reset timer by usage.",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: "usageColorBar",
|
|
172
|
+
label: "Bar",
|
|
173
|
+
currentValue: targets.bar ? "on" : "off",
|
|
174
|
+
values: ["on", "off"],
|
|
175
|
+
description: "Color the usage bar by usage.",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "usageColorLabel",
|
|
179
|
+
label: "Usage label",
|
|
180
|
+
currentValue: targets.usageLabel ? "on" : "off",
|
|
181
|
+
values: ["on", "off"],
|
|
182
|
+
description: "Color the percentage text by usage.",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: "usageColorStatus",
|
|
186
|
+
label: "Status",
|
|
187
|
+
currentValue: targets.status ? "on" : "off",
|
|
188
|
+
values: ["on", "off"],
|
|
189
|
+
description: "Color the status indicator by status.",
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function buildDisplayColorItems(settings: Settings): SettingItem[] {
|
|
195
|
+
return [
|
|
196
|
+
{
|
|
197
|
+
id: "baseTextColor",
|
|
198
|
+
label: "Base Color",
|
|
199
|
+
currentValue: normalizeBaseTextColor(settings.display.baseTextColor),
|
|
200
|
+
values: [...BASE_COLOR_OPTIONS] as BaseTextColor[],
|
|
201
|
+
description: "Base color for neutral labels and dividers.",
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "backgroundColor",
|
|
205
|
+
label: "Background Color",
|
|
206
|
+
currentValue: normalizeBackgroundColor(settings.display.backgroundColor),
|
|
207
|
+
values: [...WIDGET_BACKGROUND_OPTIONS] as WidgetBackgroundColor[],
|
|
208
|
+
description: "Background color for the widget line.",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: "colorScheme",
|
|
212
|
+
label: "Color Indicator Scheme",
|
|
213
|
+
currentValue: settings.display.colorScheme,
|
|
214
|
+
values: [
|
|
215
|
+
"base-warning-error",
|
|
216
|
+
"success-base-warning-error",
|
|
217
|
+
"monochrome",
|
|
218
|
+
] as ColorScheme[],
|
|
219
|
+
description: "Choose how usage/status indicators are color-coded.",
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "usageColorTargets",
|
|
223
|
+
label: "Color Indicator Targets",
|
|
224
|
+
currentValue: formatUsageColorTargetsSummary(settings.display.usageColorTargets),
|
|
225
|
+
description: "Pick which elements use the indicator colors.",
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "errorThreshold",
|
|
229
|
+
label: "Error Threshold (%)",
|
|
230
|
+
currentValue: String(settings.display.errorThreshold),
|
|
231
|
+
values: ["10", "15", "20", "25", "30", "35", "40", CUSTOM_OPTION],
|
|
232
|
+
description: "Percent remaining below which usage is red.",
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: "warningThreshold",
|
|
236
|
+
label: "Warning Threshold (%)",
|
|
237
|
+
currentValue: String(settings.display.warningThreshold),
|
|
238
|
+
values: ["30", "40", "50", "60", "70", CUSTOM_OPTION],
|
|
239
|
+
description: "Percent remaining below which usage is yellow.",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "successThreshold",
|
|
243
|
+
label: "Success Threshold (%)",
|
|
244
|
+
currentValue: String(settings.display.successThreshold),
|
|
245
|
+
values: ["60", "70", "75", "80", "90", CUSTOM_OPTION],
|
|
246
|
+
description: "Percent remaining above which usage is green.",
|
|
247
|
+
},
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function buildDisplayBarItems(settings: Settings): SettingItem[] {
|
|
252
|
+
const statusPlacement = isStatusLinePlacement(settings);
|
|
253
|
+
const barWidthValues = statusPlacement
|
|
254
|
+
? (["1", "4", "6", "8", "10", "12", CUSTOM_OPTION] as string[])
|
|
255
|
+
: (["1", "4", "6", "8", "10", "12", "fill", CUSTOM_OPTION] as string[]);
|
|
256
|
+
const items: SettingItem[] = [
|
|
257
|
+
{
|
|
258
|
+
id: "barType",
|
|
259
|
+
label: "Bar Type",
|
|
260
|
+
currentValue: settings.display.barType,
|
|
261
|
+
values: [
|
|
262
|
+
"horizontal-bar",
|
|
263
|
+
"horizontal-single",
|
|
264
|
+
"vertical",
|
|
265
|
+
"braille",
|
|
266
|
+
"shade",
|
|
267
|
+
] as BarType[],
|
|
268
|
+
description: "Choose the bar glyph style for usage.",
|
|
269
|
+
},
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
if (settings.display.barType === "horizontal-bar") {
|
|
273
|
+
items.push({
|
|
274
|
+
id: "barCharacter",
|
|
275
|
+
label: "H. Bar Character",
|
|
276
|
+
currentValue: settings.display.barCharacter,
|
|
277
|
+
values: ["light", "heavy", "double", "block", CUSTOM_OPTION],
|
|
278
|
+
description: "Custom bar character(s), set 1 or 2 (fill/empty)",
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
items.push(
|
|
283
|
+
{
|
|
284
|
+
id: "barWidth",
|
|
285
|
+
label: "Bar Width",
|
|
286
|
+
currentValue: String(settings.display.barWidth),
|
|
287
|
+
values: barWidthValues,
|
|
288
|
+
description: statusPlacement
|
|
289
|
+
? "Set the bar width (fill is unavailable in status-line placement)."
|
|
290
|
+
: "Set the bar width or fill available space.",
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: "containBar",
|
|
294
|
+
label: "Contain Bar",
|
|
295
|
+
currentValue: settings.display.containBar ? "on" : "off",
|
|
296
|
+
values: ["on", "off"],
|
|
297
|
+
description: "Wrap the bar with ▕ and ▏ caps.",
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
if (settings.display.barType === "braille") {
|
|
302
|
+
items.push(
|
|
303
|
+
{
|
|
304
|
+
id: "brailleFillEmpty",
|
|
305
|
+
label: "Braille Empty Fill",
|
|
306
|
+
currentValue: settings.display.brailleFillEmpty ? "on" : "off",
|
|
307
|
+
values: ["on", "off"],
|
|
308
|
+
description: "Fill empty braille cells with dim blocks.",
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: "brailleFullBlocks",
|
|
312
|
+
label: "Braille Full Blocks",
|
|
313
|
+
currentValue: settings.display.brailleFullBlocks ? "on" : "off",
|
|
314
|
+
values: ["on", "off"],
|
|
315
|
+
description: "Use full 8-dot braille blocks for filled segments.",
|
|
316
|
+
},
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
items.push({
|
|
321
|
+
id: "barStyle",
|
|
322
|
+
label: "Bar Style",
|
|
323
|
+
currentValue: settings.display.barStyle,
|
|
324
|
+
values: ["bar", "percentage", "both"] as BarStyle[],
|
|
325
|
+
description: "Show bar, percentage, or both.",
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return items;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function buildDisplayProviderItems(settings: Settings): SettingItem[] {
|
|
332
|
+
return [
|
|
333
|
+
{
|
|
334
|
+
id: "showProviderName",
|
|
335
|
+
label: "Show Provider Name",
|
|
336
|
+
currentValue: settings.display.showProviderName ? "on" : "off",
|
|
337
|
+
values: ["on", "off"],
|
|
338
|
+
description: "Toggle the provider name prefix.",
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: "providerLabel",
|
|
342
|
+
label: "Provider Label",
|
|
343
|
+
currentValue: settings.display.providerLabel,
|
|
344
|
+
values: ["none", "plan", "subscription", "sub", CUSTOM_OPTION] as (ProviderLabel | typeof CUSTOM_OPTION)[],
|
|
345
|
+
description: "Suffix appended after the provider name.",
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: "providerLabelColon",
|
|
349
|
+
label: "Provider Label Colon",
|
|
350
|
+
currentValue: settings.display.providerLabelColon ? "on" : "off",
|
|
351
|
+
values: ["on", "off"],
|
|
352
|
+
description: "Show a colon after the provider label.",
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: "providerLabelBold",
|
|
356
|
+
label: "Show in Bold",
|
|
357
|
+
currentValue: settings.display.providerLabelBold ? "on" : "off",
|
|
358
|
+
values: ["on", "off"],
|
|
359
|
+
description: "Bold the provider name and colon.",
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
id: "showUsageLabels",
|
|
363
|
+
label: "Show Usage Labels",
|
|
364
|
+
currentValue: settings.display.showUsageLabels ? "on" : "off",
|
|
365
|
+
values: ["on", "off"],
|
|
366
|
+
description: "Show “used/rem.” labels after percentages.",
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: "showWindowTitle",
|
|
370
|
+
label: "Show Title",
|
|
371
|
+
currentValue: settings.display.showWindowTitle ? "on" : "off",
|
|
372
|
+
values: ["on", "off"],
|
|
373
|
+
description: "Show window titles like 5h, Week, etc.",
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: "boldWindowTitle",
|
|
377
|
+
label: "Bold Title",
|
|
378
|
+
currentValue: settings.display.boldWindowTitle ? "on" : "off",
|
|
379
|
+
values: ["on", "off"],
|
|
380
|
+
description: "Bold window titles like 5h, Week, etc.",
|
|
381
|
+
},
|
|
382
|
+
];
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const STATUS_ICON_PACK_PREVIEW = {
|
|
386
|
+
minimal: "minimal (✓ ⚠ × ?)",
|
|
387
|
+
emoji: "emoji (✅ ⚠️ 🔴 ❓)",
|
|
388
|
+
faces: "faces (😎 😳 😵 🤔)",
|
|
389
|
+
} as const;
|
|
390
|
+
|
|
391
|
+
const STATUS_ICON_FACES_PRESET = "😎😳😵🤔";
|
|
392
|
+
|
|
393
|
+
const STATUS_ICON_CUSTOM_FALLBACK = ["✓", "⚠", "×", "?"];
|
|
394
|
+
const STATUS_ICON_CUSTOM_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
395
|
+
|
|
396
|
+
function resolveCustomStatusIcons(value?: string): [string, string, string, string] {
|
|
397
|
+
if (!value) return STATUS_ICON_CUSTOM_FALLBACK as [string, string, string, string];
|
|
398
|
+
const segments = Array.from(STATUS_ICON_CUSTOM_SEGMENTER.segment(value), (entry) => entry.segment)
|
|
399
|
+
.map((segment) => segment.trim())
|
|
400
|
+
.filter(Boolean);
|
|
401
|
+
if (segments.length < 3) return STATUS_ICON_CUSTOM_FALLBACK as [string, string, string, string];
|
|
402
|
+
if (segments.length === 3) {
|
|
403
|
+
return [segments[0], segments[1], segments[2], STATUS_ICON_CUSTOM_FALLBACK[3]] as [string, string, string, string];
|
|
404
|
+
}
|
|
405
|
+
return [segments[0], segments[1], segments[2], segments[3]] as [string, string, string, string];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function formatCustomStatusIcons(value?: string): string {
|
|
409
|
+
return resolveCustomStatusIcons(value).join(" ");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function formatStatusIconPack(pack: Exclude<StatusIconPack, "custom">): string {
|
|
413
|
+
return STATUS_ICON_PACK_PREVIEW[pack] ?? pack;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function parseStatusIconPack(value: string): StatusIconPack {
|
|
417
|
+
if (value.startsWith("minimal")) return "minimal";
|
|
418
|
+
if (value.startsWith("emoji")) return "emoji";
|
|
419
|
+
return "emoji";
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export function buildDisplayStatusItems(settings: Settings): SettingItem[] {
|
|
423
|
+
const rawMode = settings.display.statusIndicatorMode ?? "icon";
|
|
424
|
+
const mode: StatusIndicatorMode = rawMode === "text" || rawMode === "icon+text" || rawMode === "icon"
|
|
425
|
+
? rawMode
|
|
426
|
+
: "icon";
|
|
427
|
+
const items: SettingItem[] = [
|
|
428
|
+
{
|
|
429
|
+
id: "statusIndicatorMode",
|
|
430
|
+
label: "Status Mode",
|
|
431
|
+
currentValue: mode,
|
|
432
|
+
values: ["icon", "text", "icon+text"] as StatusIndicatorMode[],
|
|
433
|
+
description: "Use icons, text, or both for status indicators.",
|
|
434
|
+
},
|
|
435
|
+
];
|
|
436
|
+
|
|
437
|
+
if (mode === "icon" || mode === "icon+text") {
|
|
438
|
+
const pack = settings.display.statusIconPack ?? "emoji";
|
|
439
|
+
const customIcons = settings.display.statusIconCustom;
|
|
440
|
+
items.push({
|
|
441
|
+
id: "statusIconPack",
|
|
442
|
+
label: "Status Icon Pack",
|
|
443
|
+
currentValue: pack === "custom" ? formatCustomStatusIcons(customIcons) : formatStatusIconPack(pack),
|
|
444
|
+
values: [
|
|
445
|
+
formatStatusIconPack("minimal"),
|
|
446
|
+
formatStatusIconPack("emoji"),
|
|
447
|
+
STATUS_ICON_PACK_PREVIEW.faces,
|
|
448
|
+
CUSTOM_OPTION,
|
|
449
|
+
],
|
|
450
|
+
description: "Pick the icon set used for status indicators. Choose custom to edit icons (OK/warn/error/unknown).",
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
items.push(
|
|
455
|
+
{
|
|
456
|
+
id: "statusDismissOk",
|
|
457
|
+
label: "Dismiss Operational Status",
|
|
458
|
+
currentValue: settings.display.statusDismissOk ? "on" : "off",
|
|
459
|
+
values: ["on", "off"],
|
|
460
|
+
description: "Hide status indicators when there are no incidents.",
|
|
461
|
+
}
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
return items;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function buildDisplayDividerItems(settings: Settings): SettingItem[] {
|
|
468
|
+
const statusPlacement = isStatusLinePlacement(settings);
|
|
469
|
+
const commonItems: SettingItem[] = [
|
|
470
|
+
{
|
|
471
|
+
id: "dividerCharacter",
|
|
472
|
+
label: "Divider Character",
|
|
473
|
+
currentValue: settings.display.dividerCharacter,
|
|
474
|
+
values: ["none", "blank", "|", "│", "┃", "┆", "┇", "║", "•", "●", "○", "◇", CUSTOM_OPTION] as DividerCharacter[],
|
|
475
|
+
description: "Choose the divider glyph between windows.",
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
id: "dividerColor",
|
|
479
|
+
label: "Divider Color",
|
|
480
|
+
currentValue: normalizeDividerColor(settings.display.dividerColor ?? "borderMuted"),
|
|
481
|
+
values: [...DIVIDER_COLOR_OPTIONS] as DividerColor[],
|
|
482
|
+
description: "Color used for divider glyphs and lines.",
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: "statusProviderDivider",
|
|
486
|
+
label: "Status/Provider Divider",
|
|
487
|
+
currentValue: settings.display.statusProviderDivider ? "on" : "off",
|
|
488
|
+
values: ["on", "off"],
|
|
489
|
+
description: "Add a divider between status and provider label.",
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
id: "dividerBlanks",
|
|
493
|
+
label: "Blanks Before/After Divider",
|
|
494
|
+
currentValue: String(settings.display.dividerBlanks),
|
|
495
|
+
values: ["0", "1", "2", "3", "fill", CUSTOM_OPTION],
|
|
496
|
+
description: "Padding around the divider character.",
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
id: "showProviderDivider",
|
|
500
|
+
label: "Show Provider Divider",
|
|
501
|
+
currentValue: settings.display.showProviderDivider ? "on" : "off",
|
|
502
|
+
values: ["on", "off"],
|
|
503
|
+
description: "Show the divider after the provider label.",
|
|
504
|
+
},
|
|
505
|
+
];
|
|
506
|
+
|
|
507
|
+
if (statusPlacement) {
|
|
508
|
+
return [
|
|
509
|
+
...commonItems,
|
|
510
|
+
{
|
|
511
|
+
id: "statusLeadingDivider",
|
|
512
|
+
label: "Status Leading Divider",
|
|
513
|
+
currentValue: settings.display.statusLeadingDivider ? "on" : "off",
|
|
514
|
+
values: ["on", "off"],
|
|
515
|
+
description: "Add a divider before the status-line output.",
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
id: "statusTrailingDivider",
|
|
519
|
+
label: "Status Trailing Divider",
|
|
520
|
+
currentValue: settings.display.statusTrailingDivider ? "on" : "off",
|
|
521
|
+
values: ["on", "off"],
|
|
522
|
+
description: "Add a divider after the status-line output.",
|
|
523
|
+
},
|
|
524
|
+
];
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return [
|
|
528
|
+
...commonItems,
|
|
529
|
+
{
|
|
530
|
+
id: "showTopDivider",
|
|
531
|
+
label: "Show Top Divider",
|
|
532
|
+
currentValue: settings.display.showTopDivider ? "on" : "off",
|
|
533
|
+
values: ["on", "off"],
|
|
534
|
+
description: "Show a divider line above the widget.",
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
id: "showBottomDivider",
|
|
538
|
+
label: "Show Bottom Divider",
|
|
539
|
+
currentValue: settings.display.showBottomDivider ? "on" : "off",
|
|
540
|
+
values: ["on", "off"],
|
|
541
|
+
description: "Show a divider line below the widget.",
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
id: "dividerFooterJoin",
|
|
545
|
+
label: "Connect Dividers",
|
|
546
|
+
currentValue: settings.display.dividerFooterJoin ? "on" : "off",
|
|
547
|
+
values: ["on", "off"],
|
|
548
|
+
description: "Draw reverse-T connectors for top/bottom dividers.",
|
|
549
|
+
},
|
|
550
|
+
];
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function clampNumber(value: number, min: number, max: number): number {
|
|
554
|
+
return Math.min(max, Math.max(min, value));
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function parseClampedNumber(value: string, min: number, max: number): number | null {
|
|
558
|
+
const parsed = Number.parseInt(value, 10);
|
|
559
|
+
if (Number.isNaN(parsed)) return null;
|
|
560
|
+
return clampNumber(parsed, min, max);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function applyDisplayChange(settings: Settings, id: string, value: string): Settings {
|
|
564
|
+
switch (id) {
|
|
565
|
+
case "alignment":
|
|
566
|
+
settings.display.alignment = value as DisplayAlignment;
|
|
567
|
+
break;
|
|
568
|
+
case "barType":
|
|
569
|
+
settings.display.barType = value as BarType;
|
|
570
|
+
break;
|
|
571
|
+
case "barStyle":
|
|
572
|
+
settings.display.barStyle = value as BarStyle;
|
|
573
|
+
break;
|
|
574
|
+
case "barWidth": {
|
|
575
|
+
if (value === "fill") {
|
|
576
|
+
settings.display.barWidth = "fill" as BarWidth;
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
const parsed = parseClampedNumber(value, 0, 100);
|
|
580
|
+
if (parsed !== null) {
|
|
581
|
+
settings.display.barWidth = parsed;
|
|
582
|
+
}
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
case "containBar":
|
|
586
|
+
settings.display.containBar = value === "on";
|
|
587
|
+
break;
|
|
588
|
+
case "barCharacter":
|
|
589
|
+
settings.display.barCharacter = value as BarCharacter;
|
|
590
|
+
break;
|
|
591
|
+
case "brailleFillEmpty":
|
|
592
|
+
settings.display.brailleFillEmpty = value === "on";
|
|
593
|
+
break;
|
|
594
|
+
case "brailleFullBlocks":
|
|
595
|
+
settings.display.brailleFullBlocks = value === "on";
|
|
596
|
+
break;
|
|
597
|
+
case "colorScheme":
|
|
598
|
+
settings.display.colorScheme = value as ColorScheme;
|
|
599
|
+
break;
|
|
600
|
+
case "usageColorTitle":
|
|
601
|
+
settings.display.usageColorTargets = {
|
|
602
|
+
...resolveUsageColorTargets(settings.display.usageColorTargets),
|
|
603
|
+
title: value === "on",
|
|
604
|
+
};
|
|
605
|
+
break;
|
|
606
|
+
case "usageColorTimer":
|
|
607
|
+
settings.display.usageColorTargets = {
|
|
608
|
+
...resolveUsageColorTargets(settings.display.usageColorTargets),
|
|
609
|
+
timer: value === "on",
|
|
610
|
+
};
|
|
611
|
+
break;
|
|
612
|
+
case "usageColorBar":
|
|
613
|
+
settings.display.usageColorTargets = {
|
|
614
|
+
...resolveUsageColorTargets(settings.display.usageColorTargets),
|
|
615
|
+
bar: value === "on",
|
|
616
|
+
};
|
|
617
|
+
break;
|
|
618
|
+
case "usageColorLabel":
|
|
619
|
+
settings.display.usageColorTargets = {
|
|
620
|
+
...resolveUsageColorTargets(settings.display.usageColorTargets),
|
|
621
|
+
usageLabel: value === "on",
|
|
622
|
+
};
|
|
623
|
+
break;
|
|
624
|
+
case "usageColorStatus":
|
|
625
|
+
settings.display.usageColorTargets = {
|
|
626
|
+
...resolveUsageColorTargets(settings.display.usageColorTargets),
|
|
627
|
+
status: value === "on",
|
|
628
|
+
};
|
|
629
|
+
break;
|
|
630
|
+
case "usageColorTargets":
|
|
631
|
+
settings.display.usageColorTargets = resolveUsageColorTargets(settings.display.usageColorTargets);
|
|
632
|
+
break;
|
|
633
|
+
case "resetTimePosition":
|
|
634
|
+
settings.display.resetTimePosition = value as "off" | "front" | "back" | "integrated";
|
|
635
|
+
break;
|
|
636
|
+
case "resetTimeFormat":
|
|
637
|
+
settings.display.resetTimeFormat = value as ResetTimeFormat;
|
|
638
|
+
break;
|
|
639
|
+
case "resetTimeContainment":
|
|
640
|
+
if (value === CUSTOM_OPTION) {
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
settings.display.resetTimeContainment = value as ResetTimerContainment;
|
|
644
|
+
break;
|
|
645
|
+
case "statusIndicatorMode":
|
|
646
|
+
settings.display.statusIndicatorMode = value as StatusIndicatorMode;
|
|
647
|
+
break;
|
|
648
|
+
case "statusIconPack":
|
|
649
|
+
if (value === CUSTOM_OPTION) {
|
|
650
|
+
settings.display.statusIconPack = "custom";
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
if (value.startsWith("minimal") || value.startsWith("emoji")) {
|
|
654
|
+
settings.display.statusIconPack = parseStatusIconPack(value);
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
if (value.startsWith("faces")) {
|
|
658
|
+
settings.display.statusIconCustom = STATUS_ICON_FACES_PRESET;
|
|
659
|
+
settings.display.statusIconPack = "custom";
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
settings.display.statusIconCustom = value;
|
|
663
|
+
settings.display.statusIconPack = "custom";
|
|
664
|
+
break;
|
|
665
|
+
case "statusIconCustom":
|
|
666
|
+
settings.display.statusIconCustom = value;
|
|
667
|
+
settings.display.statusIconPack = "custom";
|
|
668
|
+
break;
|
|
669
|
+
case "statusProviderDivider":
|
|
670
|
+
settings.display.statusProviderDivider = value === "on";
|
|
671
|
+
break;
|
|
672
|
+
case "statusDismissOk":
|
|
673
|
+
settings.display.statusDismissOk = value === "on";
|
|
674
|
+
break;
|
|
675
|
+
case "showProviderName":
|
|
676
|
+
settings.display.showProviderName = value === "on";
|
|
677
|
+
break;
|
|
678
|
+
case "providerLabel":
|
|
679
|
+
settings.display.providerLabel = value as ProviderLabel;
|
|
680
|
+
break;
|
|
681
|
+
case "providerLabelColon":
|
|
682
|
+
settings.display.providerLabelColon = value === "on";
|
|
683
|
+
break;
|
|
684
|
+
case "providerLabelBold":
|
|
685
|
+
settings.display.providerLabelBold = value === "on";
|
|
686
|
+
break;
|
|
687
|
+
case "baseTextColor":
|
|
688
|
+
settings.display.baseTextColor = normalizeBaseTextColor(value);
|
|
689
|
+
break;
|
|
690
|
+
case "backgroundColor":
|
|
691
|
+
settings.display.backgroundColor = normalizeBackgroundColor(value);
|
|
692
|
+
break;
|
|
693
|
+
case "showUsageLabels":
|
|
694
|
+
settings.display.showUsageLabels = value === "on";
|
|
695
|
+
break;
|
|
696
|
+
case "showWindowTitle":
|
|
697
|
+
settings.display.showWindowTitle = value === "on";
|
|
698
|
+
break;
|
|
699
|
+
case "boldWindowTitle":
|
|
700
|
+
settings.display.boldWindowTitle = value === "on";
|
|
701
|
+
break;
|
|
702
|
+
case "widgetPlacement":
|
|
703
|
+
settings.display.widgetPlacement = value as WidgetPlacement;
|
|
704
|
+
break;
|
|
705
|
+
case "showContextBar":
|
|
706
|
+
settings.display.showContextBar = value === "on";
|
|
707
|
+
break;
|
|
708
|
+
case "paddingLeft": {
|
|
709
|
+
const parsed = parseClampedNumber(value, 0, 100);
|
|
710
|
+
if (parsed !== null) {
|
|
711
|
+
settings.display.paddingLeft = parsed;
|
|
712
|
+
}
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
case "paddingRight": {
|
|
716
|
+
const parsed = parseClampedNumber(value, 0, 100);
|
|
717
|
+
if (parsed !== null) {
|
|
718
|
+
settings.display.paddingRight = parsed;
|
|
719
|
+
}
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
case "dividerCharacter":
|
|
723
|
+
settings.display.dividerCharacter = value as DividerCharacter;
|
|
724
|
+
break;
|
|
725
|
+
case "dividerColor":
|
|
726
|
+
settings.display.dividerColor = normalizeDividerColor(value);
|
|
727
|
+
break;
|
|
728
|
+
case "dividerBlanks": {
|
|
729
|
+
if (value === "fill") {
|
|
730
|
+
settings.display.dividerBlanks = "fill" as DividerBlanks;
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
const parsed = parseClampedNumber(value, 0, 100);
|
|
734
|
+
if (parsed !== null) {
|
|
735
|
+
settings.display.dividerBlanks = parsed;
|
|
736
|
+
}
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
case "showProviderDivider":
|
|
740
|
+
settings.display.showProviderDivider = value === "on";
|
|
741
|
+
break;
|
|
742
|
+
case "statusLeadingDivider":
|
|
743
|
+
settings.display.statusLeadingDivider = value === "on";
|
|
744
|
+
break;
|
|
745
|
+
case "statusTrailingDivider":
|
|
746
|
+
settings.display.statusTrailingDivider = value === "on";
|
|
747
|
+
break;
|
|
748
|
+
case "dividerFooterJoin":
|
|
749
|
+
settings.display.dividerFooterJoin = value === "on";
|
|
750
|
+
break;
|
|
751
|
+
case "showTopDivider":
|
|
752
|
+
settings.display.showTopDivider = value === "on";
|
|
753
|
+
break;
|
|
754
|
+
case "showBottomDivider":
|
|
755
|
+
settings.display.showBottomDivider = value === "on";
|
|
756
|
+
break;
|
|
757
|
+
case "overflow":
|
|
758
|
+
settings.display.overflow = value as WidgetWrapping;
|
|
759
|
+
break;
|
|
760
|
+
case "widgetWrapping":
|
|
761
|
+
settings.display.overflow = value as WidgetWrapping;
|
|
762
|
+
break;
|
|
763
|
+
case "errorThreshold": {
|
|
764
|
+
const parsed = parseClampedNumber(value, 0, 100);
|
|
765
|
+
if (parsed !== null) {
|
|
766
|
+
settings.display.errorThreshold = parsed;
|
|
767
|
+
}
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
case "warningThreshold": {
|
|
771
|
+
const parsed = parseClampedNumber(value, 0, 100);
|
|
772
|
+
if (parsed !== null) {
|
|
773
|
+
settings.display.warningThreshold = parsed;
|
|
774
|
+
}
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
case "successThreshold": {
|
|
778
|
+
const parsed = parseClampedNumber(value, 0, 100);
|
|
779
|
+
if (parsed !== null) {
|
|
780
|
+
settings.display.successThreshold = parsed;
|
|
781
|
+
}
|
|
782
|
+
break;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return settings;
|
|
786
|
+
}
|