aboutsummaryrefslogtreecommitdiff
path: root/core/src
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2021-04-28 15:18:51 -0700
committerrtk0c <[email protected]>2021-04-28 15:18:51 -0700
commit00fd95526677d670d002ca81069636f0f74b91f7 (patch)
treea783f05be218a58c2b78175425f7576664c3f1a9 /core/src
parentb7d5b514e7bffd3149a99bc7f1424f8251876d85 (diff)
Code cleanup, fix database view paging and selection
Diffstat (limited to 'core/src')
-rw-r--r--core/src/Model/TransactionsModel.cpp6
-rw-r--r--core/src/Model/TransactionsModel.hpp1
-rw-r--r--core/src/Model/Workflow/Nodes/DocumentNodes.cpp2
-rw-r--r--core/src/Model/Workflow/Nodes/NumericNodes.cpp4
-rw-r--r--core/src/Model/Workflow/Nodes/TextNodes.cpp2
-rw-r--r--core/src/Model/Workflow/Nodes/UserInputNodes.cpp4
-rw-r--r--core/src/Model/Workflow/Value_RTTI.cpp2
-rw-r--r--core/src/Model/Workflow/Values/BasicValues.cpp23
-rw-r--r--core/src/Model/Workflow/Values/BasicValues.hpp3
-rw-r--r--core/src/Model/Workflow/Workflow.hpp21
-rw-r--r--core/src/Model/Workflow/Workflow_Main.cpp69
-rw-r--r--core/src/Model/Workflow/Workflow_RTTI.cpp3
-rw-r--r--core/src/UI/UI_DatabaseView.cpp22
-rw-r--r--core/src/UI/UI_Items.cpp36
-rw-r--r--core/src/UI/UI_Workflows.cpp31
15 files changed, 170 insertions, 59 deletions
diff --git a/core/src/Model/TransactionsModel.cpp b/core/src/Model/TransactionsModel.cpp
index 6035528..968f0c0 100644
--- a/core/src/Model/TransactionsModel.cpp
+++ b/core/src/Model/TransactionsModel.cpp
@@ -9,9 +9,9 @@ namespace fs = std::filesystem;
SalesTable::SalesTable(TransactionModel& db)
// language=SQLite
- : GetRowsStatement(db.GetSQLite(), R"""(
-SELECT * FROM Sales WHERE rowid >= ? AND rowid < ?
-)""")
+ : GetRowCountStatement(db.GetSQLite(), "SELECT Count(*) FROM Sales")
+ // language=SQLite
+ , GetRowsStatement(db.GetSQLite(), "SELECT * FROM Sales WHERE rowid >= ? AND rowid < ?")
// language=SQLite
// TODO
, FilterRowsStatement(db.GetSQLite(), R"""(
diff --git a/core/src/Model/TransactionsModel.hpp b/core/src/Model/TransactionsModel.hpp
index beed663..117c27a 100644
--- a/core/src/Model/TransactionsModel.hpp
+++ b/core/src/Model/TransactionsModel.hpp
@@ -8,6 +8,7 @@
class SalesTable {
public:
+ SQLite::Statement GetRowCountStatement;
SQLite::Statement GetRowsStatement;
SQLite::Statement FilterRowsStatement;
diff --git a/core/src/Model/Workflow/Nodes/DocumentNodes.cpp b/core/src/Model/Workflow/Nodes/DocumentNodes.cpp
index 66d2eae..255d446 100644
--- a/core/src/Model/Workflow/Nodes/DocumentNodes.cpp
+++ b/core/src/Model/Workflow/Nodes/DocumentNodes.cpp
@@ -8,7 +8,7 @@ bool DocumentTemplateExpansionNode::IsInstance(const WorkflowNode* node) {
}
DocumentTemplateExpansionNode::DocumentTemplateExpansionNode()
- : WorkflowNode(TransformType, KD_DocumentTemplateExpansion) {
+ : WorkflowNode(KD_DocumentTemplateExpansion) {
}
void DocumentTemplateExpansionNode::Evaluate(WorkflowEvaluationContext& ctx) {
diff --git a/core/src/Model/Workflow/Nodes/NumericNodes.cpp b/core/src/Model/Workflow/Nodes/NumericNodes.cpp
index 1722224..f054421 100644
--- a/core/src/Model/Workflow/Nodes/NumericNodes.cpp
+++ b/core/src/Model/Workflow/Nodes/NumericNodes.cpp
@@ -33,7 +33,7 @@ bool NumericOperationNode::IsInstance(const WorkflowNode* node) {
}
NumericOperationNode::NumericOperationNode(OperationType type)
- : WorkflowNode(TransformType, OperationTypeToNodeKind(type))
+ : WorkflowNode(OperationTypeToNodeKind(type))
, mType{ type } {
mInputs.resize(2);
mInputs[0].MatchingType = BaseValue::KD_Numeric;
@@ -79,7 +79,7 @@ bool NumericExpressionNode::IsInstance(const WorkflowNode* node) {
}
NumericExpressionNode::NumericExpressionNode()
- : WorkflowNode(TransformType, KD_NumericExpression) {
+ : WorkflowNode(KD_NumericExpression) {
}
void NumericExpressionNode::Evaluate(WorkflowEvaluationContext& ctx) {
diff --git a/core/src/Model/Workflow/Nodes/TextNodes.cpp b/core/src/Model/Workflow/Nodes/TextNodes.cpp
index 3852c66..72bd666 100644
--- a/core/src/Model/Workflow/Nodes/TextNodes.cpp
+++ b/core/src/Model/Workflow/Nodes/TextNodes.cpp
@@ -49,7 +49,7 @@ bool TextFormatterNode::IsInstance(const WorkflowNode* node) {
}
TextFormatterNode::TextFormatterNode()
- : WorkflowNode(TransformType, KD_TextFormatting) {
+ : WorkflowNode(KD_TextFormatting) {
}
int TextFormatterNode::GetElementCount() const {
diff --git a/core/src/Model/Workflow/Nodes/UserInputNodes.cpp b/core/src/Model/Workflow/Nodes/UserInputNodes.cpp
index f59226a..435cf6d 100644
--- a/core/src/Model/Workflow/Nodes/UserInputNodes.cpp
+++ b/core/src/Model/Workflow/Nodes/UserInputNodes.cpp
@@ -8,7 +8,7 @@ bool FormInputNode::IsInstance(const WorkflowNode* node) {
}
FormInputNode::FormInputNode()
- : WorkflowNode(InputType, KD_FormInput) {
+ : WorkflowNode(KD_FormInput) {
}
void FormInputNode::Evaluate(WorkflowEvaluationContext& ctx) {
@@ -19,7 +19,7 @@ bool DatabaseRowsInputNode::IsInstance(const WorkflowNode* node) {
}
DatabaseRowsInputNode::DatabaseRowsInputNode()
- : WorkflowNode(InputType, KD_DatabaseRowsInput) {
+ : WorkflowNode(KD_DatabaseRowsInput) {
}
void DatabaseRowsInputNode::Evaluate(WorkflowEvaluationContext& ctx) {
diff --git a/core/src/Model/Workflow/Value_RTTI.cpp b/core/src/Model/Workflow/Value_RTTI.cpp
index f2c8f92..2a1ac39 100644
--- a/core/src/Model/Workflow/Value_RTTI.cpp
+++ b/core/src/Model/Workflow/Value_RTTI.cpp
@@ -17,6 +17,6 @@ std::unique_ptr<BaseValue> BaseValue::CreateByKind(BaseValue::Kind kind) {
case KD_Numeric: return std::make_unique<NumericValue>();
case KD_Text: return std::make_unique<TextValue>();
case KD_DateTime: return std::make_unique<DateTimeValue>();
- case InvalidKind: return nullptr;
+ case InvalidKind: UNREACHABLE;
}
}
diff --git a/core/src/Model/Workflow/Values/BasicValues.cpp b/core/src/Model/Workflow/Values/BasicValues.cpp
index fd70acd..11d30b7 100644
--- a/core/src/Model/Workflow/Values/BasicValues.cpp
+++ b/core/src/Model/Workflow/Values/BasicValues.cpp
@@ -1,6 +1,7 @@
#include "BasicValues.hpp"
#include <charconv>
+#include <limits>
bool NumericValue::IsInstance(const BaseValue* value) {
return value->GetKind() == KD_Numeric;
@@ -10,17 +11,31 @@ NumericValue::NumericValue()
: BaseValue(BaseValue::KD_Numeric) {
}
-std::string NumericValue::GetString() const {
- char buf[64];
- auto res = std::to_chars(buf, buf + std::size(buf), mValue);
+template <class T>
+static std::string NumberToString(T value) {
+ constexpr auto kSize = std::numeric_limits<T>::max_digits10;
+ char buf[kSize];
+
+ auto res = std::to_chars(buf, buf + kSize, value);
if (res.ec == std::errc()) {
return std::string(buf, res.ptr);
} else {
- // TODO larger buffer
return "<err>";
}
}
+std::string NumericValue::GetTruncatedString() const {
+ return ::NumberToString((int64_t)mValue);
+}
+
+std::string NumericValue::GetRoundedString() const {
+ return ::NumberToString((int64_t)std::round(mValue));
+}
+
+std::string NumericValue::GetString() const {
+ return ::NumberToString(mValue);
+}
+
int64_t NumericValue::GetInt() const {
return static_cast<int64_t>(mValue);
}
diff --git a/core/src/Model/Workflow/Values/BasicValues.hpp b/core/src/Model/Workflow/Values/BasicValues.hpp
index a116c8c..e2e990d 100644
--- a/core/src/Model/Workflow/Values/BasicValues.hpp
+++ b/core/src/Model/Workflow/Values/BasicValues.hpp
@@ -14,7 +14,10 @@ public:
static bool IsInstance(const BaseValue* value);
NumericValue();
+ std::string GetTruncatedString() const;
+ std::string GetRoundedString() const;
std::string GetString() const;
+
int64_t GetInt() const;
double GetValue() const;
void SetValue(double value);
diff --git a/core/src/Model/Workflow/Workflow.hpp b/core/src/Model/Workflow/Workflow.hpp
index db341af..139a96e 100644
--- a/core/src/Model/Workflow/Workflow.hpp
+++ b/core/src/Model/Workflow/Workflow.hpp
@@ -113,7 +113,7 @@ public:
static const char* FormatType(Type type);
static std::unique_ptr<WorkflowNode> CreateByKind(Kind kind);
- WorkflowNode(Type type, Kind kind);
+ WorkflowNode(Kind kind);
virtual ~WorkflowNode() = default;
WorkflowNode(const WorkflowNode&) = delete;
@@ -179,10 +179,24 @@ private:
bool mDepthsDirty = true;
public:
+ /* Graph access */
+
+ const std::vector<WorkflowConnection>& GetConnections() const;
+ std::vector<WorkflowConnection>& GetConnections();
+ const std::vector<std::unique_ptr<WorkflowNode>>& GetNodes() const;
+ std::vector<std::unique_ptr<WorkflowNode>>& GetNodes();
+ const std::vector<std::unique_ptr<BaseValue>>& GetConstants() const;
+ std::vector<std::unique_ptr<BaseValue>>& GetConstants();
+
WorkflowConnection* GetConnectionById(size_t id);
WorkflowNode* GetStepById(size_t id);
BaseValue* GetConstantById(size_t id);
+ const std::vector<std::vector<size_t>>& GetDepthGroups() const;
+ bool DoesDepthNeedsUpdate() const;
+
+ /* Graph mutation */
+
void AddStep(std::unique_ptr<WorkflowNode> step);
void RemoveStep(size_t id);
@@ -192,8 +206,7 @@ public:
bool DisconnectBySource(WorkflowNode& sourceNode, int sourcePin);
bool DisconnectByDestination(WorkflowNode& destinationNode, int destinationPin);
- const std::vector<std::vector<size_t>>& GetDepthGroups() const;
- bool DoesDepthNeedsUpdate() const;
+ /* Graph rebuild */
struct GraphUpdate_Success {};
struct GraphUpdate_NoWorkToDo {};
@@ -213,6 +226,8 @@ public:
/// When `getInfo == false, the corresponding error code is returned but without/with empty payloads.
GraphUpdateResult UpdateGraph(bool getInfo = true);
+ /* Serialization */
+
enum ReadResult {
ReadSuccess,
ReadInvalidVersion,
diff --git a/core/src/Model/Workflow/Workflow_Main.cpp b/core/src/Model/Workflow/Workflow_Main.cpp
index c8657e8..84b4557 100644
--- a/core/src/Model/Workflow/Workflow_Main.cpp
+++ b/core/src/Model/Workflow/Workflow_Main.cpp
@@ -9,6 +9,8 @@
#include <queue>
#include <utility>
+namespace ImNodes = ax::NodeEditor;
+
WorkflowConnection::WorkflowConnection()
: MultiConnections{}
, SingleConnection{ WorkflowNode::kInvalidId, -1 }
@@ -149,9 +151,8 @@ WorkflowConnection::Direction WorkflowNode::OutputPin::GetSupportedDirection() c
return AllowsMultipleConnections ? WorkflowConnection::OneToMany : WorkflowConnection::ManyToOne;
}
-WorkflowNode::WorkflowNode(Type type, Kind kind)
- : mType{ type }
- , mKind{ kind }
+WorkflowNode::WorkflowNode(Kind kind)
+ : mKind{ kind }
, mDepth{ -1 } {
}
@@ -167,15 +168,15 @@ size_t WorkflowNode::GetId() const {
return mId;
}
-WorkflowNode::Type WorkflowNode::GetType() const {
- return mType;
+WorkflowNode::Kind WorkflowNode::GetKind() const {
+ return mKind;
}
int WorkflowNode::GetDepth() const {
return mDepth;
}
-WorkflowNode::Kind WorkflowNode::GetKind() const {
+WorkflowNode::Type WorkflowNode::GetType() const {
if (IsInputNode()) {
return InputType;
} else if (IsOutputNode()) {
@@ -218,12 +219,24 @@ bool WorkflowNode::IsOutputConnected(int nodeId) const {
}
void WorkflowNode::Draw() {
+ for (size_t i = 0; i < mInputs.size(); ++i) {
+ auto& pin = mInputs[i];
+ ImNodes::BeginPin(i, ImNodes::PinKind::Input);
+ // TODO
+ ImNodes::EndPin();
+ }
+ for (size_t i = 0; i < mOutputs.size(); ++i) {
+ auto& pin = mOutputs[i];
+ ImNodes::BeginPin(i + mInputs.size(), ImNodes::PinKind::Output);
+ // TODO
+ ImNodes::EndPin();
+ }
}
void WorkflowNode::DrawDebugInfo() const {
ImGui::BeginTooltip();
ImGui::Text("Node kind: %s", FormatKind(mKind));
- ImGui::Text("Node type: %s", FormatType(mType));
+ ImGui::Text("Node type: %s", FormatType(GetType()));
ImGui::Text("Node ID: %llu", mId);
ImGui::Text("Depth: %d", mDepth);
DrawExtraDebugInfo();
@@ -333,6 +346,30 @@ void WorkflowNode::SwapOutputPin(int a, int b) {
std::swap(pinA, pinB);
}
+const std::vector<WorkflowConnection>& Workflow::GetConnections() const {
+ return mConnections;
+}
+
+std::vector<WorkflowConnection>& Workflow::GetConnections() {
+ return mConnections;
+}
+
+const std::vector<std::unique_ptr<WorkflowNode>>& Workflow::GetNodes() const {
+ return mNodes;
+}
+
+std::vector<std::unique_ptr<WorkflowNode>>& Workflow::GetNodes() {
+ return mNodes;
+}
+
+const std::vector<std::unique_ptr<BaseValue>>& Workflow::GetConstants() const {
+ return mConstants;
+}
+
+std::vector<std::unique_ptr<BaseValue>>& Workflow::GetConstants() {
+ return mConstants;
+}
+
WorkflowConnection* Workflow::GetConnectionById(size_t id) {
return &mConnections[id];
}
@@ -345,6 +382,14 @@ BaseValue* Workflow::GetConstantById(size_t id) {
return mConstants[id].get();
}
+const std::vector<std::vector<size_t>>& Workflow::GetDepthGroups() const {
+ return mDepthGroups;
+}
+
+bool Workflow::DoesDepthNeedsUpdate() const {
+ return mDepthsDirty;
+}
+
void Workflow::AddStep(std::unique_ptr<WorkflowNode> step) {
auto [storage, id] = AllocWorkflowStep();
storage = std::move(step);
@@ -524,14 +569,6 @@ bool Workflow::DisconnectByDestination(WorkflowNode& destinationNode, int destin
return true;
}
-const std::vector<std::vector<size_t>>& Workflow::GetDepthGroups() const {
- return mDepthGroups;
-}
-
-bool Workflow::DoesDepthNeedsUpdate() const {
- return mDepthsDirty;
-}
-
Workflow::GraphUpdateResult Workflow::UpdateGraph(bool getInfo) {
if (!mDepthsDirty) {
return GraphUpdate_NoWorkToDo{};
@@ -684,7 +721,7 @@ Workflow::ReadResult Workflow::ReadFrom(std::istream& stream) {
uint32_t nKind;
stream >> nKind;
- autp kind = (BaseValue::Kind)nKind;
+ auto kind = (BaseValue::Kind)nKind;
mConstants[idx] = BaseValue::CreateByKind(kind);
mConstants[idx]->ReadFrom(stream);
diff --git a/core/src/Model/Workflow/Workflow_RTTI.cpp b/core/src/Model/Workflow/Workflow_RTTI.cpp
index 0719372..e3fafdb 100644
--- a/core/src/Model/Workflow/Workflow_RTTI.cpp
+++ b/core/src/Model/Workflow/Workflow_RTTI.cpp
@@ -29,7 +29,6 @@ const char* WorkflowNode::FormatType(Type type) {
case InputType: return "input";
case TransformType: return "transform";
case OutputType: return "output";
- default: UNREACHABLE;
}
}
@@ -46,6 +45,6 @@ std::unique_ptr<WorkflowNode> WorkflowNode::CreateByKind(WorkflowNode::Kind kind
case KD_FormInput: return std::make_unique<FormInputNode>();
case KD_DatabaseRowsInput: return std::make_unique<DatabaseRowsInputNode>();
- case InvalidKind: return nullptr;
+ case InvalidKind: UNREACHABLE;
}
}
diff --git a/core/src/UI/UI_DatabaseView.cpp b/core/src/UI/UI_DatabaseView.cpp
index 884ab51..4009e5b 100644
--- a/core/src/UI/UI_DatabaseView.cpp
+++ b/core/src/UI/UI_DatabaseView.cpp
@@ -56,6 +56,8 @@ private:
/// index by offsetting by \c mFirstCachedRowId.
std::vector<int> mActiveEntries;
+ /// Number of rows in the table.
+ int mRowCount;
/// Last possible page for the current set table and filter (inclusive).
int mLastPage;
/// The current page the user is on.
@@ -71,6 +73,13 @@ public:
void OnProjectChanged(Project* newProject) {
mProject = newProject;
+ auto& stmt = newProject->GetTransactionsModel().GetSales().GetRowCountStatement;
+ if (stmt.executeStep()) {
+ mRowCount = stmt.getColumn(0).getInt();
+ } else {
+ // TODO report error
+ }
+
mFirstCachedRowId = 0;
mLastCachedRowId = 0;
@@ -116,6 +125,7 @@ public:
auto ls = LocaleStrings::Instance.get();
if (ImGui::Button(ICON_FA_ARROW_LEFT, mCurrentPage == 0)) {
+ mSelectedEntryRowId = -1;
SetPage(mCurrentPage - 1);
}
@@ -125,6 +135,7 @@ public:
ImGui::SameLine();
if (ImGui::Button(ICON_FA_ARROW_RIGHT, mCurrentPage == mLastPage)) {
+ mSelectedEntryRowId = -1;
SetPage(mCurrentPage + 1);
}
@@ -165,6 +176,9 @@ public:
void DrawEntry(const SaleEntry& entry, int rowId) {
auto ls = LocaleStrings::Instance.get();
+ ImGui::PushID(rowId);
+ ImGui::TableNextRow();
+
ImGui::TableNextColumn();
if (ImGui::Selectable(entry.Customer.c_str(), mSelectedEntryRowId == rowId, ImGuiSelectableFlags_SpanAllColumns)) {
mSelectedEntryRowId = rowId;
@@ -179,6 +193,8 @@ public:
} else {
ImGui::Text("%s", entry.DeliveryTime.c_str());
}
+
+ ImGui::PopID();
}
const SaleEntry& GetEntry(int64_t rowId) const {
@@ -197,7 +213,7 @@ private:
void UpdateLastPage() {
mLastPage = mActiveEntries.empty()
- ? 0 // TODO calc page
+ ? CalcPageForRowId(mRowCount)
: CalcPageForRowId(mActiveEntries.back());
}
@@ -267,7 +283,10 @@ private:
auto t = chrono::system_clock::to_time_t(tp);
char data[32];
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations" // C++ doesn't have std::localtime_s
std::strftime(data, sizeof(data), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
+#pragma clang diagnostic pop
return std::string(data);
};
@@ -351,7 +370,6 @@ void UI::DatabaseViewTab() {
purchasesView.Draw();
ImGui::EndTabItem();
}
-
ImGui::EndTabBar();
}
}
diff --git a/core/src/UI/UI_Items.cpp b/core/src/UI/UI_Items.cpp
index 56d6c2a..899db6e 100644
--- a/core/src/UI/UI_Items.cpp
+++ b/core/src/UI/UI_Items.cpp
@@ -220,27 +220,19 @@ void UI::ItemsTab() {
auto ls = LocaleStrings::Instance.get();
auto& uis = UIState::GetInstance();
- constexpr float kAmount = 16.0f;
- int id = 0;
- if (ImGui::CollapsingHeader(ls->ProductCategoryName.Get())) {
- ImGui::PushID(id++);
- ImGui::Indent(kAmount);
- ItemListEditor(uis.CurrentProject->Products);
- ImGui::Unindent(kAmount);
- ImGui::PopID();
- }
- if (ImGui::CollapsingHeader(ls->FactoryCategoryName.Get())) {
- ImGui::PushID(id++);
- ImGui::Indent(kAmount);
- ItemListEditor(uis.CurrentProject->Factories);
- ImGui::Unindent(kAmount);
- ImGui::PopID();
- }
- if (ImGui::CollapsingHeader(ls->CustomerCategoryName.Get())) {
- ImGui::PushID(id++);
- ImGui::Indent(kAmount);
- ItemListEditor(uis.CurrentProject->Customers);
- ImGui::Unindent(kAmount);
- ImGui::PopID();
+ if (ImGui::BeginTabBar("##ItemViewTabs")) {
+ if (ImGui::BeginTabItem(ls->ProductCategoryName.Get())) {
+ ItemListEditor(uis.CurrentProject->Products);
+ ImGui::EndTabItem();
+ }
+ if (ImGui::BeginTabItem(ls->FactoryCategoryName.Get())) {
+ ItemListEditor(uis.CurrentProject->Factories);
+ ImGui::EndTabItem();
+ }
+ if (ImGui::BeginTabItem(ls->CustomerCategoryName.Get())) {
+ ItemListEditor(uis.CurrentProject->Customers);
+ ImGui::EndTabItem();
+ }
+ ImGui::EndTabBar();
}
}
diff --git a/core/src/UI/UI_Workflows.cpp b/core/src/UI/UI_Workflows.cpp
index 0adfdc2..fe504e2 100644
--- a/core/src/UI/UI_Workflows.cpp
+++ b/core/src/UI/UI_Workflows.cpp
@@ -9,10 +9,13 @@
#include "Utils/Macros.hpp"
#include <imgui.h>
+#include <imgui_node_editor.h>
#include <memory>
#include <span>
#include <vector>
+namespace ImNodes = ax::NodeEditor;
+
namespace {
class WorkflowCreationMenu {
private:
@@ -127,13 +130,41 @@ private:
class WorkflowUI {
private:
Workflow* mWorkflow;
+ ImNodes::EditorContext* mContext;
public:
+ WorkflowUI() {
+ mContext = ImNodes::CreateEditor();
+ }
+
+ ~WorkflowUI() {
+ ImNodes::DestroyEditor(mContext);
+ }
+
void Draw() {
+ ImNodes::SetCurrentEditor(mContext);
+ ImNodes::Begin("");
+
+ for (auto& node : mWorkflow->GetNodes()) {
+ if (!node) continue;
+
+ ImNodes::BeginNode(node->GetId());
+ node->Draw();
+ ImNodes::EndNode();
+ }
+
+ for (auto& conn : mWorkflow->GetConnections()) {
+ if (!conn.IsValid()) continue;
+
+ // TODO create link
+ }
+
+ ImNodes::End();
}
};
} // namespace
void UI::WorkflowsTab() {
+ static std::unique_ptr<WorkflowUI> openWorkflow;
// TODO
}