migrate projections tab to new keybindings
This commit is contained in:
parent
4948484a46
commit
b8b6dffcb5
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();
|
return ctx.consumeAndRedraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Escape: portfolio tab clears its account filter; projections
|
// Escape: portfolio tab clears its account filter inline.
|
||||||
// tab clears its as-of date. Other tabs fall through to the
|
// Other tabs fall through to the global keymap (no global
|
||||||
// global keymap (no global Esc binding) and then to the
|
// Esc binding) and then to the tab-local fallback dispatcher
|
||||||
// tab-local fallback dispatcher (e.g. history binds Esc to
|
// (e.g. history binds Esc to `compare_cancel`, projections
|
||||||
// `compare_cancel`). When portfolio/projections migrate,
|
// binds Esc to `clear_as_of`). When portfolio migrates, its
|
||||||
// their Esc handling moves into tab-local actions too.
|
// Esc handling moves into a tab-local action too.
|
||||||
if (key.codepoint == vaxis.Key.escape) {
|
if (key.codepoint == vaxis.Key.escape) {
|
||||||
if (self.active_tab == .portfolio and self.states.portfolio.account_filter != null) {
|
if (self.active_tab == .portfolio and self.states.portfolio.account_filter != null) {
|
||||||
self.setAccountFilter(null);
|
self.setAccountFilter(null);
|
||||||
|
|
@ -1258,14 +1258,6 @@ pub const App = struct {
|
||||||
self.setStatus("Filter cleared: showing all accounts");
|
self.setStatus("Filter cleared: showing all accounts");
|
||||||
return ctx.consumeAndRedraw();
|
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
|
// Fall through — no tab-specific handler. Esc isn't
|
||||||
// globally bound, so matchAction returns null and the
|
// globally bound, so matchAction returns null and the
|
||||||
// tab-local fallback gets a chance.
|
// tab-local fallback gets a chance.
|
||||||
|
|
@ -1391,52 +1383,12 @@ pub const App = struct {
|
||||||
return ctx.consumeAndRedraw();
|
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 => {
|
.account_filter => {
|
||||||
if (self.active_tab == .portfolio) {
|
if (self.active_tab == .portfolio) {
|
||||||
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.open_account_picker);
|
tab_modules.portfolio.tab.handleAction(&self.states.portfolio, self, tab_modules.portfolio.Action.open_account_picker);
|
||||||
return ctx.consumeAndRedraw();
|
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,
|
reload_portfolio,
|
||||||
sort_col_next,
|
sort_col_next,
|
||||||
sort_col_prev,
|
sort_col_prev,
|
||||||
sort_reverse,
|
|
||||||
account_filter,
|
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 {
|
pub const KeyCombo = struct {
|
||||||
|
|
@ -161,14 +149,7 @@ pub const global_default_bindings = [_]Binding{
|
||||||
.{ .action = .reload_portfolio, .key = .{ .codepoint = 'R' } },
|
.{ .action = .reload_portfolio, .key = .{ .codepoint = 'R' } },
|
||||||
.{ .action = .sort_col_next, .key = .{ .codepoint = '>' } },
|
.{ .action = .sort_col_next, .key = .{ .codepoint = '>' } },
|
||||||
.{ .action = .sort_col_prev, .key = .{ .codepoint = '<' } },
|
.{ .action = .sort_col_prev, .key = .{ .codepoint = '<' } },
|
||||||
.{ .action = .sort_reverse, .key = .{ .codepoint = 'o' } },
|
|
||||||
.{ .action = .account_filter, .key = .{ .codepoint = 'a' } },
|
.{ .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 {
|
pub fn defaults() KeyMap {
|
||||||
|
|
|
||||||
|
|
@ -40,17 +40,16 @@ const StyledLine = tui.StyledLine;
|
||||||
|
|
||||||
// ── Tab-local action enum ─────────────────────────────────────
|
// ── Tab-local action enum ─────────────────────────────────────
|
||||||
//
|
//
|
||||||
// Projections tab keybinds (today routed through the legacy
|
// Projections tab keybinds:
|
||||||
// global `keybinds.Action` variants and the central tui.zig
|
// - `o` : toggle the actuals overlay on the projection chart
|
||||||
// switch — `o` → sort_reverse, `c` → toggle_chart, `b` →
|
// - `v` : show/hide the percentile-band chart
|
||||||
// toggle_events, `d` → projections_as_of_input. When scoped
|
// - `e` : enable/disable simulated lifecycle events
|
||||||
// keymaps land (TODO step 3), these become genuinely tab-local
|
// - `d` : open the as-of date input popup
|
||||||
// and the global enum variants for them disappear.
|
// - Esc : clear the active as-of date (back to live view)
|
||||||
|
|
||||||
pub const Action = enum {
|
pub const Action = enum {
|
||||||
/// Toggle the as-of-anchored actuals overlay on the projection
|
/// Toggle the as-of-anchored actuals overlay on the projection
|
||||||
/// chart. No-op (with status hint) when not in as-of mode.
|
/// chart. No-op (with status hint) when not in as-of mode.
|
||||||
/// Today bound to `o` via the global `sort_reverse` action.
|
|
||||||
overlay_actuals,
|
overlay_actuals,
|
||||||
/// Show / hide the percentile-band chart. Toggles
|
/// Show / hide the percentile-band chart. Toggles
|
||||||
/// `state.chart_visible`; doesn't reload data.
|
/// `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
|
/// handled by the App; the popup commit / clear path lives in
|
||||||
/// tui.zig and calls `loadData` on this tab.
|
/// tui.zig and calls `loadData` on this tab.
|
||||||
as_of_input,
|
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 ─────────────────────────────────────────
|
// ── Tab-private state ─────────────────────────────────────────
|
||||||
|
|
@ -136,14 +138,11 @@ pub const tab = struct {
|
||||||
pub const label: []const u8 = "Projections";
|
pub const label: []const u8 = "Projections";
|
||||||
|
|
||||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
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 = .overlay_actuals, .key = .{ .codepoint = 'o' } },
|
||||||
.{ .action = .toggle_chart, .key = .{ .codepoint = 'c' } },
|
.{ .action = .toggle_chart, .key = .{ .codepoint = 'v' } },
|
||||||
.{ .action = .toggle_events, .key = .{ .codepoint = 'b' } },
|
.{ .action = .toggle_events, .key = .{ .codepoint = 'e' } },
|
||||||
.{ .action = .as_of_input, .key = .{ .codepoint = 'd' } },
|
.{ .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(.{
|
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_chart = "Toggle chart visibility",
|
||||||
.toggle_events = "Toggle lifecycle events",
|
.toggle_events = "Toggle lifecycle events",
|
||||||
.as_of_input = "Set as-of date",
|
.as_of_input = "Set as-of date",
|
||||||
|
.clear_as_of = "Clear as-of date",
|
||||||
});
|
});
|
||||||
|
|
||||||
pub const status_hints: []const Action = &.{
|
pub const status_hints: []const Action = &.{
|
||||||
|
|
@ -225,6 +225,16 @@ pub const tab = struct {
|
||||||
// No setStatus — drawStatusBar replaces the whole
|
// No setStatus — drawStatusBar replaces the whole
|
||||||
// line with the prompt + hint when mode is .date_input.
|
// 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