Modules/Taskboard/public/TaskTreeView.hpp

250 lines
9.9 KiB
C++

#pragma once
#include "Breadcrumb.hpp"
#include "Button.hpp"
#include "Collapsible.hpp"
#include "ColorPicker.hpp"
#include "Containers.hpp"
#include "Dialog.hpp"
#include "KanbanBoard.hpp"
#include "Docking.hpp"
#include "Control.hpp"
#include "Label.hpp"
#include "ListView.hpp"
#include "Menus.hpp"
#include "TabView.hpp"
#include "TreeView.hpp"
#include "PageStack.hpp"
#include "PageList.hpp"
#include "SearchField.hpp"
#include "SecretsPanel.hpp"
#include "SessionPanel.hpp"
#include "Commands.hpp"
#include "SkillsPanel.hpp"
#include "TimelineScrubber.hpp"
#include "TaskBoard.hpp"
#include "TaskCard.hpp"
#include "TaskContextMenu.hpp"
#include "TaskKanban.hpp"
#include "TaskPage.hpp"
#include "TaskPanels.hpp"
#include "TaskStore.hpp"
#include "StatusChip.hpp"
#include "TaskWidget.hpp"
#include "TaskInbox.hpp"
#include "TextField.hpp"
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace tp::taskboard {
class TaskTreeView : public ui::Panel {
public:
explicit TaskTreeView(
TaskBoard& board,
ui::FileSystem& files = ui::FileSystem::local(),
const AppStorage& storage = taskboardStorage(),
std::string skillsDirectory = defaultSkillsDirectory()
);
[[nodiscard]] static std::string defaultSkillsDirectory();
[[nodiscard]] TaskWidget& taskWidget() { return mTaskWidget; }
[[nodiscard]] TaskPage& page() { return mTaskWidget.page; }
[[nodiscard]] TaskHeader& header() { return mTaskWidget.description.header; }
[[nodiscard]] TaskDetails& details() { return mTaskWidget.page.details(); }
[[nodiscard]] SessionTab& session() { return mTaskWidget.session; }
[[nodiscard]] TaskInbox& inbox() { return mInbox; }
[[nodiscard]] ui::SearchField& search() { return mSearch; }
[[nodiscard]] ui::PageStack& pages() { return mTaskWidget.taskPages; }
[[nodiscard]] ui::Breadcrumb& breadcrumb() { return mBreadcrumb; }
[[nodiscard]] ui::TreeView& graph() { return mGraph; }
[[nodiscard]] ui::KanbanBoard& kanban() { return mKanban; }
[[nodiscard]] size_t kanbanColumn(TaskStatus status) const { return mKanbanModel.columnOf(status); }
[[nodiscard]] SkillsPanel& skills() { return mSkills; }
[[nodiscard]] SecretsPanel& secrets() { return mSecrets; }
[[nodiscard]] ui::TabView& views() { return mViews; }
[[nodiscard]] ui::DockArea& dock() { return mDock; }
[[nodiscard]] ui::DockPanel& taskPanel() { return mTaskPanel; }
[[nodiscard]] ui::DockPanel& taskListPanel() { return mTaskListPanel; }
[[nodiscard]] ui::PageList& taskList() { return mTaskList; }
[[nodiscard]] ui::ListView& taskListItems() { return mTaskListItems; }
[[nodiscard]] ui::ConfirmDialog& graphDeleteConfirm() { return mGraphDeleteConfirm; }
[[nodiscard]] TaskContextMenu& contextMenu() { return mContextMenu; }
[[nodiscard]] const TaskClipboard& clipboard() const { return mClipboard; }
[[nodiscard]] TaskDescriptionPanel& description() { return mTaskWidget.description; }
[[nodiscard]] TaskMessagesPanel& messages() { return mTaskWidget.messages; }
[[nodiscard]] TaskWorkspacePanel& workspace() { return mTaskWidget.workspace; }
[[nodiscard]] ui::ScrollView& sessionScroll() { return mTaskWidget.sessionScroll; }
[[nodiscard]] ui::FullViewFrame* zoomFrame() const { return mZoomFrame; }
[[nodiscard]] ui::TimelineScrubber& timeline() { return mTimeline; }
void setReadOnly(bool readOnly);
void showTimeline(bool shown);
[[nodiscard]] bool isTimelineShown() const { return mTimeline.isVisible(); }
[[nodiscard]] bool isReadOnly() const { return mReadOnly; }
[[nodiscard]] bool isInGraph(ui::NodeId id) const;
[[nodiscard]] std::optional<ui::NodeId> currentTask() const { return mTaskWidget.page.shownTask(); }
void openTask(std::optional<ui::NodeId> id);
void openTerminal(ui::NodeId id);
void createTask();
void selectRoot(std::optional<ui::NodeId> root);
void toggleRoot();
void setStore(TaskStore* store);
void setSecretsStore(SecretsStore* store) { mSecrets.setStore(store); }
void setAgentOptions(AgentOptions options) { session().setAgentOptions(std::move(options)); }
void showConflicts(const std::vector<std::string>& summaries);
void showSaveState(size_t unsavedChanges, bool saving);
void boardReloaded();
void attachMenus(ui::MenuBar& menuBar, std::function<void()> quit);
void openPanels();
void openTaskPanel();
void setOnBoardEdited(std::function<void()> callback) { mOnBoardEdited = std::move(callback); }
void setOnSave(std::function<void()> callback) { mOnSave = std::move(callback); }
void setOnOpenSession(std::function<void(ui::NodeId)> callback);
ui::Menu fileMenu;
ui::Menu editMenu;
ui::Menu viewMenu;
ui::Menu connectionMenu;
ui::Label status;
ui::StatusChip connectionStatus;
[[nodiscard]] std::vector<ui::SearchResult> searchResults(const std::string& query);
protected:
void onEvent(ui::Event& event) override;
void filterDescendantEvent(ui::Event& event) override;
private:
enum class BoardCommand { NewTask, OpenSession, Save, Search, Copy, Paste, DeleteTask, ToggleRoot };
void openActivity(const TaskActivity& entry);
void buildMenus();
[[nodiscard]] std::string rootMenuTitle() const;
void buildGraph();
void buildKanban();
void bindTaskWidget();
void updateKanbanCard(ui::Card& card) const;
void kanbanMoved(const ui::KanbanMove& move);
void refreshKanban();
void refreshTaskList();
void showTaskListDetail(ui::NodeId id);
void defineCommands();
void graphSelectionChanged();
void currentPageChanged();
void reportEditing(std::optional<std::pair<ui::NodeId, TaskField>> editing);
enum class SelectionSource { Board, Graph, Kanban };
void showFullView(ui::FullViewFrame& frame, std::optional<ui::NodeId> id, SelectionSource source);
void zoomInto(ui::FullViewFrame& frame);
void zoomOutOf(ui::FullViewFrame& frame);
void leaveZoom();
void focusTask(ui::NodeId id, SelectionSource source);
void syncSelection(std::optional<ui::NodeId> id);
void mainViewChanged(size_t index);
void focusCreatedTask(ui::NodeId id);
void createChildTask(ui::NodeId parent);
void createTaskAt(std::optional<ui::NodeId> parent, std::optional<TaskStatus> status);
[[nodiscard]] TaskContextMenu::Actions contextMenuActions();
void showContextMenu(TaskMenuTarget target, Vec2F windowPoint);
void copyTasks(const std::vector<ui::NodeId>& ids);
void pasteTasks(std::optional<ui::NodeId> parent, std::optional<TaskStatus> status);
void setTasksStatus(const std::vector<ui::NodeId>& ids, TaskStatus status);
[[nodiscard]] std::vector<ui::NodeId> mainViewSelection() const;
[[nodiscard]] bool isMainViewFocused();
void reparentTask(ui::NodeId child, ui::NodeId parent);
[[nodiscard]] std::unique_ptr<ui::Card> createCard(ui::TreeItemId id);
void updateCard(ui::Card& card) const;
void refreshGraph();
void showPath(const std::vector<std::optional<ui::NodeId>>& path);
void pathChosen(size_t index);
void deleteCurrentTask();
void askDeleteTasks(const std::vector<ui::NodeId>& ids);
void deleteTasks(const std::vector<ui::NodeId>& ids);
void focusMainView();
[[nodiscard]] std::vector<ui::NodeId> selectedTasks() const;
void boardEdited();
void refreshSummary();
[[nodiscard]] std::vector<std::optional<ui::NodeId>> pathTo(std::optional<ui::NodeId> id) const;
[[nodiscard]] std::string pageTitle(std::optional<ui::NodeId> id) const;
TaskBoard& mBoard;
TaskStore* mStore = nullptr;
std::optional<ui::NodeId> mDisplayedRoot;
ui::Menu mRootMenu;
ui::Menu mRecentRootsMenu;
ui::PopupButton* mRootMenuButton = nullptr;
TaskInbox mInbox;
std::optional<TaskActivity> mPendingActivity;
ui::SearchField mSearch;
unsigned mSearchGeneration = 0;
std::vector<ui::NodeId> mSearchChoices;
class GraphModel : public ui::TreeModel {
public:
explicit GraphModel(const TaskTreeView& view) :
mView(view) {}
[[nodiscard]] std::vector<ui::TreeItemId> roots() const override;
[[nodiscard]] std::vector<ui::TreeItemId> children(ui::TreeItemId id) const override;
[[nodiscard]] std::vector<std::pair<ui::TreeItemId, ui::TreeItemId>> crossLinks() const override;
[[nodiscard]] bool canReparent(ui::TreeItemId child, ui::TreeItemId parent) const override;
private:
const TaskTreeView& mView;
};
SelectionSource mSelectionSource = SelectionSource::Board;
bool mSyncingSelection = false;
GraphModel mGraphModel{ *this };
ui::TreeView mGraph{ mGraphModel };
KanbanColumnSettings mKanbanColumns;
TaskKanbanModel mKanbanModel{ mBoard, mKanbanColumns };
ui::KanbanBoard mKanban{ mKanbanModel };
SkillsPanel mSkills;
SecretsPanel mSecrets;
ui::TabView mViews;
ui::DockArea mDock;
ui::DockPanel mTaskPanel{ "Task", ui::DockSide::Right };
ui::Widget mTaskListRoot;
ui::Widget mTaskListControls;
ui::ListView mTaskListItems{ ui::ListView::Mode::Single };
ui::PageList mTaskList{ "Tasks", mTaskListRoot };
ui::Dropdown mTaskListSort{ { "Updated time", "Created time" } };
ui::Dropdown mTaskListStatus{
{ "All statuses", "Backlog", "Queued", "In progress", "Daemon", "Review", "Done", "Blocked" }
};
std::vector<ui::NodeId> mTaskListIds;
std::optional<ui::NodeId> mTaskListShown;
ui::Widget mTaskListDetail;
ui::Label mTaskListSummary;
ui::Label mTaskListDescription;
ui::Button mTaskListOpen{ "Open task" };
ui::DockPanel mTaskListPanel{ "Tasks", ui::DockSide::Left };
TaskWidget mTaskWidget;
ui::FullViewFrame* mZoomFrame = nullptr;
bool mTaskPanelOpenBeforeZoom = false;
ui::Widget mPathArea;
ui::Widget mPathBar;
ui::Breadcrumb mBreadcrumb;
std::vector<std::optional<ui::NodeId>> mPath;
ui::Commands<BoardCommand> mCommands;
ui::ConfirmDialog mGraphDeleteConfirm;
TaskClipboard mClipboard;
TaskContextMenu mContextMenu{ mBoard, mClipboard, contextMenuActions() };
ui::TimelineScrubber mTimeline;
bool mReadOnly = false;
ui::Panel mStatusArea{ ui::Panel::Surface::None };
ui::StatusChip mSaveState;
std::function<void()> mQuit;
std::function<void()> mOnBoardEdited;
std::function<void()> mOnSave;
std::function<void(ui::NodeId)> mOnOpenSession;
};
}