Skip to content

Commit 16acf57

Browse files
committed
refactor: split runtime settings and storage helpers
1 parent cb5f457 commit 16acf57

78 files changed

Lines changed: 9816 additions & 5349 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

index.ts

Lines changed: 3645 additions & 3614 deletions
Large diffs are not rendered by default.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { PluginConfig } from "../types.js";
2+
import type {
3+
BackendCategoryKey,
4+
BackendCategoryOption,
5+
BackendNumberSettingKey,
6+
BackendNumberSettingOption,
7+
BackendSettingFocusKey,
8+
BackendToggleSettingKey,
9+
} from "./backend-settings-schema.js";
10+
11+
export function resolveFocusedBackendNumberKey(
12+
focus: BackendSettingFocusKey,
13+
numberOptions: BackendNumberSettingOption[],
14+
): BackendNumberSettingKey {
15+
const numberKeys = new Set<BackendNumberSettingKey>(
16+
numberOptions.map((option) => option.key),
17+
);
18+
if (focus && numberKeys.has(focus as BackendNumberSettingKey)) {
19+
return focus as BackendNumberSettingKey;
20+
}
21+
return numberOptions[0]?.key ?? "fetchTimeoutMs";
22+
}
23+
24+
export function getBackendCategory(
25+
key: BackendCategoryKey,
26+
categoryOptions: readonly BackendCategoryOption[],
27+
): BackendCategoryOption | null {
28+
return categoryOptions.find((category) => category.key === key) ?? null;
29+
}
30+
31+
export function getBackendCategoryInitialFocus(
32+
category: BackendCategoryOption,
33+
): BackendSettingFocusKey {
34+
const firstToggle = category.toggleKeys[0];
35+
if (firstToggle) return firstToggle;
36+
return category.numberKeys[0] ?? null;
37+
}
38+
39+
export function applyBackendCategoryDefaults(
40+
draft: PluginConfig,
41+
category: BackendCategoryOption,
42+
deps: {
43+
backendDefaults: PluginConfig;
44+
numberOptionByKey: ReadonlyMap<
45+
BackendNumberSettingKey,
46+
BackendNumberSettingOption
47+
>;
48+
},
49+
): PluginConfig {
50+
const next = { ...draft };
51+
for (const key of category.toggleKeys) {
52+
next[key as BackendToggleSettingKey] = deps.backendDefaults[key] ?? false;
53+
}
54+
for (const key of category.numberKeys) {
55+
const option = deps.numberOptionByKey.get(key);
56+
const fallback = option?.min ?? 0;
57+
next[key] = deps.backendDefaults[key] ?? fallback;
58+
}
59+
return next;
60+
}
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
import type { PluginConfig } from "../types.js";
2+
import type { UiRuntimeOptions } from "../ui/runtime.js";
3+
import type { MenuItem } from "../ui/select.js";
4+
import type {
5+
BackendCategoryConfigAction,
6+
BackendCategoryOption,
7+
BackendNumberSettingKey,
8+
BackendNumberSettingOption,
9+
BackendSettingFocusKey,
10+
BackendToggleSettingKey,
11+
BackendToggleSettingOption,
12+
} from "./backend-settings-schema.js";
13+
14+
export async function promptBackendCategorySettingsMenu(params: {
15+
initial: PluginConfig;
16+
category: BackendCategoryOption;
17+
initialFocus: BackendSettingFocusKey;
18+
ui: UiRuntimeOptions;
19+
cloneBackendPluginConfig: (config: PluginConfig) => PluginConfig;
20+
buildBackendSettingsPreview: (
21+
config: PluginConfig,
22+
ui: UiRuntimeOptions,
23+
focusKey: BackendSettingFocusKey,
24+
deps: {
25+
highlightPreviewToken: (text: string, ui: UiRuntimeOptions) => string;
26+
},
27+
) => { label: string; hint: string };
28+
highlightPreviewToken: (text: string, ui: UiRuntimeOptions) => string;
29+
resolveFocusedBackendNumberKey: (
30+
focus: BackendSettingFocusKey,
31+
numberOptions: BackendNumberSettingOption[],
32+
) => BackendNumberSettingKey;
33+
clampBackendNumber: (
34+
option: BackendNumberSettingOption,
35+
value: number,
36+
) => number;
37+
formatBackendNumberValue: (
38+
option: BackendNumberSettingOption,
39+
value: number,
40+
) => string;
41+
formatDashboardSettingState: (enabled: boolean) => string;
42+
applyBackendCategoryDefaults: (
43+
config: PluginConfig,
44+
category: BackendCategoryOption,
45+
) => PluginConfig;
46+
getBackendCategoryInitialFocus: (
47+
category: BackendCategoryOption,
48+
) => BackendSettingFocusKey;
49+
backendDefaults: PluginConfig;
50+
toggleOptionByKey: ReadonlyMap<
51+
BackendToggleSettingKey,
52+
BackendToggleSettingOption
53+
>;
54+
numberOptionByKey: ReadonlyMap<
55+
BackendNumberSettingKey,
56+
BackendNumberSettingOption
57+
>;
58+
select: <T>(
59+
items: MenuItem<T>[],
60+
options: {
61+
message: string;
62+
subtitle: string;
63+
help: string;
64+
clearScreen: boolean;
65+
theme: UiRuntimeOptions["theme"];
66+
selectedEmphasis: "minimal";
67+
initialCursor?: number;
68+
onCursorChange: (event: { cursor: number }) => void;
69+
onInput: (raw: string) => T | undefined;
70+
},
71+
) => Promise<T | null>;
72+
copy: {
73+
previewHeading: string;
74+
backendToggleHeading: string;
75+
backendNumberHeading: string;
76+
backendDecrease: string;
77+
backendIncrease: string;
78+
backendResetCategory: string;
79+
backendBackToCategories: string;
80+
backendCategoryTitle: string;
81+
backendCategoryHelp: string;
82+
};
83+
}): Promise<{ draft: PluginConfig; focusKey: BackendSettingFocusKey }> {
84+
const {
85+
initial,
86+
category,
87+
initialFocus,
88+
ui,
89+
cloneBackendPluginConfig,
90+
buildBackendSettingsPreview,
91+
highlightPreviewToken,
92+
resolveFocusedBackendNumberKey,
93+
clampBackendNumber,
94+
formatBackendNumberValue,
95+
formatDashboardSettingState,
96+
applyBackendCategoryDefaults,
97+
getBackendCategoryInitialFocus,
98+
backendDefaults,
99+
toggleOptionByKey,
100+
numberOptionByKey,
101+
select,
102+
copy,
103+
} = params;
104+
105+
let draft = cloneBackendPluginConfig(initial);
106+
let focusKey: BackendSettingFocusKey = initialFocus;
107+
if (
108+
!focusKey ||
109+
(!category.toggleKeys.includes(focusKey as BackendToggleSettingKey) &&
110+
!category.numberKeys.includes(focusKey as BackendNumberSettingKey))
111+
) {
112+
focusKey = getBackendCategoryInitialFocus(category);
113+
}
114+
115+
const toggleOptions = category.toggleKeys
116+
.map((key) => toggleOptionByKey.get(key))
117+
.filter((option): option is BackendToggleSettingOption => !!option);
118+
const numberOptions = category.numberKeys
119+
.map((key) => numberOptionByKey.get(key))
120+
.filter((option): option is BackendNumberSettingOption => !!option);
121+
122+
while (true) {
123+
const preview = buildBackendSettingsPreview(draft, ui, focusKey, {
124+
highlightPreviewToken,
125+
});
126+
const toggleItems: MenuItem<BackendCategoryConfigAction>[] =
127+
toggleOptions.map((option, index) => {
128+
const enabled =
129+
draft[option.key] ?? backendDefaults[option.key] ?? false;
130+
return {
131+
label: `${formatDashboardSettingState(enabled)} ${index + 1}. ${option.label}`,
132+
hint: option.description,
133+
value: { type: "toggle", key: option.key },
134+
color: enabled ? "green" : "yellow",
135+
};
136+
});
137+
const numberItems: MenuItem<BackendCategoryConfigAction>[] =
138+
numberOptions.map((option) => {
139+
const rawValue =
140+
draft[option.key] ?? backendDefaults[option.key] ?? option.min;
141+
const numericValue =
142+
typeof rawValue === "number" && Number.isFinite(rawValue)
143+
? rawValue
144+
: option.min;
145+
const clampedValue = clampBackendNumber(option, numericValue);
146+
const valueLabel = formatBackendNumberValue(option, clampedValue);
147+
return {
148+
label: `${option.label}: ${valueLabel}`,
149+
hint: `${option.description} Step ${formatBackendNumberValue(option, option.step)}.`,
150+
value: { type: "bump", key: option.key, direction: 1 },
151+
color: "yellow",
152+
};
153+
});
154+
155+
const focusedNumberKey = resolveFocusedBackendNumberKey(
156+
focusKey,
157+
numberOptions,
158+
);
159+
const items: MenuItem<BackendCategoryConfigAction>[] = [
160+
{ label: copy.previewHeading, value: { type: "back" }, kind: "heading" },
161+
{
162+
label: preview.label,
163+
hint: preview.hint,
164+
value: { type: "back" },
165+
disabled: true,
166+
color: "green",
167+
hideUnavailableSuffix: true,
168+
},
169+
{ label: "", value: { type: "back" }, separator: true },
170+
{
171+
label: copy.backendToggleHeading,
172+
value: { type: "back" },
173+
kind: "heading",
174+
},
175+
...toggleItems,
176+
{ label: "", value: { type: "back" }, separator: true },
177+
{
178+
label: copy.backendNumberHeading,
179+
value: { type: "back" },
180+
kind: "heading",
181+
},
182+
...numberItems,
183+
];
184+
185+
if (numberOptions.length > 0) {
186+
items.push({ label: "", value: { type: "back" }, separator: true });
187+
items.push({
188+
label: copy.backendDecrease,
189+
value: { type: "bump", key: focusedNumberKey, direction: -1 },
190+
color: "yellow",
191+
});
192+
items.push({
193+
label: copy.backendIncrease,
194+
value: { type: "bump", key: focusedNumberKey, direction: 1 },
195+
color: "green",
196+
});
197+
}
198+
199+
items.push({ label: "", value: { type: "back" }, separator: true });
200+
items.push({
201+
label: copy.backendResetCategory,
202+
value: { type: "reset-category" },
203+
color: "yellow",
204+
});
205+
items.push({
206+
label: copy.backendBackToCategories,
207+
value: { type: "back" },
208+
color: "red",
209+
});
210+
211+
const initialCursor = items.findIndex((item) => {
212+
if (item.separator || item.disabled || item.kind === "heading")
213+
return false;
214+
if (item.value.type === "toggle" && focusKey === item.value.key)
215+
return true;
216+
if (item.value.type === "bump" && focusKey === item.value.key)
217+
return true;
218+
return false;
219+
});
220+
221+
const result = await select(items, {
222+
message: `${copy.backendCategoryTitle}: ${category.label}`,
223+
subtitle: category.description,
224+
help: copy.backendCategoryHelp,
225+
clearScreen: true,
226+
theme: ui.theme,
227+
selectedEmphasis: "minimal",
228+
initialCursor: initialCursor >= 0 ? initialCursor : undefined,
229+
onCursorChange: ({ cursor }) => {
230+
const focusedItem = items[cursor];
231+
if (
232+
focusedItem?.value.type === "toggle" ||
233+
focusedItem?.value.type === "bump"
234+
) {
235+
focusKey = focusedItem.value.key;
236+
}
237+
},
238+
onInput: (raw) => {
239+
const lower = raw.toLowerCase();
240+
if (lower === "q") return { type: "back" as const };
241+
if (lower === "r") return { type: "reset-category" as const };
242+
if (
243+
numberOptions.length > 0 &&
244+
(lower === "+" || lower === "=" || lower === "]" || lower === "d")
245+
) {
246+
return {
247+
type: "bump" as const,
248+
key: resolveFocusedBackendNumberKey(focusKey, numberOptions),
249+
direction: 1 as const,
250+
};
251+
}
252+
if (
253+
numberOptions.length > 0 &&
254+
(lower === "-" || lower === "[" || lower === "a")
255+
) {
256+
return {
257+
type: "bump" as const,
258+
key: resolveFocusedBackendNumberKey(focusKey, numberOptions),
259+
direction: -1 as const,
260+
};
261+
}
262+
const parsed = Number.parseInt(raw, 10);
263+
if (
264+
Number.isFinite(parsed) &&
265+
parsed >= 1 &&
266+
parsed <= toggleOptions.length
267+
) {
268+
const target = toggleOptions[parsed - 1];
269+
if (target) return { type: "toggle" as const, key: target.key };
270+
}
271+
return undefined;
272+
},
273+
});
274+
275+
if (!result || result.type === "back") {
276+
return { draft, focusKey };
277+
}
278+
if (result.type === "reset-category") {
279+
draft = applyBackendCategoryDefaults(draft, category);
280+
focusKey = getBackendCategoryInitialFocus(category);
281+
continue;
282+
}
283+
if (result.type === "toggle") {
284+
const currentValue =
285+
draft[result.key] ?? backendDefaults[result.key] ?? false;
286+
draft = { ...draft, [result.key]: !currentValue };
287+
focusKey = result.key;
288+
continue;
289+
}
290+
291+
const option = numberOptionByKey.get(result.key);
292+
if (!option) continue;
293+
const currentValue =
294+
draft[result.key] ?? backendDefaults[result.key] ?? option.min;
295+
const numericCurrent =
296+
typeof currentValue === "number" && Number.isFinite(currentValue)
297+
? currentValue
298+
: option.min;
299+
draft = {
300+
...draft,
301+
[result.key]: clampBackendNumber(
302+
option,
303+
numericCurrent + option.step * result.direction,
304+
),
305+
};
306+
focusKey = result.key;
307+
}
308+
}

0 commit comments

Comments
 (0)