@aemonge-dev/pi-stardeck 0.5.0 → 0.7.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/README.md +3 -2
- package/docs/architecture.md +8 -5
- package/licenses/README.md +9 -7
- package/package.json +2 -2
- package/packages/footer/README.md +15 -0
- package/packages/footer/index.ts +37 -0
- package/packages/footer/package.json +32 -0
- package/packages/footer/src/protocol.ts +10 -0
- package/packages/footer/src/quota.ts +26 -0
- package/packages/footer/src/telemetry.ts +59 -0
- package/packages/pi-stardeck/README.md +7 -6
- package/packages/pi-stardeck/src/deck.ts +79 -44
- package/packages/status/package.json +1 -1
- package/src/health.ts +0 -1
- package/src/index.ts +3 -4
- package/licenses/pi-fancy-footer-LICENSE +0 -21
- package/packages/pi-fancy-footer-full-palette/LICENSE +0 -21
- package/packages/pi-fancy-footer-full-palette/LOCAL-CHANGES.md +0 -41
- package/packages/pi-fancy-footer-full-palette/README.md +0 -468
- package/packages/pi-fancy-footer-full-palette/package.json +0 -52
- package/packages/pi-fancy-footer-full-palette/src/api.ts +0 -204
- package/packages/pi-fancy-footer-full-palette/src/config-editor.ts +0 -634
- package/packages/pi-fancy-footer-full-palette/src/config.ts +0 -852
- package/packages/pi-fancy-footer-full-palette/src/data-widgets.ts +0 -259
- package/packages/pi-fancy-footer-full-palette/src/git.ts +0 -130
- package/packages/pi-fancy-footer-full-palette/src/index.ts +0 -479
- package/packages/pi-fancy-footer-full-palette/src/layout.ts +0 -309
- package/packages/pi-fancy-footer-full-palette/src/provider-status.ts +0 -1466
- package/packages/pi-fancy-footer-full-palette/src/render.ts +0 -1061
- package/packages/pi-fancy-footer-full-palette/src/shared.ts +0 -903
|
@@ -1,852 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { Type } from "typebox";
|
|
4
|
-
import { Compile } from "typebox/compile";
|
|
5
|
-
import {
|
|
6
|
-
getAgentDir,
|
|
7
|
-
getSettingsListTheme,
|
|
8
|
-
type Theme,
|
|
9
|
-
} from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import {
|
|
11
|
-
Container,
|
|
12
|
-
type SettingItem,
|
|
13
|
-
SettingsList,
|
|
14
|
-
Text,
|
|
15
|
-
} from "@earendil-works/pi-tui";
|
|
16
|
-
import {
|
|
17
|
-
GAUGE_STYLES,
|
|
18
|
-
getGaugeStyle,
|
|
19
|
-
type GaugeStyleDef,
|
|
20
|
-
DEFAULT_FOOTER_CONFIG,
|
|
21
|
-
DEFAULT_PROVIDER_STATUS_CONFIG,
|
|
22
|
-
FOOTER_CONFIG_FILE,
|
|
23
|
-
FOOTER_ICON_FAMILIES,
|
|
24
|
-
FOOTER_MIN_WIDTH_OPTIONS,
|
|
25
|
-
FOOTER_REFRESH_OPTIONS,
|
|
26
|
-
FOOTER_WIDGET_COLORS,
|
|
27
|
-
FOOTER_WIDGET_IDS,
|
|
28
|
-
FOOTER_WIDGET_META,
|
|
29
|
-
MAX_FOOTER_REFRESH_MS,
|
|
30
|
-
MAX_PROVIDER_STATUS_CACHE_TTL_MS,
|
|
31
|
-
MAX_PROVIDER_STATUS_REFRESH_MS,
|
|
32
|
-
MAX_WIDGET_MIN_WIDTH,
|
|
33
|
-
MAX_WIDGET_POSITION,
|
|
34
|
-
MAX_WIDGET_ROW,
|
|
35
|
-
MIN_FOOTER_REFRESH_MS,
|
|
36
|
-
MIN_GAUGE_WIDTH,
|
|
37
|
-
MAX_GAUGE_WIDTH,
|
|
38
|
-
GAUGE_WIDTH_OPTIONS,
|
|
39
|
-
DEFAULT_GAUGE_COLORS,
|
|
40
|
-
MIN_PROVIDER_STATUS_CACHE_TTL_MS,
|
|
41
|
-
MIN_PROVIDER_STATUS_REFRESH_MS,
|
|
42
|
-
PROVIDER_STATUS_DISPLAYS,
|
|
43
|
-
PROVIDER_STATUS_PROVIDER_IDS,
|
|
44
|
-
PROVIDER_STATUS_RESET_MODES,
|
|
45
|
-
type BuiltInFooterWidgetId,
|
|
46
|
-
type FooterConfigSnapshot,
|
|
47
|
-
type FooterWidgetAlign,
|
|
48
|
-
type FooterWidgetColor,
|
|
49
|
-
type FooterWidgetConfigOverride,
|
|
50
|
-
type FooterWidgetFill,
|
|
51
|
-
clampInt,
|
|
52
|
-
getDefaultWidgetIcon,
|
|
53
|
-
isFooterWidgetColor,
|
|
54
|
-
toBoundedNonNegativeInt,
|
|
55
|
-
} from "./shared.ts";
|
|
56
|
-
import {
|
|
57
|
-
resolveDataWidgetIcon,
|
|
58
|
-
type NormalizedFancyFooterDataWidget,
|
|
59
|
-
} from "./data-widgets.ts";
|
|
60
|
-
|
|
61
|
-
const literalUnion = (values: readonly string[]) =>
|
|
62
|
-
Type.Union(values.map((value) => Type.Literal(value)));
|
|
63
|
-
|
|
64
|
-
const footerWidgetColorSchema = literalUnion(FOOTER_WIDGET_COLORS);
|
|
65
|
-
const footerIconFamilySchema = literalUnion(FOOTER_ICON_FAMILIES);
|
|
66
|
-
const gaugeStyleSchema = literalUnion(GAUGE_STYLES.map((style) => style.label));
|
|
67
|
-
const footerWidgetAlignSchema = literalUnion(["left", "middle", "right"]);
|
|
68
|
-
const footerWidgetFillSchema = literalUnion(["none", "grow"]);
|
|
69
|
-
const footerWidgetIconModeSchema = literalUnion(["default", "hide"]);
|
|
70
|
-
const providerStatusProviderSchema = literalUnion(PROVIDER_STATUS_PROVIDER_IDS);
|
|
71
|
-
const providerStatusDisplaySchema = literalUnion(PROVIDER_STATUS_DISPLAYS);
|
|
72
|
-
const providerStatusResetModeSchema = literalUnion(PROVIDER_STATUS_RESET_MODES);
|
|
73
|
-
const PROVIDER_STATUS_RESET_MIN_USED_PERCENT_OPTIONS = [
|
|
74
|
-
0, 50, 75, 80, 90, 95, 100,
|
|
75
|
-
] as const;
|
|
76
|
-
|
|
77
|
-
const providerStatusConfigSchema = Type.Object(
|
|
78
|
-
{
|
|
79
|
-
refreshMs: Type.Optional(
|
|
80
|
-
Type.Integer({
|
|
81
|
-
minimum: MIN_PROVIDER_STATUS_REFRESH_MS,
|
|
82
|
-
maximum: MAX_PROVIDER_STATUS_REFRESH_MS,
|
|
83
|
-
}),
|
|
84
|
-
),
|
|
85
|
-
cacheTtlMs: Type.Optional(
|
|
86
|
-
Type.Integer({
|
|
87
|
-
minimum: MIN_PROVIDER_STATUS_CACHE_TTL_MS,
|
|
88
|
-
maximum: MAX_PROVIDER_STATUS_CACHE_TTL_MS,
|
|
89
|
-
}),
|
|
90
|
-
),
|
|
91
|
-
providers: Type.Optional(Type.Array(providerStatusProviderSchema)),
|
|
92
|
-
display: Type.Optional(providerStatusDisplaySchema),
|
|
93
|
-
showCredits: Type.Optional(Type.Boolean()),
|
|
94
|
-
showReset: Type.Optional(providerStatusResetModeSchema),
|
|
95
|
-
resetMinUsedPercent: Type.Optional(Type.Number({ minimum: 0, maximum: 100 })),
|
|
96
|
-
},
|
|
97
|
-
{ additionalProperties: false },
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
const footerWidgetConfigOverrideSchema = Type.Object(
|
|
101
|
-
{
|
|
102
|
-
enabled: Type.Optional(Type.Boolean()),
|
|
103
|
-
row: Type.Optional(Type.Integer({ minimum: 0, maximum: MAX_WIDGET_ROW })),
|
|
104
|
-
position: Type.Optional(Type.Integer({ minimum: 0, maximum: MAX_WIDGET_POSITION })),
|
|
105
|
-
align: Type.Optional(footerWidgetAlignSchema),
|
|
106
|
-
fill: Type.Optional(footerWidgetFillSchema),
|
|
107
|
-
minWidth: Type.Optional(
|
|
108
|
-
Type.Integer({ minimum: 0, maximum: MAX_WIDGET_MIN_WIDTH }),
|
|
109
|
-
),
|
|
110
|
-
icon: Type.Optional(footerWidgetIconModeSchema),
|
|
111
|
-
iconColor: Type.Optional(footerWidgetColorSchema),
|
|
112
|
-
textColor: Type.Optional(footerWidgetColorSchema),
|
|
113
|
-
},
|
|
114
|
-
{ additionalProperties: false },
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
const footerConfigFileSchema = Type.Object(
|
|
118
|
-
{
|
|
119
|
-
refreshMs: Type.Optional(
|
|
120
|
-
Type.Integer({
|
|
121
|
-
minimum: MIN_FOOTER_REFRESH_MS,
|
|
122
|
-
maximum: MAX_FOOTER_REFRESH_MS,
|
|
123
|
-
}),
|
|
124
|
-
),
|
|
125
|
-
iconFamily: Type.Optional(footerIconFamilySchema),
|
|
126
|
-
gaugeStyle: Type.Optional(gaugeStyleSchema),
|
|
127
|
-
gaugeWidth: Type.Optional(
|
|
128
|
-
Type.Integer({ minimum: MIN_GAUGE_WIDTH, maximum: MAX_GAUGE_WIDTH }),
|
|
129
|
-
),
|
|
130
|
-
gaugeColors: Type.Optional(
|
|
131
|
-
Type.Object(
|
|
132
|
-
{
|
|
133
|
-
ok: Type.Optional(footerWidgetColorSchema),
|
|
134
|
-
warning: Type.Optional(footerWidgetColorSchema),
|
|
135
|
-
error: Type.Optional(footerWidgetColorSchema),
|
|
136
|
-
},
|
|
137
|
-
{ additionalProperties: false },
|
|
138
|
-
),
|
|
139
|
-
),
|
|
140
|
-
defaultTextColor: Type.Optional(footerWidgetColorSchema),
|
|
141
|
-
defaultIconColor: Type.Optional(footerWidgetColorSchema),
|
|
142
|
-
providerStatus: Type.Optional(providerStatusConfigSchema),
|
|
143
|
-
widgets: Type.Optional(
|
|
144
|
-
Type.Object(
|
|
145
|
-
Object.fromEntries(
|
|
146
|
-
FOOTER_WIDGET_IDS.map((widgetId) => [
|
|
147
|
-
widgetId,
|
|
148
|
-
Type.Optional(footerWidgetConfigOverrideSchema),
|
|
149
|
-
]),
|
|
150
|
-
),
|
|
151
|
-
{ additionalProperties: false },
|
|
152
|
-
),
|
|
153
|
-
),
|
|
154
|
-
extensionWidgets: Type.Optional(
|
|
155
|
-
Type.Record(Type.String({ minLength: 1 }), footerWidgetConfigOverrideSchema),
|
|
156
|
-
),
|
|
157
|
-
},
|
|
158
|
-
{ additionalProperties: false },
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
const validateFooterConfigFile = Compile(footerConfigFileSchema);
|
|
162
|
-
let lastFooterConfigError: string | undefined;
|
|
163
|
-
|
|
164
|
-
export function hasFooterConfigError(): boolean {
|
|
165
|
-
return lastFooterConfigError !== undefined;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
type WidgetConfigBucket = "widgets" | "extensionWidgets";
|
|
169
|
-
|
|
170
|
-
export interface ConfigurableWidgetMeta {
|
|
171
|
-
id: string;
|
|
172
|
-
label: string;
|
|
173
|
-
shortLabel: string;
|
|
174
|
-
description: string;
|
|
175
|
-
defaults: {
|
|
176
|
-
row: number;
|
|
177
|
-
position: number;
|
|
178
|
-
align: FooterWidgetAlign;
|
|
179
|
-
fill: FooterWidgetFill;
|
|
180
|
-
minWidth?: number;
|
|
181
|
-
enabled?: boolean;
|
|
182
|
-
};
|
|
183
|
-
defaultIcon?: { text: string; color: FooterWidgetColor };
|
|
184
|
-
preferredIconColor?: FooterWidgetColor;
|
|
185
|
-
bucket: WidgetConfigBucket;
|
|
186
|
-
builtInId?: BuiltInFooterWidgetId;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function parseJsonFile(filePath: string): unknown | undefined {
|
|
190
|
-
if (!existsSync(filePath)) return undefined;
|
|
191
|
-
const content = readFileSync(filePath, "utf8");
|
|
192
|
-
try {
|
|
193
|
-
return JSON.parse(content);
|
|
194
|
-
} catch (error) {
|
|
195
|
-
throw new Error(`Failed to parse ${filePath}: ${error}`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function defaultFooterConfig(): FooterConfigSnapshot {
|
|
200
|
-
return structuredClone(DEFAULT_FOOTER_CONFIG);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function pruneWidgetOverrides(
|
|
204
|
-
overrides: Record<string, FooterWidgetConfigOverride> | undefined,
|
|
205
|
-
): Record<string, FooterWidgetConfigOverride> {
|
|
206
|
-
if (!overrides) return {};
|
|
207
|
-
|
|
208
|
-
const pruned: Record<string, FooterWidgetConfigOverride> = {};
|
|
209
|
-
for (const [widgetId, override] of Object.entries(overrides)) {
|
|
210
|
-
if (!override || Object.keys(override).length === 0) continue;
|
|
211
|
-
pruned[widgetId] = structuredClone(override);
|
|
212
|
-
}
|
|
213
|
-
return pruned;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function parseProviderStatusConfig(
|
|
217
|
-
input: FooterConfigSnapshot["providerStatus"] | undefined,
|
|
218
|
-
): FooterConfigSnapshot["providerStatus"] {
|
|
219
|
-
const providers = input?.providers ?? DEFAULT_PROVIDER_STATUS_CONFIG.providers;
|
|
220
|
-
const knownProviders = PROVIDER_STATUS_PROVIDER_IDS.filter((id) =>
|
|
221
|
-
providers.includes(id),
|
|
222
|
-
);
|
|
223
|
-
return {
|
|
224
|
-
refreshMs: input?.refreshMs ?? DEFAULT_PROVIDER_STATUS_CONFIG.refreshMs,
|
|
225
|
-
cacheTtlMs: input?.cacheTtlMs ?? DEFAULT_PROVIDER_STATUS_CONFIG.cacheTtlMs,
|
|
226
|
-
providers: knownProviders,
|
|
227
|
-
display: input?.display ?? DEFAULT_PROVIDER_STATUS_CONFIG.display,
|
|
228
|
-
showCredits: input?.showCredits ?? DEFAULT_PROVIDER_STATUS_CONFIG.showCredits,
|
|
229
|
-
showReset: input?.showReset ?? DEFAULT_PROVIDER_STATUS_CONFIG.showReset,
|
|
230
|
-
resetMinUsedPercent:
|
|
231
|
-
input?.resetMinUsedPercent ?? DEFAULT_PROVIDER_STATUS_CONFIG.resetMinUsedPercent,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Keys that existed in earlier releases, mapped to their replacement (or
|
|
236
|
-
// undefined when the key was removed without one).
|
|
237
|
-
const RENAMED_CONFIG_KEYS: Record<string, string | undefined> = {
|
|
238
|
-
contextBarStyle: "gaugeStyle",
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
function knownKeysAt(path: string): readonly string[] {
|
|
242
|
-
if (path === "") return Object.keys(footerConfigFileSchema.properties);
|
|
243
|
-
if (path === "/providerStatus") {
|
|
244
|
-
return Object.keys(providerStatusConfigSchema.properties);
|
|
245
|
-
}
|
|
246
|
-
if (path === "/gaugeColors") return Object.keys(DEFAULT_GAUGE_COLORS);
|
|
247
|
-
if (path === "/widgets") return FOOTER_WIDGET_IDS;
|
|
248
|
-
if (/^\/(widgets|extensionWidgets)\/[^/]+$/.test(path)) {
|
|
249
|
-
return Object.keys(footerWidgetConfigOverrideSchema.properties);
|
|
250
|
-
}
|
|
251
|
-
return [];
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function describeConfigError(error: {
|
|
255
|
-
message: string;
|
|
256
|
-
instancePath?: string;
|
|
257
|
-
params?: unknown;
|
|
258
|
-
}): string[] {
|
|
259
|
-
const path = error.instancePath || "";
|
|
260
|
-
const display = path || "/";
|
|
261
|
-
if (path === "/providerStatus/showReset") {
|
|
262
|
-
return [
|
|
263
|
-
` - ${display}: use "off", "primary", or "all" (replace true with "primary" and false with "off")`,
|
|
264
|
-
];
|
|
265
|
-
}
|
|
266
|
-
if (path === "/providerStatus/resetMinUsedPercent") {
|
|
267
|
-
return [
|
|
268
|
-
` - ${display}: use a number from 0 to 100 (the used quota percentage at which reset countdowns appear)`,
|
|
269
|
-
];
|
|
270
|
-
}
|
|
271
|
-
const params = error.params as { additionalProperties?: unknown } | undefined;
|
|
272
|
-
const unknown = Array.isArray(params?.additionalProperties)
|
|
273
|
-
? params.additionalProperties.filter(
|
|
274
|
-
(key): key is string => typeof key === "string",
|
|
275
|
-
)
|
|
276
|
-
: [];
|
|
277
|
-
if (unknown.length === 0) return [` - ${display}: ${error.message}`];
|
|
278
|
-
|
|
279
|
-
const known = knownKeysAt(path);
|
|
280
|
-
return unknown.map((key) => {
|
|
281
|
-
if (key in RENAMED_CONFIG_KEYS) {
|
|
282
|
-
const replacement = RENAMED_CONFIG_KEYS[key];
|
|
283
|
-
const hint = replacement
|
|
284
|
-
? `it was renamed to "${replacement}"`
|
|
285
|
-
: "it was removed";
|
|
286
|
-
return ` - ${display}: unknown key "${key}" (${hint})`;
|
|
287
|
-
}
|
|
288
|
-
const suggestion = closestKey(key, known);
|
|
289
|
-
return ` - ${display}: unknown key "${key}"${
|
|
290
|
-
suggestion ? ` (did you mean "${suggestion}"?)` : ""
|
|
291
|
-
}`;
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function closestKey(key: string, candidates: readonly string[]): string | undefined {
|
|
296
|
-
let best: string | undefined;
|
|
297
|
-
let bestDistance = Math.max(2, Math.floor(key.length / 3));
|
|
298
|
-
for (const candidate of candidates) {
|
|
299
|
-
const distance = editDistance(key.toLowerCase(), candidate.toLowerCase());
|
|
300
|
-
if (distance <= bestDistance) {
|
|
301
|
-
best = candidate;
|
|
302
|
-
bestDistance = distance;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return best;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function editDistance(a: string, b: string): number {
|
|
309
|
-
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
310
|
-
for (let i = 1; i <= a.length; i++) {
|
|
311
|
-
let previous = row[0]!;
|
|
312
|
-
row[0] = i;
|
|
313
|
-
for (let j = 1; j <= b.length; j++) {
|
|
314
|
-
const current = row[j]!;
|
|
315
|
-
row[j] = Math.min(
|
|
316
|
-
current + 1,
|
|
317
|
-
row[j - 1]! + 1,
|
|
318
|
-
previous + (a[i - 1] === b[j - 1] ? 0 : 1),
|
|
319
|
-
);
|
|
320
|
-
previous = current;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
return row[b.length]!;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
export function footerConfigValidationErrors(value: unknown): string[] {
|
|
327
|
-
if (validateFooterConfigFile.Check(value)) return [];
|
|
328
|
-
|
|
329
|
-
const errors = [...validateFooterConfigFile.Errors(value)];
|
|
330
|
-
const unknownPropertyPaths = new Set<string>();
|
|
331
|
-
for (const error of errors) {
|
|
332
|
-
const params = error.params as { additionalProperties?: unknown } | undefined;
|
|
333
|
-
if (!Array.isArray(params?.additionalProperties)) continue;
|
|
334
|
-
|
|
335
|
-
const parentPath = error.instancePath || "";
|
|
336
|
-
for (const key of params.additionalProperties) {
|
|
337
|
-
if (typeof key !== "string") continue;
|
|
338
|
-
const pointerKey = key.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
339
|
-
unknownPropertyPaths.add(`${parentPath}/${pointerKey}`);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const seen = new Set<string>();
|
|
344
|
-
const messages: string[] = [];
|
|
345
|
-
for (const error of errors) {
|
|
346
|
-
if (
|
|
347
|
-
error.message === "schema is false" &&
|
|
348
|
-
unknownPropertyPaths.has(error.instancePath)
|
|
349
|
-
) {
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
for (const message of describeConfigError(error)) {
|
|
353
|
-
if (seen.has(message)) continue;
|
|
354
|
-
seen.add(message);
|
|
355
|
-
messages.push(message);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return messages;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function parseFooterConfig(filePath: string, value: unknown): FooterConfigSnapshot {
|
|
362
|
-
if (value === undefined) return defaultFooterConfig();
|
|
363
|
-
|
|
364
|
-
const errors = footerConfigValidationErrors(value);
|
|
365
|
-
if (errors.length > 0) {
|
|
366
|
-
throw new Error(`Invalid ${filePath}:\n${errors.join("\n")}`);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const input = value as FooterConfigSnapshot;
|
|
370
|
-
return {
|
|
371
|
-
refreshMs: input.refreshMs ?? DEFAULT_FOOTER_CONFIG.refreshMs,
|
|
372
|
-
iconFamily: input.iconFamily ?? DEFAULT_FOOTER_CONFIG.iconFamily,
|
|
373
|
-
gaugeStyle: input.gaugeStyle ?? DEFAULT_FOOTER_CONFIG.gaugeStyle,
|
|
374
|
-
gaugeWidth: input.gaugeWidth ?? DEFAULT_FOOTER_CONFIG.gaugeWidth,
|
|
375
|
-
gaugeColors: {
|
|
376
|
-
ok: input.gaugeColors?.ok ?? DEFAULT_GAUGE_COLORS.ok,
|
|
377
|
-
warning: input.gaugeColors?.warning ?? DEFAULT_GAUGE_COLORS.warning,
|
|
378
|
-
error: input.gaugeColors?.error ?? DEFAULT_GAUGE_COLORS.error,
|
|
379
|
-
},
|
|
380
|
-
defaultTextColor: input.defaultTextColor ?? DEFAULT_FOOTER_CONFIG.defaultTextColor,
|
|
381
|
-
defaultIconColor: input.defaultIconColor ?? DEFAULT_FOOTER_CONFIG.defaultIconColor,
|
|
382
|
-
providerStatus: parseProviderStatusConfig(input.providerStatus),
|
|
383
|
-
widgets: pruneWidgetOverrides(
|
|
384
|
-
input.widgets as Record<string, FooterWidgetConfigOverride> | undefined,
|
|
385
|
-
) as Partial<Record<BuiltInFooterWidgetId, FooterWidgetConfigOverride>>,
|
|
386
|
-
extensionWidgets: pruneWidgetOverrides(input.extensionWidgets),
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
export function getFooterConfigPath(): string {
|
|
391
|
-
return join(getAgentDir(), FOOTER_CONFIG_FILE);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
export function loadFooterConfig(): FooterConfigSnapshot {
|
|
395
|
-
const configPath = getFooterConfigPath();
|
|
396
|
-
|
|
397
|
-
try {
|
|
398
|
-
const config = parseJsonFile(configPath);
|
|
399
|
-
const parsed = parseFooterConfig(configPath, config);
|
|
400
|
-
lastFooterConfigError = undefined;
|
|
401
|
-
return parsed;
|
|
402
|
-
} catch (error) {
|
|
403
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
404
|
-
if (message !== lastFooterConfigError) {
|
|
405
|
-
console.warn(
|
|
406
|
-
`${message}\nUsing default footer settings until the config is fixed. Run /fancy-footer to edit it interactively.`,
|
|
407
|
-
);
|
|
408
|
-
lastFooterConfigError = message;
|
|
409
|
-
}
|
|
410
|
-
return defaultFooterConfig();
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function writeFooterConfigFile(content: string): void {
|
|
415
|
-
mkdirSync(getAgentDir(), { recursive: true });
|
|
416
|
-
writeFileSync(getFooterConfigPath(), content, "utf8");
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
export function cloneFooterConfig(config: FooterConfigSnapshot): FooterConfigSnapshot {
|
|
420
|
-
return structuredClone(config);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function isEmptyWidgetOverride(
|
|
424
|
-
override: FooterWidgetConfigOverride | undefined,
|
|
425
|
-
): boolean {
|
|
426
|
-
if (!override) return true;
|
|
427
|
-
for (const value of Object.values(override)) {
|
|
428
|
-
if (value !== undefined) return false;
|
|
429
|
-
}
|
|
430
|
-
return true;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function toFooterConfigObject(config: FooterConfigSnapshot): Record<string, unknown> {
|
|
434
|
-
const widgets: Partial<Record<BuiltInFooterWidgetId, FooterWidgetConfigOverride>> =
|
|
435
|
-
{};
|
|
436
|
-
for (const widgetId of FOOTER_WIDGET_IDS) {
|
|
437
|
-
const override = config.widgets[widgetId];
|
|
438
|
-
if (isEmptyWidgetOverride(override)) continue;
|
|
439
|
-
widgets[widgetId] = structuredClone(override);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const extensionWidgets: Record<string, FooterWidgetConfigOverride> = {};
|
|
443
|
-
for (const [widgetId, override] of Object.entries(config.extensionWidgets)) {
|
|
444
|
-
if (isEmptyWidgetOverride(override)) continue;
|
|
445
|
-
extensionWidgets[widgetId] = structuredClone(override);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const out: Record<string, unknown> = {
|
|
449
|
-
refreshMs: clampInt(config.refreshMs, MIN_FOOTER_REFRESH_MS, MAX_FOOTER_REFRESH_MS),
|
|
450
|
-
iconFamily: config.iconFamily,
|
|
451
|
-
gaugeStyle: config.gaugeStyle,
|
|
452
|
-
gaugeWidth: clampInt(config.gaugeWidth, MIN_GAUGE_WIDTH, MAX_GAUGE_WIDTH),
|
|
453
|
-
defaultTextColor: config.defaultTextColor,
|
|
454
|
-
defaultIconColor: config.defaultIconColor,
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
if (
|
|
458
|
-
config.gaugeColors.ok !== DEFAULT_GAUGE_COLORS.ok ||
|
|
459
|
-
config.gaugeColors.warning !== DEFAULT_GAUGE_COLORS.warning ||
|
|
460
|
-
config.gaugeColors.error !== DEFAULT_GAUGE_COLORS.error
|
|
461
|
-
) {
|
|
462
|
-
out.gaugeColors = structuredClone(config.gaugeColors);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (
|
|
466
|
-
config.providerStatus.refreshMs !== DEFAULT_PROVIDER_STATUS_CONFIG.refreshMs ||
|
|
467
|
-
config.providerStatus.cacheTtlMs !== DEFAULT_PROVIDER_STATUS_CONFIG.cacheTtlMs ||
|
|
468
|
-
config.providerStatus.display !== DEFAULT_PROVIDER_STATUS_CONFIG.display ||
|
|
469
|
-
config.providerStatus.showCredits !== DEFAULT_PROVIDER_STATUS_CONFIG.showCredits ||
|
|
470
|
-
config.providerStatus.showReset !== DEFAULT_PROVIDER_STATUS_CONFIG.showReset ||
|
|
471
|
-
config.providerStatus.resetMinUsedPercent !==
|
|
472
|
-
DEFAULT_PROVIDER_STATUS_CONFIG.resetMinUsedPercent ||
|
|
473
|
-
config.providerStatus.providers.join(",") !==
|
|
474
|
-
DEFAULT_PROVIDER_STATUS_CONFIG.providers.join(",")
|
|
475
|
-
) {
|
|
476
|
-
out.providerStatus = structuredClone(config.providerStatus);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (Object.keys(widgets).length > 0) out.widgets = widgets;
|
|
480
|
-
if (Object.keys(extensionWidgets).length > 0) {
|
|
481
|
-
out.extensionWidgets = extensionWidgets;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
return out;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
export function writeFooterConfigSnapshot(config: FooterConfigSnapshot): void {
|
|
488
|
-
writeFooterConfigFile(`${JSON.stringify(toFooterConfigObject(config), null, 2)}\n`);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
export function getWidgetOverride(
|
|
492
|
-
config: FooterConfigSnapshot,
|
|
493
|
-
widget: ConfigurableWidgetMeta,
|
|
494
|
-
): FooterWidgetConfigOverride | undefined {
|
|
495
|
-
return widget.bucket === "widgets"
|
|
496
|
-
? config.widgets[widget.id as BuiltInFooterWidgetId]
|
|
497
|
-
: config.extensionWidgets[widget.id];
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
export function updateWidgetOverride(
|
|
501
|
-
config: FooterConfigSnapshot,
|
|
502
|
-
widget: ConfigurableWidgetMeta,
|
|
503
|
-
mutate: (override: FooterWidgetConfigOverride) => void,
|
|
504
|
-
): void {
|
|
505
|
-
const target =
|
|
506
|
-
widget.bucket === "widgets"
|
|
507
|
-
? (config.widgets as Record<string, FooterWidgetConfigOverride>)
|
|
508
|
-
: config.extensionWidgets;
|
|
509
|
-
const override = structuredClone(target[widget.id] ?? {});
|
|
510
|
-
mutate(override);
|
|
511
|
-
|
|
512
|
-
if (isEmptyWidgetOverride(override)) {
|
|
513
|
-
delete target[widget.id];
|
|
514
|
-
} else {
|
|
515
|
-
target[widget.id] = override;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
export function buildConfigurableWidgets(
|
|
520
|
-
config: FooterConfigSnapshot,
|
|
521
|
-
extensionWidgets: readonly NormalizedFancyFooterDataWidget[],
|
|
522
|
-
): ConfigurableWidgetMeta[] {
|
|
523
|
-
return [
|
|
524
|
-
...FOOTER_WIDGET_IDS.map((widgetId) => ({
|
|
525
|
-
id: widgetId,
|
|
526
|
-
label: widgetId,
|
|
527
|
-
shortLabel: FOOTER_WIDGET_META[widgetId].shortLabel,
|
|
528
|
-
description: FOOTER_WIDGET_META[widgetId].description,
|
|
529
|
-
defaults: FOOTER_WIDGET_META[widgetId].defaults,
|
|
530
|
-
defaultIcon: getDefaultWidgetIcon(widgetId, config.iconFamily),
|
|
531
|
-
bucket: "widgets" as const,
|
|
532
|
-
builtInId: widgetId,
|
|
533
|
-
})),
|
|
534
|
-
...extensionWidgets.map((widget) => ({
|
|
535
|
-
id: widget.id,
|
|
536
|
-
label: widget.label ?? widget.id,
|
|
537
|
-
shortLabel: widget.label ?? widget.id,
|
|
538
|
-
description: widget.description,
|
|
539
|
-
defaults: widget.defaults,
|
|
540
|
-
defaultIcon: resolveDataWidgetIcon(widget.icon, config.iconFamily),
|
|
541
|
-
preferredIconColor: widget.icon ? widget.icon.color : undefined,
|
|
542
|
-
bucket: "extensionWidgets" as const,
|
|
543
|
-
})),
|
|
544
|
-
];
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
function widgetLabel(
|
|
548
|
-
config: FooterConfigSnapshot,
|
|
549
|
-
widget: ConfigurableWidgetMeta,
|
|
550
|
-
theme: Theme,
|
|
551
|
-
): string {
|
|
552
|
-
const icon = widget.defaultIcon?.text ?? "◌";
|
|
553
|
-
const override = getWidgetOverride(config, widget);
|
|
554
|
-
if (override?.icon === "hide") {
|
|
555
|
-
return `${theme.fg("dim", icon)} ${widget.label}`;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const iconColor =
|
|
559
|
-
override?.iconColor ?? widget.preferredIconColor ?? config.defaultIconColor;
|
|
560
|
-
return `${theme.fg(iconColor, icon)} ${widget.label}`;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function applyWidgetField(
|
|
564
|
-
config: FooterConfigSnapshot,
|
|
565
|
-
widget: ConfigurableWidgetMeta,
|
|
566
|
-
fieldId: string,
|
|
567
|
-
newValue: string,
|
|
568
|
-
): void {
|
|
569
|
-
if (widget.builtInId === "provider-status" && fieldId === "resetMinUsedPercent") {
|
|
570
|
-
config.providerStatus.resetMinUsedPercent = Number(newValue);
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
updateWidgetOverride(config, widget, (override) => {
|
|
575
|
-
switch (fieldId) {
|
|
576
|
-
case "enabled":
|
|
577
|
-
if (newValue === "default") delete override.enabled;
|
|
578
|
-
else if (newValue === "enabled") override.enabled = true;
|
|
579
|
-
else if (newValue === "disabled") override.enabled = false;
|
|
580
|
-
break;
|
|
581
|
-
case "icon":
|
|
582
|
-
if (newValue === "default") delete override.icon;
|
|
583
|
-
else if (newValue === "hide") override.icon = "hide";
|
|
584
|
-
break;
|
|
585
|
-
case "iconColor":
|
|
586
|
-
case "textColor": {
|
|
587
|
-
const color = plainSettingValue(newValue);
|
|
588
|
-
if (color === "default") {
|
|
589
|
-
delete override[fieldId];
|
|
590
|
-
} else if (isFooterWidgetColor(color)) {
|
|
591
|
-
override[fieldId] = color;
|
|
592
|
-
}
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
case "minWidth": {
|
|
596
|
-
if (newValue === "default") {
|
|
597
|
-
delete override.minWidth;
|
|
598
|
-
break;
|
|
599
|
-
}
|
|
600
|
-
const parsed = toBoundedNonNegativeInt(newValue, MAX_WIDGET_MIN_WIDTH);
|
|
601
|
-
if (parsed !== undefined) override.minWidth = parsed;
|
|
602
|
-
break;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function optionValues(
|
|
609
|
-
base: readonly number[],
|
|
610
|
-
currentValue: number | undefined,
|
|
611
|
-
): string[] {
|
|
612
|
-
const values = new Set(base.map((value) => String(value)));
|
|
613
|
-
if (currentValue !== undefined) values.add(String(currentValue));
|
|
614
|
-
return ["default", ...Array.from(values).sort((a, b) => Number(a) - Number(b))];
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Settings values for colors and gauge styles carry a preview prefix
|
|
618
|
-
// (e.g. "\u2588\u2588 success" or "\u25b0\u25b0\u25b0\u25b1\u25b1 parallelograms") so the list shows
|
|
619
|
-
// what each option resolves to. plainSettingValue() recovers the bare
|
|
620
|
-
// option name when a change comes back from the settings list.
|
|
621
|
-
function colorSettingValue(theme: Theme, color: string): string {
|
|
622
|
-
if (!isFooterWidgetColor(color)) return color;
|
|
623
|
-
return `${theme.fg(color, "\u2588\u2588")} ${color}`;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
function gaugeStyleSettingValue(style: GaugeStyleDef): string {
|
|
627
|
-
return `${style.filled.repeat(3)}${style.empty.repeat(2)} ${style.label}`;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
export function plainSettingValue(value: string): string {
|
|
631
|
-
const stripped = value.replace(/\x1b\[[0-9;]*m/g, "").trim();
|
|
632
|
-
const parts = stripped.split(/\s+/);
|
|
633
|
-
return parts[parts.length - 1] ?? stripped;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
function widgetSettingsItems(
|
|
637
|
-
config: FooterConfigSnapshot,
|
|
638
|
-
widget: ConfigurableWidgetMeta,
|
|
639
|
-
theme: Theme,
|
|
640
|
-
): SettingItem[] {
|
|
641
|
-
const override = getWidgetOverride(config, widget);
|
|
642
|
-
|
|
643
|
-
const items: SettingItem[] = [
|
|
644
|
-
{
|
|
645
|
-
id: "enabled",
|
|
646
|
-
label: "visibility",
|
|
647
|
-
currentValue:
|
|
648
|
-
override?.enabled === true
|
|
649
|
-
? "enabled"
|
|
650
|
-
: override?.enabled === false
|
|
651
|
-
? "disabled"
|
|
652
|
-
: "default",
|
|
653
|
-
values: ["default", "enabled", "disabled"],
|
|
654
|
-
description:
|
|
655
|
-
"Choose whether this widget follows its normal behavior, always shows, or stays hidden.",
|
|
656
|
-
},
|
|
657
|
-
{
|
|
658
|
-
id: "icon",
|
|
659
|
-
label: "icon",
|
|
660
|
-
currentValue: override?.icon === "hide" ? "hide" : "default",
|
|
661
|
-
values: ["default", "hide"],
|
|
662
|
-
description: widget.defaultIcon
|
|
663
|
-
? `Use the default ${config.iconFamily} icon: ${widget.defaultIcon.text}`
|
|
664
|
-
: "This widget doesn't have a built-in icon.",
|
|
665
|
-
},
|
|
666
|
-
{
|
|
667
|
-
id: "iconColor",
|
|
668
|
-
label: "icon color",
|
|
669
|
-
currentValue: colorSettingValue(theme, override?.iconColor ?? "default"),
|
|
670
|
-
values: ["default", ...FOOTER_WIDGET_COLORS].map((color) =>
|
|
671
|
-
colorSettingValue(theme, color),
|
|
672
|
-
),
|
|
673
|
-
description: "Choose the icon color when the icon is visible.",
|
|
674
|
-
},
|
|
675
|
-
{
|
|
676
|
-
id: "textColor",
|
|
677
|
-
label: "text color",
|
|
678
|
-
currentValue: colorSettingValue(theme, override?.textColor ?? "default"),
|
|
679
|
-
values: ["default", ...FOOTER_WIDGET_COLORS].map((color) =>
|
|
680
|
-
colorSettingValue(theme, color),
|
|
681
|
-
),
|
|
682
|
-
description: "Choose the text color for this widget.",
|
|
683
|
-
},
|
|
684
|
-
{
|
|
685
|
-
id: "minWidth",
|
|
686
|
-
label: "min width",
|
|
687
|
-
currentValue:
|
|
688
|
-
override?.minWidth !== undefined ? String(override.minWidth) : "default",
|
|
689
|
-
values: optionValues(FOOTER_MIN_WIDTH_OPTIONS, override?.minWidth),
|
|
690
|
-
description: "Reserve at least this much width for the widget.",
|
|
691
|
-
},
|
|
692
|
-
];
|
|
693
|
-
|
|
694
|
-
if (widget.builtInId === "provider-status") {
|
|
695
|
-
const values = new Set([
|
|
696
|
-
...PROVIDER_STATUS_RESET_MIN_USED_PERCENT_OPTIONS,
|
|
697
|
-
config.providerStatus.resetMinUsedPercent,
|
|
698
|
-
]);
|
|
699
|
-
items.push({
|
|
700
|
-
id: "resetMinUsedPercent",
|
|
701
|
-
label: "reset countdown threshold (%)",
|
|
702
|
-
currentValue: String(config.providerStatus.resetMinUsedPercent),
|
|
703
|
-
values: Array.from(values)
|
|
704
|
-
.sort((a, b) => a - b)
|
|
705
|
-
.map(String),
|
|
706
|
-
description:
|
|
707
|
-
"Show each reset countdown once its quota window reaches this used percentage. Applies to all provider windows.",
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
return items;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
export function widgetSettingsSubmenu(
|
|
715
|
-
draft: FooterConfigSnapshot,
|
|
716
|
-
theme: Theme,
|
|
717
|
-
widget: ConfigurableWidgetMeta,
|
|
718
|
-
applyDraft: () => void,
|
|
719
|
-
) {
|
|
720
|
-
return (_currentValue: string, done: (selectedValue?: string) => void) => {
|
|
721
|
-
const submenuItems = widgetSettingsItems(draft, widget, theme);
|
|
722
|
-
const container = new Container();
|
|
723
|
-
container.addChild(
|
|
724
|
-
new Text(
|
|
725
|
-
theme.fg("accent", theme.bold(`Widget: ${widgetLabel(draft, widget, theme)}`)),
|
|
726
|
-
1,
|
|
727
|
-
0,
|
|
728
|
-
),
|
|
729
|
-
);
|
|
730
|
-
container.addChild(
|
|
731
|
-
new Text(
|
|
732
|
-
theme.fg("dim", "Change this widget's layout, appearance, and behavior"),
|
|
733
|
-
1,
|
|
734
|
-
0,
|
|
735
|
-
),
|
|
736
|
-
);
|
|
737
|
-
|
|
738
|
-
const settings = new SettingsList(
|
|
739
|
-
submenuItems,
|
|
740
|
-
Math.min(submenuItems.length + 2, 14),
|
|
741
|
-
getSettingsListTheme(),
|
|
742
|
-
(fieldId, newValue) => {
|
|
743
|
-
applyWidgetField(draft, widget, fieldId, newValue);
|
|
744
|
-
applyDraft();
|
|
745
|
-
},
|
|
746
|
-
() => {
|
|
747
|
-
done();
|
|
748
|
-
},
|
|
749
|
-
);
|
|
750
|
-
|
|
751
|
-
container.addChild(settings);
|
|
752
|
-
container.addChild(
|
|
753
|
-
new Text(theme.fg("dim", "↑↓ navigate • enter/space change • esc back"), 1, 0),
|
|
754
|
-
);
|
|
755
|
-
|
|
756
|
-
return {
|
|
757
|
-
render(width: number) {
|
|
758
|
-
return container.render(width);
|
|
759
|
-
},
|
|
760
|
-
invalidate() {
|
|
761
|
-
container.invalidate();
|
|
762
|
-
},
|
|
763
|
-
handleInput(data: string) {
|
|
764
|
-
settings.handleInput?.(data);
|
|
765
|
-
},
|
|
766
|
-
};
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
export function genericFooterSettingsItems(
|
|
771
|
-
draft: FooterConfigSnapshot,
|
|
772
|
-
theme: Theme,
|
|
773
|
-
): SettingItem[] {
|
|
774
|
-
const refreshValues = Array.from(
|
|
775
|
-
new Set([
|
|
776
|
-
String(draft.refreshMs),
|
|
777
|
-
...FOOTER_REFRESH_OPTIONS.map((value) => String(value)),
|
|
778
|
-
]),
|
|
779
|
-
).sort((a, b) => Number(a) - Number(b));
|
|
780
|
-
|
|
781
|
-
return [
|
|
782
|
-
{
|
|
783
|
-
id: "refreshMs",
|
|
784
|
-
label: "refresh interval (ms)",
|
|
785
|
-
currentValue: String(draft.refreshMs),
|
|
786
|
-
values: refreshValues,
|
|
787
|
-
description:
|
|
788
|
-
"Choose how often the footer refreshes. Lower values update faster. Higher values reduce background Git calls.",
|
|
789
|
-
},
|
|
790
|
-
{
|
|
791
|
-
id: "iconFamily",
|
|
792
|
-
label: "icon family",
|
|
793
|
-
currentValue: draft.iconFamily,
|
|
794
|
-
values: [...FOOTER_ICON_FAMILIES],
|
|
795
|
-
description:
|
|
796
|
-
"Choose the icon style used across the footer and this configuration screen.",
|
|
797
|
-
},
|
|
798
|
-
{
|
|
799
|
-
id: "gaugeStyle",
|
|
800
|
-
label: "gauge style",
|
|
801
|
-
currentValue: gaugeStyleSettingValue(getGaugeStyle(draft.gaugeStyle)),
|
|
802
|
-
values: GAUGE_STYLES.map((style) => gaugeStyleSettingValue(style)),
|
|
803
|
-
description: "Choose the glyphs used by the context and provider status gauges.",
|
|
804
|
-
},
|
|
805
|
-
{
|
|
806
|
-
id: "gaugeWidth",
|
|
807
|
-
label: "gauge width (cells)",
|
|
808
|
-
currentValue: String(draft.gaugeWidth),
|
|
809
|
-
values: GAUGE_WIDTH_OPTIONS.map((value) => String(value)),
|
|
810
|
-
description:
|
|
811
|
-
"Choose how many cells the provider status gauges and the compact context gauge span. A context bar with fill set to grow spans the row instead.",
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
id: "gaugeColorOk",
|
|
815
|
-
label: "gauge color (healthy)",
|
|
816
|
-
currentValue: colorSettingValue(theme, draft.gaugeColors.ok),
|
|
817
|
-
values: FOOTER_WIDGET_COLORS.map((color) => colorSettingValue(theme, color)),
|
|
818
|
-
description:
|
|
819
|
-
"Choose the fill color for healthy gauges. Defaults to the theme accent so gauges only stand out when running low.",
|
|
820
|
-
},
|
|
821
|
-
{
|
|
822
|
-
id: "gaugeColorWarning",
|
|
823
|
-
label: "gauge color (warning)",
|
|
824
|
-
currentValue: colorSettingValue(theme, draft.gaugeColors.warning),
|
|
825
|
-
values: FOOTER_WIDGET_COLORS.map((color) => colorSettingValue(theme, color)),
|
|
826
|
-
description: "Choose the fill color for gauges above 40% used.",
|
|
827
|
-
},
|
|
828
|
-
{
|
|
829
|
-
id: "gaugeColorError",
|
|
830
|
-
label: "gauge color (critical)",
|
|
831
|
-
currentValue: colorSettingValue(theme, draft.gaugeColors.error),
|
|
832
|
-
values: FOOTER_WIDGET_COLORS.map((color) => colorSettingValue(theme, color)),
|
|
833
|
-
description: "Choose the fill color for gauges above 75% used.",
|
|
834
|
-
},
|
|
835
|
-
{
|
|
836
|
-
id: "defaultTextColor",
|
|
837
|
-
label: "default text color",
|
|
838
|
-
currentValue: colorSettingValue(theme, draft.defaultTextColor),
|
|
839
|
-
values: FOOTER_WIDGET_COLORS.map((color) => colorSettingValue(theme, color)),
|
|
840
|
-
description:
|
|
841
|
-
"Choose the default text color for widgets. You can still change individual widgets.",
|
|
842
|
-
},
|
|
843
|
-
{
|
|
844
|
-
id: "defaultIconColor",
|
|
845
|
-
label: "default icon color",
|
|
846
|
-
currentValue: colorSettingValue(theme, draft.defaultIconColor),
|
|
847
|
-
values: FOOTER_WIDGET_COLORS.map((color) => colorSettingValue(theme, color)),
|
|
848
|
-
description:
|
|
849
|
-
"Choose the default icon color for widgets. You can still change individual widgets.",
|
|
850
|
-
},
|
|
851
|
-
];
|
|
852
|
-
}
|