migrate projections tab to new keybindings
This commit is contained in:
parent
4b4b954a64
commit
dda56e3c97
3 changed files with 29 additions and 86 deletions
60
src/tui.zig
60
src/tui.zig
|
|
@ -1243,12 +1243,12 @@ pub const App = struct {
|
|||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
|
||||
// Escape: portfolio tab clears its account filter; projections
|
||||
// tab clears its as-of date. Other tabs fall through to the
|
||||
// global keymap (no global Esc binding) and then to the
|
||||
// tab-local fallback dispatcher (e.g. history binds Esc to
|
||||
// `compare_cancel`). When portfolio/projections migrate,
|
||||
// their Esc handling moves into tab-local actions too.
|
||||
// Escape: portfolio tab clears its account filter inline.
|
||||
// Other tabs fall through to the global keymap (no global
|
||||
// Esc binding) and then to the tab-local fallback dispatcher
|
||||
// (e.g. history binds Esc to `compare_cancel`, projections
|
||||
// binds Esc to `clear_as_of`). When portfolio migrates, its
|
||||
// Esc handling moves into a tab-local action too.
|
||||
if (key.codepoint == vaxis.Key.escape) {
|
||||
if (self.active_tab == .portfolio and self.states.portfolio.account_filter != null) {
|
||||
self.setAccountFilter(null);
|
||||
|
|
@ -1258,14 +1258,6 @@ pub const App = struct {
|
|||
self.setStatus("Filter cleared: showing all accounts");
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
if (self.active_tab == .projections and self.states.projections.as_of != null) {
|
||||
self.states.projections.as_of = null;
|
||||
self.states.projections.as_of_requested = null;
|
||||
self.states.projections.overlay_actuals = false;
|
||||
tab_modules.projections.tab.reload(&self.states.projections, self) catch {};
|
||||
self.setStatus("As-of cleared — showing live");
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
// Fall through — no tab-specific handler. Esc isn't
|
||||
// globally bound, so matchAction returns null and the
|
||||
// tab-local fallback gets a chance.
|
||||
|
|
@ -1391,52 +1383,12 @@ pub const App = struct {
|
|||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.sort_reverse => {
|
||||
// The `o` keybind dual-dispatches by active tab:
|
||||
// - portfolio → flip the sort direction
|
||||
// - projections → toggle the actuals-overlay
|
||||
// `matchAction` is first-match-wins so we can't have
|
||||
// separate Action variants share a codepoint; routing
|
||||
// via the active tab in the handler is the project's
|
||||
// existing pattern (see also `s` → compare_select /
|
||||
// select_symbol). The action name stays `sort_reverse`
|
||||
// because portfolio was the first consumer.
|
||||
if (self.active_tab == .portfolio) {
|
||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.sort_reverse);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
if (self.active_tab == .projections) {
|
||||
tab_modules.projections.tab.handleAction(&self.states.projections, self, tab_modules.projections.Action.overlay_actuals);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.account_filter => {
|
||||
if (self.active_tab == .portfolio) {
|
||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.open_account_picker);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.toggle_chart => {
|
||||
if (self.active_tab == .projections) {
|
||||
tab_modules.projections.tab.handleAction(&self.states.projections, self, tab_modules.projections.Action.toggle_chart);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.toggle_events => {
|
||||
if (self.active_tab == .projections) {
|
||||
tab_modules.projections.tab.handleAction(&self.states.projections, self, tab_modules.projections.Action.toggle_events);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
.projections_as_of_input => {
|
||||
// Only meaningful on the projections tab. Other tabs
|
||||
// let the same key flow to their own handlers (none
|
||||
// currently bind plain 'd').
|
||||
if (self.active_tab == .projections) {
|
||||
tab_modules.projections.tab.handleAction(&self.states.projections, self, tab_modules.projections.Action.as_of_input);
|
||||
return ctx.consumeAndRedraw();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,19 +29,7 @@ pub const Action = enum {
|
|||
reload_portfolio,
|
||||
sort_col_next,
|
||||
sort_col_prev,
|
||||
sort_reverse,
|
||||
account_filter,
|
||||
toggle_chart,
|
||||
toggle_events,
|
||||
/// Projections tab: open the as-of date input popup. Default: 'd'.
|
||||
/// Accepts YYYY-MM-DD, N[WMQY] shortcuts (1W, 1M, 3M, 1Q, 1Y), or 'live'.
|
||||
/// Empty input + Enter returns to live. See `parseAsOfDate` in
|
||||
/// `src/commands/common.zig`.
|
||||
///
|
||||
/// To return to live without opening the popup, press Esc on the
|
||||
/// projections tab while an as-of date is active. That path is
|
||||
/// intercepted directly in `tui.zig` — no separate keybind action.
|
||||
projections_as_of_input,
|
||||
};
|
||||
|
||||
pub const KeyCombo = struct {
|
||||
|
|
@ -161,14 +149,7 @@ pub const global_default_bindings = [_]Binding{
|
|||
.{ .action = .reload_portfolio, .key = .{ .codepoint = 'R' } },
|
||||
.{ .action = .sort_col_next, .key = .{ .codepoint = '>' } },
|
||||
.{ .action = .sort_col_prev, .key = .{ .codepoint = '<' } },
|
||||
.{ .action = .sort_reverse, .key = .{ .codepoint = 'o' } },
|
||||
.{ .action = .account_filter, .key = .{ .codepoint = 'a' } },
|
||||
.{ .action = .toggle_chart, .key = .{ .codepoint = 'v' } },
|
||||
.{ .action = .toggle_events, .key = .{ .codepoint = 'e' } },
|
||||
// Projections-tab date-picker popup. `d` opens the popup; to
|
||||
// clear an active as-of date, press Esc while on the projections
|
||||
// tab (intercepted in `tui.zig` before `matchAction`).
|
||||
.{ .action = .projections_as_of_input, .key = .{ .codepoint = 'd' } },
|
||||
};
|
||||
|
||||
pub fn defaults() KeyMap {
|
||||
|
|
|
|||
|
|
@ -40,17 +40,16 @@ const StyledLine = tui.StyledLine;
|
|||
|
||||
// ── Tab-local action enum ─────────────────────────────────────
|
||||
//
|
||||
// Projections tab keybinds (today routed through the legacy
|
||||
// global `keybinds.Action` variants and the central tui.zig
|
||||
// switch — `o` → sort_reverse, `c` → toggle_chart, `b` →
|
||||
// toggle_events, `d` → projections_as_of_input. When scoped
|
||||
// keymaps land (TODO step 3), these become genuinely tab-local
|
||||
// and the global enum variants for them disappear.
|
||||
// Projections tab keybinds:
|
||||
// - `o` : toggle the actuals overlay on the projection chart
|
||||
// - `v` : show/hide the percentile-band chart
|
||||
// - `e` : enable/disable simulated lifecycle events
|
||||
// - `d` : open the as-of date input popup
|
||||
// - Esc : clear the active as-of date (back to live view)
|
||||
|
||||
pub const Action = enum {
|
||||
/// Toggle the as-of-anchored actuals overlay on the projection
|
||||
/// chart. No-op (with status hint) when not in as-of mode.
|
||||
/// Today bound to `o` via the global `sort_reverse` action.
|
||||
overlay_actuals,
|
||||
/// Show / hide the percentile-band chart. Toggles
|
||||
/// `state.chart_visible`; doesn't reload data.
|
||||
|
|
@ -63,6 +62,9 @@ pub const Action = enum {
|
|||
/// handled by the App; the popup commit / clear path lives in
|
||||
/// tui.zig and calls `loadData` on this tab.
|
||||
as_of_input,
|
||||
/// Clear the active as-of date and return to the live view.
|
||||
/// No-op when no as-of date is set. Bound to Esc.
|
||||
clear_as_of,
|
||||
};
|
||||
|
||||
// ── Tab-private state ─────────────────────────────────────────
|
||||
|
|
@ -136,14 +138,11 @@ pub const tab = struct {
|
|||
pub const label: []const u8 = "Projections";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
// Today's keybinds are in the global keymap (sort_reverse,
|
||||
// toggle_chart, toggle_events, projections_as_of_input).
|
||||
// These per-tab declarations become authoritative when
|
||||
// scoped keymaps land.
|
||||
.{ .action = .overlay_actuals, .key = .{ .codepoint = 'o' } },
|
||||
.{ .action = .toggle_chart, .key = .{ .codepoint = 'c' } },
|
||||
.{ .action = .toggle_events, .key = .{ .codepoint = 'b' } },
|
||||
.{ .action = .toggle_chart, .key = .{ .codepoint = 'v' } },
|
||||
.{ .action = .toggle_events, .key = .{ .codepoint = 'e' } },
|
||||
.{ .action = .as_of_input, .key = .{ .codepoint = 'd' } },
|
||||
.{ .action = .clear_as_of, .key = .{ .codepoint = vaxis.Key.escape } },
|
||||
};
|
||||
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).init(.{
|
||||
|
|
@ -151,6 +150,7 @@ pub const tab = struct {
|
|||
.toggle_chart = "Toggle chart visibility",
|
||||
.toggle_events = "Toggle lifecycle events",
|
||||
.as_of_input = "Set as-of date",
|
||||
.clear_as_of = "Clear as-of date",
|
||||
});
|
||||
|
||||
pub const status_hints: []const Action = &.{
|
||||
|
|
@ -225,6 +225,16 @@ pub const tab = struct {
|
|||
// No setStatus — drawStatusBar replaces the whole
|
||||
// line with the prompt + hint when mode is .date_input.
|
||||
},
|
||||
.clear_as_of => {
|
||||
// No-op when no as-of date is set. Returns to the
|
||||
// live view by clearing as-of state and reloading.
|
||||
if (state.as_of == null) return;
|
||||
state.as_of = null;
|
||||
state.as_of_requested = null;
|
||||
state.overlay_actuals = false;
|
||||
tab.reload(state, app) catch {};
|
||||
app.setStatus("As-of cleared — showing live");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue