diff options
author | rtk0c <[email protected]> | 2021-05-25 23:56:02 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2021-05-25 23:56:02 -0700 |
commit | a611b22650d1e40593db4fb1bce29d925e49e932 (patch) | |
tree | 1103179b6344c368e214852f16742129642c338b /core | |
parent | bb2ab4bc5b2c9cc25ef1858ac538f2dc48af2d2c (diff) |
More work on workflow management UI
Diffstat (limited to 'core')
-rw-r--r-- | core/locale/zh_CN.json | 1 | ||||
-rw-r--r-- | core/src/Model/GlobalStates.cpp | 10 | ||||
-rw-r--r-- | core/src/Model/GlobalStates.hpp | 4 | ||||
-rw-r--r-- | core/src/Model/Project.cpp | 106 | ||||
-rw-r--r-- | core/src/Model/Project.hpp | 17 | ||||
-rw-r--r-- | core/src/Model/Workflow/Workflow.hpp | 16 | ||||
-rw-r--r-- | core/src/Model/Workflow/Workflow_Main.cpp | 14 | ||||
-rw-r--r-- | core/src/Model/Workflow/fwd.hpp | 11 | ||||
-rw-r--r-- | core/src/UI/Localization.hpp | 1 | ||||
-rw-r--r-- | core/src/UI/UI_DatabaseView.cpp | 8 | ||||
-rw-r--r-- | core/src/UI/UI_Workflows.cpp | 191 |
11 files changed, 311 insertions, 68 deletions
diff --git a/core/locale/zh_CN.json b/core/locale/zh_CN.json index 31f8827..e5df7ee 100644 --- a/core/locale/zh_CN.json +++ b/core/locale/zh_CN.json @@ -4,6 +4,7 @@ "Generic.Add": "\uf067 新建", "Generic.Edit": "\uf044 编辑", "Generic.Delete": "\uf1f8 删除", + "Generic.Rename": "\uf246 重命名", "Generic.Disconnect": "\uf127 断开连接", "Generic.Close": "\uf00d 关闭", "Generic.Dialog.Confirm": "确定", diff --git a/core/src/Model/GlobalStates.cpp b/core/src/Model/GlobalStates.cpp index 4004f4a..a9b6806 100644 --- a/core/src/Model/GlobalStates.cpp +++ b/core/src/Model/GlobalStates.cpp @@ -45,8 +45,8 @@ void GlobalStates::Init(std::filesystem::path userDataDir) auto utf8String = path.string(); globalStateInstance->mRecentProjects.push_back(RecentProject{ - .path = std::move(path), - .cachedUtf8String = std::move(utf8String), + .Path = std::move(path), + .CachedUtf8String = std::move(utf8String), }); } } @@ -85,8 +85,8 @@ void GlobalStates::ClearRecentProjects() void GlobalStates::AddRecentProject(const Project& project) { mRecentProjects.push_back(RecentProject{ - .path = project.GetPath(), - .cachedUtf8String = project.GetPath().string(), + .Path = project.GetPath(), + .CachedUtf8String = project.GetPath().string(), }); MarkDirty(); } @@ -94,7 +94,7 @@ void GlobalStates::AddRecentProject(const Project& project) void GlobalStates::MoveProjectToTop(const Project& project) { for (auto it = mRecentProjects.begin(); it != mRecentProjects.end(); ++it) { - if (it->path == project.GetPath()) { + if (it->Path == project.GetPath()) { std::rotate(it, it + 1, mRecentProjects.end()); MarkDirty(); return; diff --git a/core/src/Model/GlobalStates.hpp b/core/src/Model/GlobalStates.hpp index d88f752..6970642 100644 --- a/core/src/Model/GlobalStates.hpp +++ b/core/src/Model/GlobalStates.hpp @@ -19,8 +19,8 @@ public: struct RecentProject { - std::filesystem::path path; - std::string cachedUtf8String; + std::filesystem::path Path; + std::string CachedUtf8String; }; public: diff --git a/core/src/Model/Project.cpp b/core/src/Model/Project.cpp index 74e7142..2d7c82a 100644 --- a/core/src/Model/Project.cpp +++ b/core/src/Model/Project.cpp @@ -1,5 +1,8 @@ #include "Project.hpp" +#include "Model/Workflow/Workflow.hpp" +#include "Utils/Macros.hpp" + #include <json/reader.h> #include <json/value.h> #include <json/writer.h> @@ -25,7 +28,7 @@ void ReadItemList(ItemList<T>& list, const fs::path& filePath) Project::Project(const fs::path& rootPath) : mRootPath{ rootPath } , mRootPathString{ mRootPath.string() } - , mDb(*this) + , Database(*this) { // TODO better diagnostic const char* kInvalidFormatErr = "Failed to load project: invalid format."; @@ -59,13 +62,30 @@ Project::Project(const fs::path& rootPath) ReadItemList(Products, itemsDir / "products.json"); ReadItemList(Factories, itemsDir / "factories.json"); ReadItemList(Customers, itemsDir / "customers.json"); + + auto workflowsDir = mRootPath / "workflows"; + fs::create_directories(workflowsDir); + + for (auto& entry : fs::directory_iterator(workflowsDir)) { + if (!entry.is_regular_file()) continue; + auto& path = entry.path(); + if (path.extension() != ".cplt-workflow") continue; + + auto name = path.stem().string(); + auto [it, DISCARD] = mWorkflows.insert(name, WorkflowInfo{}); + auto& info = it.value(); + + info.Name = std::move(name); + info.PathStringCache = path.string(); + info.Path = path; + } } Project::Project(std::filesystem::path rootPath, std::string name) : mRootPath{ std::move(rootPath) } , mRootPathString{ mRootPath.string() } , mName{ std::move(name) } - , mDb(*this) + , Database(*this) { } @@ -89,14 +109,68 @@ void Project::SetName(std::string name) mName = std::move(name); } -const TransactionModel& Project::GetTransactionsModel() const +const decltype(Project::mWorkflows)& Project::GetWorkflows() const +{ + return mWorkflows; +} + +std::unique_ptr<Workflow> Project::LoadWorkflow(std::string_view name) +{ + auto iter = mWorkflows.find(name); + if (iter == mWorkflows.end()) { + return iter.value().LoadFromDisk(); + } else { + return nullptr; + } +} + +std::unique_ptr<Workflow> Project::CreateWorkflow(std::string_view name) { - return mDb; + if (mWorkflows.find(name) != mWorkflows.end()) { + // Workflow with name already exists + return nullptr; + } + + auto workflow = std::make_unique<Workflow>(); + auto [it, DISCARD] = mWorkflows.insert(name, WorkflowInfo{}); + auto& info = it.value(); + + info.Name = name; + info.Path = GetWorkflowPath(name); + + return workflow; } -TransactionModel& Project::GetTransactionsModel() +bool Project::RemoveWorkflow(std::string_view name) { - return mDb; + auto iter = mWorkflows.find(name); + if (iter == mWorkflows.end()) { + return false; + } + auto& info = iter.value(); + + fs::remove(info.Path); + mWorkflows.erase(iter); + + return true; +} + +bool Project::RenameWorkflow(std::string_view name, std::string_view newName) +{ + auto iter = mWorkflows.find(name); + if (iter == mWorkflows.end()) return false; + + auto info = std::move(iter.value()); + + auto& oldPath = info.Path; + auto newPath = GetWorkflowPath(newName); + fs::rename(oldPath, newPath); + info.Path = std::move(newPath); + + mWorkflows.insert(newName, std::move(info)); + mWorkflows.erase(iter); + + return true; } Json::Value Project::Serialize() @@ -127,3 +201,23 @@ void Project::WriteToDisk() WriteItemList(Factories, itemsDir / "factories.json"); WriteItemList(Customers, itemsDir / "customers.json"); } + +std::filesystem::path Project::GetDatabasesDirectory() const +{ + return mRootPath / "databases"; +} + +std::filesystem::path Project::GetItemsDirectory() const +{ + return mRootPath / "items"; +} + +std::filesystem::path Project::GetWorkflowsDirectory() const +{ + return mRootPath / "workflows"; +} + +std::filesystem::path Project::GetWorkflowPath(std::string_view name) const +{ + return (mRootPath / "workflows" / name).concat(".cplt-workflow"); +} diff --git a/core/src/Model/Project.hpp b/core/src/Model/Project.hpp index 998befb..5f26532 100644 --- a/core/src/Model/Project.hpp +++ b/core/src/Model/Project.hpp @@ -4,8 +4,10 @@ #include "Model/TransactionsModel.hpp" #include <json/forwards.h> +#include <tsl/array_map.h> #include <filesystem> #include <string> +#include <string_view> class Project { @@ -13,12 +15,13 @@ public: ItemList<ProductItem> Products; ItemList<FactoryItem> Factories; ItemList<CustomerItem> Customers; + TransactionModel Database; private: + tsl::array_map<char, WorkflowInfo> mWorkflows; std::filesystem::path mRootPath; std::string mRootPathString; std::string mName; - TransactionModel mDb; public: /// Load the project from a directory containing the cplt_project.json file. @@ -32,11 +35,19 @@ public: const std::filesystem::path& GetPath() const; const std::string& GetPathString() const; + std::filesystem::path GetDatabasesDirectory() const; + std::filesystem::path GetItemsDirectory() const; + std::filesystem::path GetWorkflowsDirectory() const; + std::filesystem::path GetWorkflowPath(std::string_view name) const; + const std::string& GetName() const; void SetName(std::string name); - const TransactionModel& GetTransactionsModel() const; - TransactionModel& GetTransactionsModel(); + const decltype(mWorkflows)& GetWorkflows() const; + std::unique_ptr<Workflow> LoadWorkflow(std::string_view name); + std::unique_ptr<Workflow> CreateWorkflow(std::string_view name); + bool RemoveWorkflow(std::string_view name); + bool RenameWorkflow(std::string_view name, std::string_view newName); Json::Value Serialize(); void WriteToDisk(); diff --git a/core/src/Model/Workflow/Workflow.hpp b/core/src/Model/Workflow/Workflow.hpp index 0aabcdc..99e4e90 100644 --- a/core/src/Model/Workflow/Workflow.hpp +++ b/core/src/Model/Workflow/Workflow.hpp @@ -7,6 +7,7 @@ #include <imgui_node_editor.h> #include <cstddef> #include <cstdint> +#include <filesystem> #include <iosfwd> #include <limits> #include <memory> @@ -135,14 +136,14 @@ public: void DisconnectInput(uint32_t pinId); void DrawInputPinDebugInfo(uint32_t pinId) const; - const InputPin& GetInputPin(uint32_t pinId)const ; + const InputPin& GetInputPin(uint32_t pinId) const; ImNodes::PinId GetInputPinUniqueId(uint32_t pinId) const; void ConnectOutput(uint32_t pinId, WorkflowNode& dstNode, uint32_t dstPinId); void DisconnectOutput(uint32_t pinId); - void DrawOutputPinDebugInfo(uint32_t pinId)const; - const OutputPin& GetOutputPin(uint32_t pinId)const ; + void DrawOutputPinDebugInfo(uint32_t pinId) const; + const OutputPin& GetOutputPin(uint32_t pinId) const; ImNodes::PinId GetOutputPinUniqueId(uint32_t pinId) const; virtual void Evaluate(WorkflowEvaluationContext& ctx) = 0; @@ -170,6 +171,15 @@ protected: void OnDetach(); }; +struct WorkflowInfo +{ + std::string Name; + std::string PathStringCache = Path.string(); + std::filesystem::path Path; + + std::unique_ptr<Workflow> LoadFromDisk() const; +}; + class Workflow { private: diff --git a/core/src/Model/Workflow/Workflow_Main.cpp b/core/src/Model/Workflow/Workflow_Main.cpp index c9ae328..bfe007c 100644 --- a/core/src/Model/Workflow/Workflow_Main.cpp +++ b/core/src/Model/Workflow/Workflow_Main.cpp @@ -4,6 +4,7 @@ #include <imgui_node_editor.h> #include <tsl/robin_set.h> #include <cassert> +#include <fstream> #include <iostream> #include <queue> #include <utility> @@ -319,6 +320,19 @@ void WorkflowNode::OnDetach() { } +std::unique_ptr<Workflow> WorkflowInfo::LoadFromDisk() const +{ + std::ifstream ifs(this->Path); + if (!ifs) return nullptr; + + auto workflow = std::make_unique<Workflow>(); + if (workflow->ReadFrom(ifs) == Workflow::RR_Success) { + return workflow; + } + + return nullptr; +} + const std::vector<WorkflowConnection>& Workflow::GetConnections() const { return mConnections; diff --git a/core/src/Model/Workflow/fwd.hpp b/core/src/Model/Workflow/fwd.hpp index 2323a91..b541e52 100644 --- a/core/src/Model/Workflow/fwd.hpp +++ b/core/src/Model/Workflow/fwd.hpp @@ -3,12 +3,19 @@ #include "Model/Workflow/Nodes/fwd.hpp" #include "Model/Workflow/Values/fwd.hpp" +// Evaluation.hpp +class WorkflowEvaluationError; +class WorkflowEvaluationContext; + +// SavedWorkflow.hpp +class SavedWorkflowCache; +class SavedWorkflow; + // Value.hpp class BaseValue; // Workflow.hpp class WorkflowConnection; class WorkflowNode; +struct WorkflowInfo; class Workflow; -class WorkflowEvaluationError; -class WorkflowEvaluationContext; diff --git a/core/src/UI/Localization.hpp b/core/src/UI/Localization.hpp index b421d24..c470fa7 100644 --- a/core/src/UI/Localization.hpp +++ b/core/src/UI/Localization.hpp @@ -19,6 +19,7 @@ public: BasicTranslation Add{ "Generic.Add"sv }; BasicTranslation Edit{ "Generic.Edit"sv }; BasicTranslation Delete{ "Generic.Delete"sv }; + BasicTranslation Rename{ "Generic.Rename"sv }; BasicTranslation Disconnect{ "Generic.Disconnect"sv }; BasicTranslation Close{ "Generic.Close"sv }; BasicTranslation DialogConfirm{ "Generic.Dialog.Confirm"sv }; diff --git a/core/src/UI/UI_DatabaseView.cpp b/core/src/UI/UI_DatabaseView.cpp index fb7fdfe..73ea657 100644 --- a/core/src/UI/UI_DatabaseView.cpp +++ b/core/src/UI/UI_DatabaseView.cpp @@ -468,7 +468,7 @@ private: case DeliveryDirection::WarehouseToCustomer: outgoingFlag = true; break; } - auto& stmt = mProject->GetTransactionsModel().GetDeliveries().FilterByTypeAndId; + auto& stmt = mProject->Database.GetDeliveries().FilterByTypeAndId; // clang-format off DEFER { stmt.reset(); }; // clang-format on @@ -482,7 +482,7 @@ private: int arrivalTimeCol = stmt.getColumnIndex("ArrivalTime"); while (stmt.executeStep()) { auto items = LoadItems( - mProject->GetTransactionsModel().GetDeliveries().GetItems, + mProject->Database.GetDeliveries().GetItems, stmt.getColumn(rowIdCol).getInt64()); auto summary = CreateItemsSummary(items); @@ -609,7 +609,7 @@ public: #pragma ide diagnostic ignored "HidingNonVirtualFunction" void OnProjectChanged(Project* newProject) { - auto& table = newProject->GetTransactionsModel().GetSales(); + auto& table = newProject->Database.GetSales(); Statements.GetRowCount = &table.GetRowCount; Statements.GetRows = &table.GetRows; Statements.GetItems = &table.GetItems; @@ -634,7 +634,7 @@ public: #pragma ide diagnostic ignored "HidingNonVirtualFunction" void OnProjectChanged(Project* newProject) { - auto& table = newProject->GetTransactionsModel().GetPurchases(); + auto& table = newProject->Database.GetPurchases(); Statements.GetRowCount = &table.GetRowCount; Statements.GetRows = &table.GetRows; Statements.GetItems = &table.GetItems; diff --git a/core/src/UI/UI_Workflows.cpp b/core/src/UI/UI_Workflows.cpp index a375ec0..feebe89 100644 --- a/core/src/UI/UI_Workflows.cpp +++ b/core/src/UI/UI_Workflows.cpp @@ -1,15 +1,18 @@ #include "UI.hpp" +#include "Model/Project.hpp" #include "Model/Workflow/Nodes/DocumentNodes.hpp" #include "Model/Workflow/Nodes/NumericNodes.hpp" #include "Model/Workflow/Nodes/TextNodes.hpp" #include "Model/Workflow/Nodes/UserInputNodes.hpp" #include "Model/Workflow/Workflow.hpp" #include "UI/Localization.hpp" +#include "UI/States.hpp" #include "Utils/Macros.hpp" #include <imgui.h> #include <imgui_node_editor.h> +#include <imgui_stdlib.h> #include <memory> #include <span> #include <vector> @@ -19,12 +22,12 @@ namespace ImNodes = ax::NodeEditor; namespace { enum class WorkflowCategory { - NumericCategory, - TextCategory, - DocumentsCategory, - UserInputCategory, - SystemInputCategory, - OutputCategory, + Numeric, + Text, + Documents, + UserInput, + SystemInput, + Output, }; class WorkflowDatabase @@ -42,12 +45,6 @@ public: private: std::vector<Candidate> mCandidates; -#define SUB_RANGE_ACCESS(Type, AccessorName, storage, begin, nextBegin) \ - std::span<Type> AccessorName() \ - { \ - return { &storage[begin], (size_t)(nextBegin - begin) }; \ - } - int mTextOffset; int mDocumentOffset; int mUserInputOffset; @@ -55,57 +52,64 @@ private: int mOutputOffset; public: - SUB_RANGE_ACCESS(Candidate, GetNumericNodes, mCandidates, 0, mTextOffset); - SUB_RANGE_ACCESS(Candidate, GetTextNodes, mCandidates, mTextOffset, mDocumentOffset); - SUB_RANGE_ACCESS(Candidate, GetDocumentNodes, mCandidates, mDocumentOffset, mUserInputOffset); - SUB_RANGE_ACCESS(Candidate, GetUserInputNodes, mCandidates, mUserInputOffset, mSystemInputNodes); - SUB_RANGE_ACCESS(Candidate, GetSystemInputNodes, mCandidates, mSystemInputNodes, mOutputOffset); - SUB_RANGE_ACCESS(Candidate, GetOutputNodes, mCandidates, mOutputOffset, mCandidates.size()); + // clang-format off + std::span<Candidate> GetNumericNodes() { return { &mCandidates[0], (size_t)(mTextOffset - 0) }; }; + std::span<Candidate> GetTextNodes() { return { &mCandidates[mTextOffset], (size_t)(mDocumentOffset - mTextOffset) }; }; + std::span<Candidate> GetDocumentNodes() { return { &mCandidates[mDocumentOffset], (size_t)(mUserInputOffset - mDocumentOffset) }; }; + std::span<Candidate> GetUserInputNodes() { return { &mCandidates[mUserInputOffset], (size_t)(mSystemInputNodes - mUserInputOffset) }; }; + std::span<Candidate> GetSystemInputNodes() { return { &mCandidates[mSystemInputNodes], (size_t)(mOutputOffset - mSystemInputNodes) }; }; + std::span<Candidate> GetOutputNodes() { return { &mCandidates[mOutputOffset], (size_t)(mCandidates.size() - mOutputOffset) }; }; + // clang-format on -#undef SUB_RANGE_ACCESS +public: + static WorkflowDatabase& GetInstance() + { + static WorkflowDatabase database; + return database; + } public: - void SetupCandidates() + WorkflowDatabase() { // Numeric nodes offset start at 0 mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<NumericOperationNode>(NumericOperationNode::Addition); }, .Name = "Add", - .Category = WorkflowCategory::NumericCategory, + .Category = WorkflowCategory::Numeric, }); mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<NumericOperationNode>(NumericOperationNode::Subtraction); }, .Name = "Subtract", - .Category = WorkflowCategory::NumericCategory, + .Category = WorkflowCategory::Numeric, }); mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<NumericOperationNode>(NumericOperationNode::Multiplication); }, .Name = "Multiply", - .Category = WorkflowCategory::NumericCategory, + .Category = WorkflowCategory::Numeric, }); mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<NumericOperationNode>(NumericOperationNode::Division); }, .Name = "Divide", - .Category = WorkflowCategory::NumericCategory, + .Category = WorkflowCategory::Numeric, }); mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<NumericExpressionNode>(); }, .Name = "Evaluate expression", - .Category = WorkflowCategory::NumericCategory, + .Category = WorkflowCategory::Numeric, }); mTextOffset = mCandidates.size(); mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<TextFormatterNode>(); }, .Name = "Fill template text", - .Category = WorkflowCategory::TextCategory, + .Category = WorkflowCategory::Text, }); mDocumentOffset = mCandidates.size(); mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<DocumentTemplateExpansionNode>(); }, .Name = "Document template", - .Category = WorkflowCategory::DocumentsCategory, + .Category = WorkflowCategory::Documents, }); /* Inputs */ @@ -114,13 +118,13 @@ public: mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<FormInputNode>(); }, .Name = "Input: form", - .Category = WorkflowCategory::UserInputCategory, + .Category = WorkflowCategory::UserInput, }); mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<DatabaseRowsInputNode>(); }, .Name = "Input: database rows", - .Category = WorkflowCategory::UserInputCategory, + .Category = WorkflowCategory::UserInput, }); mSystemInputNodes = mCandidates.size(); @@ -134,19 +138,21 @@ public: class WorkflowUI { private: - Workflow* mWorkflow; + std::unique_ptr<Workflow> mWorkflow; + WorkflowDatabase* mWorkflowDb; + ImNodes::EditorContext* mContext; - WorkflowDatabase mWorkflowDatabase; ImNodes::NodeId mContextMenuNodeId = 0; ImNodes::PinId mContextMenuPinId = 0; ImNodes::LinkId mContextMenuLinkId = 0; public: - WorkflowUI() + WorkflowUI(std::unique_ptr<Workflow> workflow) + : mWorkflow{ std::move(workflow) } { + mWorkflowDb = &WorkflowDatabase::GetInstance(); mContext = ImNodes::CreateEditor(); - mWorkflowDatabase.SetupCandidates(); } ~WorkflowUI() @@ -333,12 +339,12 @@ public: } }; - DisplayCandidatesCategory("Numeric nodes", mWorkflowDatabase.GetNumericNodes()); - DisplayCandidatesCategory("Text nodes", mWorkflowDatabase.GetTextNodes()); - DisplayCandidatesCategory("Document nodes", mWorkflowDatabase.GetDocumentNodes()); - DisplayCandidatesCategory("User input nodes", mWorkflowDatabase.GetUserInputNodes()); - DisplayCandidatesCategory("System input nodes", mWorkflowDatabase.GetSystemInputNodes()); - DisplayCandidatesCategory("Output nodes", mWorkflowDatabase.GetOutputNodes()); + DisplayCandidatesCategory("Numeric nodes", mWorkflowDb->GetNumericNodes()); + DisplayCandidatesCategory("Text nodes", mWorkflowDb->GetTextNodes()); + DisplayCandidatesCategory("Document nodes", mWorkflowDb->GetDocumentNodes()); + DisplayCandidatesCategory("User input nodes", mWorkflowDb->GetUserInputNodes()); + DisplayCandidatesCategory("System input nodes", mWorkflowDb->GetSystemInputNodes()); + DisplayCandidatesCategory("Output nodes", mWorkflowDb->GetOutputNodes()); ImGui::EndPopup(); } @@ -352,31 +358,130 @@ public: ImNodes::End(); } }; + +struct DrawWorkflowList_State +{ + const WorkflowInfo* SelectedWorkflow = nullptr; +}; + +void DrawWorkflowList(DrawWorkflowList_State& state) +{ + auto& uis = UIState::GetInstance(); + auto& workflows = uis.CurrentProject->GetWorkflows(); + + // TODO sort the list + for (auto& info : workflows) { + if (ImGui::Selectable(info.Name.c_str(), state.SelectedWorkflow == &info)) { + state.SelectedWorkflow = &info; + } + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::Text("Path: %s", info.PathStringCache.c_str()); + ImGui::EndTooltip(); + } + } +} } // namespace void UI::WorkflowsTab() { auto ls = LocaleStrings::Instance.get(); + auto& uis = UIState::GetInstance(); + bool openedDummy = true; static std::unique_ptr<WorkflowUI> openWorkflow; + static DrawWorkflowList_State state; + // Toolbar item: close if (ImGui::Button(ls->Close.Get(), openWorkflow == nullptr)) { openWorkflow = nullptr; } + + // Toolbar item: open... ImGui::SameLine(); if (ImGui::Button(ls->OpenWorkflow.Get())) { ImGui::OpenPopup(ls->OpenWorkflowDialogTitle.Get()); } - if (ImGui::BeginPopupModal(ls->OpenWorkflowDialogTitle.Get())) { - // TODO + if (ImGui::BeginPopupModal(ls->OpenWorkflowDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + DrawWorkflowList(state); + + if (state.SelectedWorkflow) { + auto workflow = state.SelectedWorkflow->LoadFromDisk(); + openWorkflow = std::make_unique<WorkflowUI>(std::move(workflow)); + } + ImGui::EndPopup(); } + + // Toolbar item: manage... ImGui::SameLine(); if (ImGui::Button(ls->ManageWorkflows.Get())) { ImGui::OpenPopup(ls->ManageWorkflowsDialogTitle.Get()); } - if (ImGui::BeginPopupModal(ls->ManageWorkflowsDialogTitle.Get())) { - // TODO + if (ImGui::BeginPopupModal(ls->ManageWorkflowsDialogTitle.Get(), &openedDummy, ImGuiWindowFlags_AlwaysAutoResize)) { + DrawWorkflowList(state); + + enum class NameSelectionError + { + None, + Duplicated, + Empty, + }; + static std::string newName; + static NameSelectionError newNameError; + if (ImGui::Button(ls->Rename.Get(), state.SelectedWorkflow == nullptr)) { + ImGui::OpenPopup("Rename workflow"); + newName.clear(); + } + if (ImGui::BeginPopupModal("Rename workflow")) { + if (ImGui::InputText("New name", &newName)) { + if (newName.empty()) { + newNameError = NameSelectionError::Empty; + } + + auto& workflows = uis.CurrentProject->GetWorkflows(); + if (workflows.find(newName) != workflows.end()) { + newNameError = NameSelectionError::Duplicated; + } + } + + if (ImGui::Button(ls->DialogConfirm.Get(), newName.empty())) { + auto& project = uis.CurrentProject; + project->RenameWorkflow(state.SelectedWorkflow->Name, newName); + state.SelectedWorkflow = &project->GetWorkflows()[newName]; + } + ImGui::SameLine(); + if (ImGui::Button(ls->DialogCancel.Get())) { + ImGui::CloseCurrentPopup(); + } + + switch (newNameError) { + case NameSelectionError::None: break; + case NameSelectionError::Duplicated: + ImGui::ErrorMessage("Duplicate workflow name"); + break; + case NameSelectionError::Empty: + ImGui::ErrorMessage("Workflow name cannot be empty"); + break; + } + + ImGui::EndPopup(); + } + + if (ImGui::Button(ls->Delete.Get(), state.SelectedWorkflow == nullptr)) { + ImGui::OpenPopup("Delete confirmation"); + } + if (ImGui::BeginPopupModal("Delete confirmation")) { + if (ImGui::Button(ls->DialogConfirm.Get())) { + uis.CurrentProject->RemoveWorkflow(state.SelectedWorkflow->Name); + } + ImGui::SameLine(); + if (ImGui::Button(ls->DialogCancel.Get())) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::EndPopup(); } |