From 933f4bb481cab7ba4d27daa2de3b1683e84f0f13 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sun, 27 Apr 2025 11:59:07 +0200 Subject: [PATCH] Add package list component --- cli/CMakeLists.txt | 2 +- cli/component.cpp | 159 ++++++++++----------------------------------- cli/component.hpp | 68 +++---------------- cli/main.cpp | 2 +- cli/page.cpp | 147 +++++++++++++++++++++++++++++++++++++++++ cli/page.hpp | 78 ++++++++++++++++++++++ 6 files changed, 273 insertions(+), 183 deletions(-) create mode 100644 cli/page.cpp create mode 100644 cli/page.hpp diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index aaf6ebe..56fc76f 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -14,7 +14,7 @@ FetchContent_Declare(toml11 ) FetchContent_MakeAvailable(toml11) -add_executable(katja-cli main.cpp component.hpp component.cpp) +add_executable(katja-cli main.cpp component.hpp component.cpp page.hpp page.cpp) target_include_directories(katja-cli PRIVATE ${Boost_INCLUDE_DIR}) target_link_libraries(katja-cli LINK_PUBLIC katja diff --git a/cli/component.cpp b/cli/component.cpp index ebd5dd3..6218379 100644 --- a/cli/component.cpp +++ b/cli/component.cpp @@ -1,154 +1,67 @@ #include "component.hpp" -#include +#include namespace katja { - ScreenContainer::ScreenContainer(std::vector> pages, std::function on_enter) - : on_enter(on_enter) - { - ftxui::Components menu_pages; - - std::transform(std::cbegin(pages), std::cend(pages), std::back_inserter(menu_entries), - [](const std::pair& pair) { return pair.first; }); - std::transform(std::cbegin(pages), std::cend(pages), std::back_inserter(this->menu_pages), - [](const std::pair& pair) { return pair.second; }); - std::copy(std::cbegin(this->menu_pages), std::cend(this->menu_pages), std::back_inserter(menu_pages)); - - ftxui::MenuOption menu_option = ftxui::MenuOption::Horizontal(); - this->menu = ftxui::Toggle(&this->menu_entries, &this->menu_selected); - - this->content = ftxui::Container::Tab(std::move(menu_pages), &this->menu_selected); - } - - ftxui::Element ScreenContainer::OnRender() - { - return ftxui::vbox({ - this->menu->Render(), - ftxui::separator(), - this->content->Render() - }); - } - - bool ScreenContainer::OnEvent(ftxui::Event event) - { - if (event == ftxui::Event::CtrlQ && this->on_enter) - { - on_enter(); - return true; - } - int previously = this->menu_selected; - bool result{ false }; - - if (event == ftxui::Event::CtrlP) - { - result = menu->OnEvent(ftxui::Event::ArrowLeft); - } - else if (event == ftxui::Event::CtrlN) - { - result = menu->OnEvent(ftxui::Event::ArrowRight); - } - if (previously != this->menu_selected) - { - this->menu_pages.at(this->menu_selected)->Load(); - } - if (!result) - { - result = this->menu_pages.at(this->menu_selected)->OnEvent(event); - } - return result; - } - - ftxui::Component Screen(std::vector> pages, std::function on_enter) - { - return std::make_shared(std::move(pages), on_enter); - } - - ftxui::Element WelcomePage::OnRender() - { - return ftxui::paragraph("Select an action in the menu."); - } - - void WelcomePage::Load() + PackageListBase::PackageListBase(const std::string& title, const std::vector& packages) + : title(title), packages(packages) { } - UpdatesPage::UpdatesPage(std::shared_ptr repository, package_database database) - : repository(repository), database(database) - { - } - - void UpdatesPage::Load() - { - this->updatable = repository->get_updates(this->database); - } - - ftxui::Element UpdatesPage::OnRender() + ftxui::Element PackageListBase::OnRender() { std::vector lines; - for (const auto& package_identifier : this->updatable) + for (const auto& package_identifier : this->packages) { auto line = ftxui::text(package_identifier.to_string()) | color(ftxui::Color::SkyBlue2); lines.push_back(line); } - ftxui::Element summary = ftxui::text(" Updates (" + std::to_string(lines.size()) + ")"); - - return ftxui::window(summary, ftxui::vbox(lines)); - } - - SearchPage::SearchPage(std::shared_ptr repository, const std::string& architecture) - : repository(repository), architecture(architecture) - { - ftxui::InputOption search_input_option = { .multiline = false }; - this->search_input = ftxui::Input(&this->needle, "Search", search_input_option); - this->type_input = ftxui::Radiobox(std::vector{ "Names", "Description" }, &this->search_type); - } - - void SearchPage::Load() - { - this->needle.erase(); - } - - ftxui::Element SearchPage::OnRender() - { - std::vector lines; - - for (const auto& package_identifier : this->search_results) + if (this->selected.has_value() && this->selected.value() < lines.size()) { - auto line = ftxui::text(package_identifier.to_string()) | color(ftxui::Color::SkyBlue2); - lines.push_back(line); + lines[this->selected.value()] |= ftxui::focus; } - ftxui::FlexboxConfig config; - config.justify_content = ftxui::FlexboxConfig::JustifyContent::SpaceAround; - config.align_items = ftxui::FlexboxConfig::AlignItems::FlexStart; - config.direction = ftxui::FlexboxConfig::Direction::Row; - config.wrap = ftxui::FlexboxConfig::Wrap::NoWrap; - config.SetGap(3, 0); + std::stringstream summary; - return ftxui::vbox({ - ftxui::flexbox({ - this->search_input->Render(), - ftxui::window(ftxui::text("Search in"), type_input->Render()) - }, config), - ftxui::vbox(lines) | ftxui::flex | ftxui::border - }); + summary << title << '(' << packages.size() << ')'; + + return ftxui::window(ftxui::text(summary.str()), ftxui::vbox(lines) | ftxui::yframe); } - bool SearchPage::OnEvent(ftxui::Event event) + bool PackageListBase::OnEvent(ftxui::Event event) { - if (event == ftxui::Event::Return) + if (event == ftxui::Event::ArrowDown) { - if (!this->needle.empty()) + if (!this->selected.has_value() && !this->packages.empty()) { - this->search_results = this->repository->search_names(this->architecture, this->needle); + this->selected = std::make_optional(0); + } + else if (this->selected.has_value() && this->selected.value() + 1 < this->packages.size()) + { + this->selected = std::make_optional(this->selected.value() + 1); } return true; } - else + else if (event == ftxui::Event::ArrowUp) { - return this->search_input->OnEvent(event); + if (!this->selected.has_value() && !this->packages.empty()) + { + this->selected = std::make_optional(0); + } + else if (this->selected.has_value() + && this->selected.value() < this->packages.size() + && this->selected.value() > 0) + { + this->selected = std::make_optional(this->selected.value() - 1); + } + return true; } return false; } + + ftxui::Component PackageList(const std::string& title, const std::vector& packages) + { + return ftxui::Make(title, packages); + } } diff --git a/cli/component.hpp b/cli/component.hpp index c419020..8ae3e67 100644 --- a/cli/component.hpp +++ b/cli/component.hpp @@ -1,76 +1,28 @@ #pragma once +#include + #include -#include #include +#include #include "katja/repository.hpp" -#include "katja/database.hpp" namespace katja { - class PageBase : public ftxui::ComponentBase + class PackageListBase : public ftxui::ComponentBase { - public: - virtual void Load() = 0; - }; - - using Page = std::shared_ptr; - using Pages = std::vector; - - class ScreenContainer final : public ftxui::ComponentBase - { - int menu_selected{ 0 }; - ftxui::Component menu; - ftxui::Component content; - std::vector menu_entries; - Pages menu_pages; - std::function on_enter; + std::string title; + const std::vector packages; + std::optional selected; public: - ScreenContainer(std::vector> pages, std::function on_enter); + PackageListBase(const std::string& title, const std::vector& packages = {}); ftxui::Element OnRender() override; bool OnEvent(ftxui::Event event) override; }; - ftxui::Component Screen(std::vector> pages, std::function on_enter); - - class WelcomePage final : public PageBase - { - public: - void Load() override; - ftxui::Element OnRender() override; - }; - - class UpdatesPage final : public PageBase - { - std::vector updatable; - std::shared_ptr repository; - package_database database; - - public: - UpdatesPage(std::shared_ptr repository, package_database database); - - void Load() override; - ftxui::Element OnRender() override; - }; - - class SearchPage final : public PageBase - { - std::string needle; - ftxui::Component search_input; - ftxui::Component type_input; - std::shared_ptr repository; - std::string architecture; - std::vector search_results; - int search_type{ 0 }; - - public: - SearchPage(std::shared_ptr repository, const std::string& architecture); - - void Load() override; - ftxui::Element OnRender() override; - bool OnEvent(ftxui::Event event) override; - }; + ftxui::Component PackageList(const std::string& title, const std::vector& packages = {}); + } diff --git a/cli/main.cpp b/cli/main.cpp index a204d2f..b080ca8 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -6,7 +6,7 @@ #include "katja/sbo.hpp" #include "katja/database.hpp" -#include "component.hpp" +#include "page.hpp" int main(int argc, const char **argv) { diff --git a/cli/page.cpp b/cli/page.cpp new file mode 100644 index 0000000..c0e1594 --- /dev/null +++ b/cli/page.cpp @@ -0,0 +1,147 @@ +#include "page.hpp" + +#include + +namespace katja +{ + ScreenContainer::ScreenContainer(std::vector> pages, std::function on_enter) + : on_enter(on_enter) + { + ftxui::Components menu_pages; + + std::transform(std::cbegin(pages), std::cend(pages), std::back_inserter(menu_entries), + [](const std::pair& pair) { return pair.first; }); + std::transform(std::cbegin(pages), std::cend(pages), std::back_inserter(this->menu_pages), + [](const std::pair& pair) { return pair.second; }); + std::copy(std::cbegin(this->menu_pages), std::cend(this->menu_pages), std::back_inserter(menu_pages)); + + ftxui::MenuOption menu_option = ftxui::MenuOption::Horizontal(); + this->menu = ftxui::Toggle(&this->menu_entries, &this->menu_selected); + + this->content = ftxui::Container::Tab(std::move(menu_pages), &this->menu_selected); + } + + ftxui::Element ScreenContainer::OnRender() + { + return ftxui::vbox({ + this->menu->Render(), + ftxui::separator(), + this->content->Render() + }); + } + + bool ScreenContainer::OnEvent(ftxui::Event event) + { + if (event == ftxui::Event::CtrlQ && this->on_enter) + { + on_enter(); + return true; + } + int previously = this->menu_selected; + bool result{ false }; + + if (event == ftxui::Event::CtrlP) + { + result = menu->OnEvent(ftxui::Event::ArrowLeft); + } + else if (event == ftxui::Event::CtrlN) + { + result = menu->OnEvent(ftxui::Event::ArrowRight); + } + if (previously != this->menu_selected) + { + this->menu_pages.at(this->menu_selected)->Load(); + } + if (!result) + { + result = this->menu_pages.at(this->menu_selected)->OnEvent(event); + } + return result; + } + + ftxui::Component Screen(std::vector> pages, std::function on_enter) + { + return std::make_shared(std::move(pages), on_enter); + } + + ftxui::Element WelcomePage::OnRender() + { + return ftxui::paragraph("Select an action in the menu."); + } + + void WelcomePage::Load() + { + } + + UpdatesPage::UpdatesPage(std::shared_ptr repository, package_database database) + : repository(repository), database(database) + { + } + + void UpdatesPage::Load() + { + this->updatable = PackageList("Updates", repository->get_updates(this->database)); + } + + ftxui::Element UpdatesPage::OnRender() + { + return this->updatable->Render(); + } + + SearchPage::SearchPage(std::shared_ptr repository, const std::string& architecture) + : repository(repository), architecture(architecture) + { + ftxui::InputOption search_input_option = { .multiline = false }; + this->search_input = ftxui::Input(&this->needle, "Search", search_input_option); + this->type_input = ftxui::Radiobox(std::vector{ "Names", "Description" }, &this->search_type); + } + + void SearchPage::Load() + { + this->needle.erase(); + } + + ftxui::Element SearchPage::OnRender() + { + std::vector lines; + + ftxui::FlexboxConfig config; + config.justify_content = ftxui::FlexboxConfig::JustifyContent::SpaceAround; + config.align_items = ftxui::FlexboxConfig::AlignItems::FlexStart; + config.direction = ftxui::FlexboxConfig::Direction::Row; + config.wrap = ftxui::FlexboxConfig::Wrap::NoWrap; + config.SetGap(3, 0); + + return ftxui::vbox({ + ftxui::flexbox({ + this->search_input->Render(), + ftxui::window(ftxui::text("Search in"), type_input->Render()) + }, config), + this->search_results->Render() | ftxui::flex + }); + } + + bool SearchPage::OnEvent(ftxui::Event event) + { + if (event == ftxui::Event::Return) + { + if (!this->needle.empty()) + { + std::vector result = + this->repository->search_names(this->architecture, this->needle); + this->search_results = PackageList("Search", std::move(result)); + } + return true; + } + else if (event == ftxui::Event::ArrowUp || event == ftxui::Event::ArrowDown) + { + this->search_results->OnEvent(event); + return true; + } + else + { + return this->search_input->OnEvent(event); + } + return false; + } +} diff --git a/cli/page.hpp b/cli/page.hpp new file mode 100644 index 0000000..66ea8a0 --- /dev/null +++ b/cli/page.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include + +#include "katja/repository.hpp" +#include "katja/database.hpp" + +#include "component.hpp" + +namespace katja +{ + class PageBase : public ftxui::ComponentBase + { + public: + virtual void Load() = 0; + }; + + using Page = std::shared_ptr; + using Pages = std::vector; + + class ScreenContainer final : public ftxui::ComponentBase + { + int menu_selected{ 0 }; + ftxui::Component menu; + ftxui::Component content; + std::vector menu_entries; + Pages menu_pages; + std::function on_enter; + + public: + ScreenContainer(std::vector> pages, std::function on_enter); + + ftxui::Element OnRender() override; + bool OnEvent(ftxui::Event event) override; + }; + + ftxui::Component Screen(std::vector> pages, std::function on_enter); + + class WelcomePage final : public PageBase + { + public: + void Load() override; + ftxui::Element OnRender() override; + }; + + class UpdatesPage final : public PageBase + { + ftxui::Component updatable = PackageList("Updates"); + std::shared_ptr repository; + package_database database; + + public: + UpdatesPage(std::shared_ptr repository, package_database database); + + void Load() override; + ftxui::Element OnRender() override; + }; + + class SearchPage final : public PageBase + { + std::string needle; + ftxui::Component search_input; + ftxui::Component type_input; + std::shared_ptr repository; + std::string architecture; + ftxui::Component search_results = PackageList("Results"); + int search_type{ 0 }; + + public: + SearchPage(std::shared_ptr repository, const std::string& architecture); + + void Load() override; + ftxui::Element OnRender() override; + bool OnEvent(ftxui::Event event) override; + }; +}