feat: Add Todo Manager Component and Dashboard Navigation
- Implemented a new Todo Manager component for managing tasks, including adding, completing, and deleting todos with a user-friendly interface. - Introduced a Dashboard component to serve as the main entry point for navigating various tools, enhancing user experience. - Updated layout navigation to switch from the Main Menu to the Dashboard, improving the flow of the application.
This commit is contained in:
162
src/cli/tui.cc
162
src/cli/tui.cc
@@ -18,6 +18,7 @@
|
|||||||
#include "cli/z3ed_ascii_logo.h"
|
#include "cli/z3ed_ascii_logo.h"
|
||||||
#include "cli/service/agent/simple_chat_session.h"
|
#include "cli/service/agent/simple_chat_session.h"
|
||||||
#include "cli/service/agent/conversational_agent_service.h"
|
#include "cli/service/agent/conversational_agent_service.h"
|
||||||
|
#include "cli/service/agent/todo_manager.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
namespace cli {
|
namespace cli {
|
||||||
@@ -226,6 +227,89 @@ void GenerateSaveFileComponent(ftxui::ScreenInteractive &screen) {
|
|||||||
screen.Loop(renderer);
|
screen.Loop(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TodoManagerComponent(ftxui::ScreenInteractive &screen) {
|
||||||
|
static agent::TodoManager manager;
|
||||||
|
static bool initialized = false;
|
||||||
|
if (!initialized) {
|
||||||
|
manager.Initialize();
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string new_todo_description;
|
||||||
|
static int selected_todo = 0;
|
||||||
|
|
||||||
|
auto refresh_todos = [&]() {
|
||||||
|
auto todos = manager.GetAllTodos();
|
||||||
|
std::vector<std::string> entries;
|
||||||
|
for (const auto& item : todos) {
|
||||||
|
std::string status_emoji;
|
||||||
|
switch (item.status) {
|
||||||
|
case agent::TodoItem::Status::PENDING: status_emoji = "⏳"; break;
|
||||||
|
case agent::TodoItem::Status::IN_PROGRESS: status_emoji = "🔄"; break;
|
||||||
|
case agent::TodoItem::Status::COMPLETED: status_emoji = "✅"; break;
|
||||||
|
case agent::TodoItem::Status::BLOCKED: status_emoji = "🚫"; break;
|
||||||
|
case agent::TodoItem::Status::CANCELLED: status_emoji = "❌"; break;
|
||||||
|
}
|
||||||
|
entries.push_back(absl::StrFormat("%s [%s] %s", status_emoji, item.id, item.description));
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<std::string> todo_entries = refresh_todos();
|
||||||
|
|
||||||
|
auto input_field = Input(&new_todo_description, "New TODO description");
|
||||||
|
auto add_button = Button("Add", [&]() {
|
||||||
|
if (!new_todo_description.empty()) {
|
||||||
|
manager.CreateTodo(new_todo_description);
|
||||||
|
new_todo_description.clear();
|
||||||
|
todo_entries = refresh_todos();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto complete_button = Button("Complete", [&]() {
|
||||||
|
auto todos = manager.GetAllTodos();
|
||||||
|
if (selected_todo < todos.size()) {
|
||||||
|
manager.UpdateStatus(todos[selected_todo].id, agent::TodoItem::Status::COMPLETED);
|
||||||
|
todo_entries = refresh_todos();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto delete_button = Button("Delete", [&]() {
|
||||||
|
auto todos = manager.GetAllTodos();
|
||||||
|
if (selected_todo < todos.size()) {
|
||||||
|
manager.DeleteTodo(todos[selected_todo].id);
|
||||||
|
if (selected_todo >= todo_entries.size() -1) {
|
||||||
|
selected_todo--;
|
||||||
|
}
|
||||||
|
todo_entries = refresh_todos();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
auto back_button = Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
|
||||||
|
|
||||||
|
auto todo_menu = Menu(&todo_entries, &selected_todo);
|
||||||
|
|
||||||
|
auto container = Container::Vertical({
|
||||||
|
Container::Horizontal({input_field, add_button}),
|
||||||
|
todo_menu,
|
||||||
|
Container::Horizontal({complete_button, delete_button, back_button}),
|
||||||
|
});
|
||||||
|
|
||||||
|
auto renderer = Renderer(container, [&] {
|
||||||
|
return vbox({
|
||||||
|
text("📝 TODO Manager") | bold | center,
|
||||||
|
separator(),
|
||||||
|
hbox({text("New: "), input_field->Render(), add_button->Render()}),
|
||||||
|
separator(),
|
||||||
|
todo_menu->Render() | vscroll_indicator | frame | flex,
|
||||||
|
separator(),
|
||||||
|
hbox({complete_button->Render(), delete_button->Render(), back_button->Render()}) | center,
|
||||||
|
}) | border;
|
||||||
|
});
|
||||||
|
|
||||||
|
screen.Loop(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen) {
|
void ApplyAsarPatchComponent(ftxui::ScreenInteractive &screen) {
|
||||||
@@ -684,7 +768,7 @@ void HexViewerComponent(ftxui::ScreenInteractive &screen) {
|
|||||||
ReturnIfRomNotLoaded(screen);
|
ReturnIfRomNotLoaded(screen);
|
||||||
|
|
||||||
auto back_button =
|
auto back_button =
|
||||||
Button("Back", [&] { SwitchComponents(screen, LayoutID::kMainMenu); });
|
Button("Back", [&] { SwitchComponents(screen, LayoutID::kDashboard); });
|
||||||
|
|
||||||
static int offset = 0;
|
static int offset = 0;
|
||||||
const int lines_to_show = 20;
|
const int lines_to_show = 20;
|
||||||
@@ -739,13 +823,74 @@ void HexViewerComponent(ftxui::ScreenInteractive &screen) {
|
|||||||
if (event == Event::PageUp) offset = std::max(0, offset - (lines_to_show * 16));
|
if (event == Event::PageUp) offset = std::max(0, offset - (lines_to_show * 16));
|
||||||
if (event == Event::PageDown) offset = std::min(static_cast<int>(app_context.rom.size()) - (lines_to_show * 16), offset + (lines_to_show * 16));
|
if (event == Event::PageDown) offset = std::min(static_cast<int>(app_context.rom.size()) - (lines_to_show * 16), offset + (lines_to_show * 16));
|
||||||
if (event == Event::Character('q')) SwitchComponents(screen, LayoutID::kExit);
|
if (event == Event::Character('q')) SwitchComponents(screen, LayoutID::kExit);
|
||||||
if (event == Event::Return) SwitchComponents(screen, LayoutID::kMainMenu);
|
if (event == Event::Return) SwitchComponents(screen, LayoutID::kDashboard);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
screen.Loop(event_handler);
|
screen.Loop(event_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DashboardComponent(ftxui::ScreenInteractive &screen) {
|
||||||
|
static int selected = 0;
|
||||||
|
MenuOption option;
|
||||||
|
option.focused_entry = &selected;
|
||||||
|
auto menu = Menu(&kMainMenuEntries, &selected, option);
|
||||||
|
|
||||||
|
auto content_renderer = ftxui::Renderer([&] {
|
||||||
|
return vbox({
|
||||||
|
text(GetColoredLogo()) | center,
|
||||||
|
separator(),
|
||||||
|
text("Welcome to the z3ed Dashboard!") | center,
|
||||||
|
text("Select a tool from the menu to begin.") | center | dim,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
auto main_container = Container::Horizontal({
|
||||||
|
menu,
|
||||||
|
content_renderer
|
||||||
|
});
|
||||||
|
|
||||||
|
auto layout = Renderer(main_container, [&] {
|
||||||
|
std::string rom_info = app_context.rom.is_loaded() ? app_context.rom.title() : "No ROM";
|
||||||
|
return vbox({
|
||||||
|
hbox({
|
||||||
|
menu->Render() | size(WIDTH, EQUAL, 30) | border,
|
||||||
|
(content_renderer->Render() | center | flex) | border
|
||||||
|
}),
|
||||||
|
hbox({
|
||||||
|
text(rom_info) | bold,
|
||||||
|
filler(),
|
||||||
|
text("q: Quit | ↑/↓: Navigate | Enter: Select")
|
||||||
|
}) | border
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
auto event_handler = CatchEvent(layout, [&](Event event) {
|
||||||
|
if (event == Event::Character('q')) {
|
||||||
|
SwitchComponents(screen, LayoutID::kExit);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (event == Event::Return) {
|
||||||
|
// Still use SwitchComponents for now to maintain old behavior
|
||||||
|
switch ((MainMenuEntry)selected) {
|
||||||
|
case MainMenuEntry::kLoadRom: SwitchComponents(screen, LayoutID::kLoadRom); break;
|
||||||
|
case MainMenuEntry::kAIAgentChat: SwitchComponents(screen, LayoutID::kAIAgentChat); break;
|
||||||
|
case MainMenuEntry::kTodoManager: SwitchComponents(screen, LayoutID::kTodoManager); break;
|
||||||
|
case MainMenuEntry::kRomTools: SwitchComponents(screen, LayoutID::kRomTools); break;
|
||||||
|
case MainMenuEntry::kGraphicsTools: SwitchComponents(screen, LayoutID::kGraphicsTools); break;
|
||||||
|
case MainMenuEntry::kTestingTools: SwitchComponents(screen, LayoutID::kTestingTools); break;
|
||||||
|
case MainMenuEntry::kSettings: SwitchComponents(screen, LayoutID::kSettings); break;
|
||||||
|
case MainMenuEntry::kHelp: SwitchComponents(screen, LayoutID::kHelp); break;
|
||||||
|
case MainMenuEntry::kExit: SwitchComponents(screen, LayoutID::kExit); break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
screen.Loop(event_handler);
|
||||||
|
}
|
||||||
|
|
||||||
void MainMenuComponent(ftxui::ScreenInteractive &screen) {
|
void MainMenuComponent(ftxui::ScreenInteractive &screen) {
|
||||||
// Tracks which menu item is selected.
|
// Tracks which menu item is selected.
|
||||||
static int selected = 0;
|
static int selected = 0;
|
||||||
@@ -845,8 +990,15 @@ void MainMenuComponent(ftxui::ScreenInteractive &screen) {
|
|||||||
|
|
||||||
void ShowMain() {
|
void ShowMain() {
|
||||||
auto screen = ScreenInteractive::TerminalOutput();
|
auto screen = ScreenInteractive::TerminalOutput();
|
||||||
while (true) {
|
while (app_context.current_layout != LayoutID::kExit) {
|
||||||
|
if (app_context.rom.is_loaded() && app_context.current_layout == LayoutID::kMainMenu) {
|
||||||
|
app_context.current_layout = LayoutID::kDashboard;
|
||||||
|
}
|
||||||
|
|
||||||
switch (app_context.current_layout) {
|
switch (app_context.current_layout) {
|
||||||
|
case LayoutID::kDashboard: {
|
||||||
|
DashboardComponent(screen);
|
||||||
|
} break;
|
||||||
case LayoutID::kMainMenu: {
|
case LayoutID::kMainMenu: {
|
||||||
MainMenuComponent(screen);
|
MainMenuComponent(screen);
|
||||||
} break;
|
} break;
|
||||||
@@ -867,9 +1019,7 @@ void ShowMain() {
|
|||||||
app_context.current_layout = LayoutID::kMainMenu;
|
app_context.current_layout = LayoutID::kMainMenu;
|
||||||
} break;
|
} break;
|
||||||
case LayoutID::kTodoManager: {
|
case LayoutID::kTodoManager: {
|
||||||
// TODO: Implement TODO manager TUI
|
TodoManagerComponent(screen);
|
||||||
app_context.error_message = "TODO Manager TUI coming soon - use CLI commands for now";
|
|
||||||
app_context.current_layout = LayoutID::kError;
|
|
||||||
} break;
|
} break;
|
||||||
case LayoutID::kRomTools: {
|
case LayoutID::kRomTools: {
|
||||||
// Show submenu for ROM tools
|
// Show submenu for ROM tools
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ enum class LayoutID {
|
|||||||
kHelp,
|
kHelp,
|
||||||
kExit,
|
kExit,
|
||||||
kMainMenu,
|
kMainMenu,
|
||||||
|
kDashboard,
|
||||||
kError,
|
kError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user