Skip to content

Commit b531307

Browse files
committed
refactor: extract dashboard display panel
1 parent 0c8f45c commit b531307

2 files changed

Lines changed: 272 additions & 213 deletions

File tree

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import { stdin as input, stdout as output } from "node:process";
2+
import {
3+
type DashboardAccountSortMode,
4+
type DashboardDisplaySettings,
5+
DEFAULT_DASHBOARD_DISPLAY_SETTINGS,
6+
} from "../dashboard-settings.js";
7+
import type { UI_COPY } from "../ui/copy.js";
8+
import { getUiRuntimeOptions } from "../ui/runtime.js";
9+
import { type MenuItem, select } from "../ui/select.js";
10+
11+
export type DashboardDisplaySettingKey =
12+
| "menuShowStatusBadge"
13+
| "menuShowCurrentBadge"
14+
| "menuShowLastUsed"
15+
| "menuShowQuotaSummary"
16+
| "menuShowQuotaCooldown"
17+
| "menuShowDetailsForUnselectedRows"
18+
| "menuShowFetchStatus"
19+
| "menuHighlightCurrentRow"
20+
| "menuSortEnabled"
21+
| "menuSortPinCurrent"
22+
| "menuSortQuickSwitchVisibleRow";
23+
24+
export interface DashboardDisplaySettingOption {
25+
key: DashboardDisplaySettingKey;
26+
label: string;
27+
description: string;
28+
}
29+
30+
export type DashboardConfigAction =
31+
| { type: "toggle"; key: DashboardDisplaySettingKey }
32+
| { type: "cycle-sort-mode" }
33+
| { type: "cycle-layout-mode" }
34+
| { type: "reset" }
35+
| { type: "save" }
36+
| { type: "cancel" };
37+
38+
export interface DashboardDisplayPanelDeps {
39+
cloneDashboardSettings: (
40+
settings: DashboardDisplaySettings,
41+
) => DashboardDisplaySettings;
42+
buildAccountListPreview: (
43+
settings: DashboardDisplaySettings,
44+
ui: ReturnType<typeof getUiRuntimeOptions>,
45+
focusKey: DashboardDisplaySettingKey | "menuSortMode" | "menuLayoutMode",
46+
) => { label: string; hint?: string };
47+
formatDashboardSettingState: (enabled: boolean) => string;
48+
formatMenuSortMode: (mode: DashboardAccountSortMode) => string;
49+
resolveMenuLayoutMode: (
50+
settings?: DashboardDisplaySettings,
51+
) => "compact-details" | "expanded-rows";
52+
formatMenuLayoutMode: (mode: "compact-details" | "expanded-rows") => string;
53+
applyDashboardDefaultsForKeys: (
54+
draft: DashboardDisplaySettings,
55+
keys: readonly (keyof DashboardDisplaySettings)[],
56+
) => DashboardDisplaySettings;
57+
DASHBOARD_DISPLAY_OPTIONS: readonly DashboardDisplaySettingOption[];
58+
ACCOUNT_LIST_PANEL_KEYS: readonly (keyof DashboardDisplaySettings)[];
59+
UI_COPY: typeof UI_COPY;
60+
}
61+
62+
export async function promptDashboardDisplayPanel(
63+
initial: DashboardDisplaySettings,
64+
deps: DashboardDisplayPanelDeps,
65+
): Promise<DashboardDisplaySettings | null> {
66+
if (!input.isTTY || !output.isTTY) return null;
67+
68+
const ui = getUiRuntimeOptions();
69+
let draft = deps.cloneDashboardSettings(initial);
70+
let focusKey: DashboardDisplaySettingKey | "menuSortMode" | "menuLayoutMode" =
71+
deps.DASHBOARD_DISPLAY_OPTIONS[0]?.key ?? "menuShowStatusBadge";
72+
73+
while (true) {
74+
const preview = deps.buildAccountListPreview(draft, ui, focusKey);
75+
const optionItems: MenuItem<DashboardConfigAction>[] =
76+
deps.DASHBOARD_DISPLAY_OPTIONS.map((option, index) => {
77+
const enabled = draft[option.key] ?? true;
78+
return {
79+
label: `${deps.formatDashboardSettingState(enabled)} ${index + 1}. ${option.label}`,
80+
hint: option.description,
81+
value: { type: "toggle", key: option.key },
82+
color: enabled ? "green" : "yellow",
83+
};
84+
});
85+
const sortMode =
86+
draft.menuSortMode ??
87+
DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ??
88+
"ready-first";
89+
const sortModeItem: MenuItem<DashboardConfigAction> = {
90+
label: `Sort mode: ${deps.formatMenuSortMode(sortMode)}`,
91+
hint: "Applies when smart sort is enabled.",
92+
value: { type: "cycle-sort-mode" },
93+
color: sortMode === "ready-first" ? "green" : "yellow",
94+
};
95+
const layoutMode = deps.resolveMenuLayoutMode(draft);
96+
const layoutModeItem: MenuItem<DashboardConfigAction> = {
97+
label: `Layout: ${deps.formatMenuLayoutMode(layoutMode)}`,
98+
hint: "Compact shows one-line rows with a selected details pane.",
99+
value: { type: "cycle-layout-mode" },
100+
color: layoutMode === "compact-details" ? "green" : "yellow",
101+
};
102+
const items: MenuItem<DashboardConfigAction>[] = [
103+
{
104+
label: deps.UI_COPY.settings.previewHeading,
105+
value: { type: "cancel" },
106+
kind: "heading",
107+
},
108+
{
109+
label: preview.label,
110+
hint: preview.hint,
111+
value: { type: "cancel" },
112+
color: "green",
113+
disabled: true,
114+
hideUnavailableSuffix: true,
115+
},
116+
{ label: "", value: { type: "cancel" }, separator: true },
117+
{
118+
label: deps.UI_COPY.settings.displayHeading,
119+
value: { type: "cancel" },
120+
kind: "heading",
121+
},
122+
...optionItems,
123+
sortModeItem,
124+
layoutModeItem,
125+
{ label: "", value: { type: "cancel" }, separator: true },
126+
{
127+
label: deps.UI_COPY.settings.resetDefault,
128+
value: { type: "reset" },
129+
color: "yellow",
130+
},
131+
{
132+
label: deps.UI_COPY.settings.saveAndBack,
133+
value: { type: "save" },
134+
color: "green",
135+
},
136+
{
137+
label: deps.UI_COPY.settings.backNoSave,
138+
value: { type: "cancel" },
139+
color: "red",
140+
},
141+
];
142+
143+
const initialCursor = items.findIndex(
144+
(item) =>
145+
(item.value.type === "toggle" && item.value.key === focusKey) ||
146+
(item.value.type === "cycle-sort-mode" &&
147+
focusKey === "menuSortMode") ||
148+
(item.value.type === "cycle-layout-mode" &&
149+
focusKey === "menuLayoutMode"),
150+
);
151+
152+
const updateFocusedPreview = (cursor: number) => {
153+
const focusedItem = items[cursor];
154+
const focused =
155+
focusedItem?.value.type === "toggle"
156+
? focusedItem.value.key
157+
: focusedItem?.value.type === "cycle-sort-mode"
158+
? "menuSortMode"
159+
: focusedItem?.value.type === "cycle-layout-mode"
160+
? "menuLayoutMode"
161+
: focusKey;
162+
const nextPreview = deps.buildAccountListPreview(draft, ui, focused);
163+
const previewItem = items[1];
164+
if (!previewItem) return;
165+
previewItem.label = nextPreview.label;
166+
previewItem.hint = nextPreview.hint;
167+
};
168+
169+
const result = await select<DashboardConfigAction>(items, {
170+
message: deps.UI_COPY.settings.accountListTitle,
171+
subtitle: deps.UI_COPY.settings.accountListSubtitle,
172+
help: deps.UI_COPY.settings.accountListHelp,
173+
clearScreen: true,
174+
theme: ui.theme,
175+
selectedEmphasis: "minimal",
176+
initialCursor: initialCursor >= 0 ? initialCursor : undefined,
177+
onCursorChange: ({ cursor }) => {
178+
const focusedItem = items[cursor];
179+
if (focusedItem?.value.type === "toggle")
180+
focusKey = focusedItem.value.key;
181+
else if (focusedItem?.value.type === "cycle-sort-mode")
182+
focusKey = "menuSortMode";
183+
else if (focusedItem?.value.type === "cycle-layout-mode")
184+
focusKey = "menuLayoutMode";
185+
updateFocusedPreview(cursor);
186+
},
187+
onInput: (raw) => {
188+
const lower = raw.toLowerCase();
189+
if (lower === "q") return { type: "cancel" };
190+
if (lower === "s") return { type: "save" };
191+
if (lower === "r") return { type: "reset" };
192+
if (lower === "m") return { type: "cycle-sort-mode" };
193+
if (lower === "l") return { type: "cycle-layout-mode" };
194+
const parsed = Number.parseInt(raw, 10);
195+
if (
196+
Number.isFinite(parsed) &&
197+
parsed >= 1 &&
198+
parsed <= deps.DASHBOARD_DISPLAY_OPTIONS.length
199+
) {
200+
const target = deps.DASHBOARD_DISPLAY_OPTIONS[parsed - 1];
201+
if (target) return { type: "toggle", key: target.key };
202+
}
203+
if (parsed === deps.DASHBOARD_DISPLAY_OPTIONS.length + 1)
204+
return { type: "cycle-sort-mode" };
205+
if (parsed === deps.DASHBOARD_DISPLAY_OPTIONS.length + 2)
206+
return { type: "cycle-layout-mode" };
207+
return undefined;
208+
},
209+
});
210+
211+
if (!result || result.type === "cancel") return null;
212+
if (result.type === "save") return draft;
213+
if (result.type === "reset") {
214+
draft = deps.applyDashboardDefaultsForKeys(
215+
draft,
216+
deps.ACCOUNT_LIST_PANEL_KEYS,
217+
);
218+
focusKey = deps.DASHBOARD_DISPLAY_OPTIONS[0]?.key ?? focusKey;
219+
continue;
220+
}
221+
if (result.type === "cycle-sort-mode") {
222+
const currentMode =
223+
draft.menuSortMode ??
224+
DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortMode ??
225+
"ready-first";
226+
const nextMode: DashboardAccountSortMode =
227+
currentMode === "ready-first" ? "manual" : "ready-first";
228+
draft = {
229+
...draft,
230+
menuSortMode: nextMode,
231+
menuSortEnabled:
232+
nextMode === "ready-first"
233+
? true
234+
: (draft.menuSortEnabled ??
235+
DEFAULT_DASHBOARD_DISPLAY_SETTINGS.menuSortEnabled ??
236+
true),
237+
};
238+
focusKey = "menuSortMode";
239+
continue;
240+
}
241+
if (result.type === "cycle-layout-mode") {
242+
const currentLayout = deps.resolveMenuLayoutMode(draft);
243+
const nextLayout =
244+
currentLayout === "compact-details"
245+
? "expanded-rows"
246+
: "compact-details";
247+
draft = {
248+
...draft,
249+
menuLayoutMode: nextLayout,
250+
menuShowDetailsForUnselectedRows: nextLayout === "expanded-rows",
251+
};
252+
focusKey = "menuLayoutMode";
253+
continue;
254+
}
255+
focusKey = result.key;
256+
draft = { ...draft, [result.key]: !draft[result.key] };
257+
}
258+
}

0 commit comments

Comments
 (0)