228 lines
8.3 KiB
C++
228 lines
8.3 KiB
C++
#pragma once
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
namespace adminive {
|
|
enum class Composition_Node_Kind {
|
|
slot,
|
|
heading,
|
|
frontend,
|
|
vertical,
|
|
horizontal,
|
|
flow,
|
|
grid,
|
|
list,
|
|
group,
|
|
card,
|
|
tabs,
|
|
tab
|
|
};
|
|
struct Composition_Node {
|
|
Composition_Node_Kind kind{Composition_Node_Kind::vertical};
|
|
std::string slot;
|
|
std::string title;
|
|
std::string description;
|
|
std::string visible_on;
|
|
std::string accent_color;
|
|
std::size_t columns{};
|
|
bool collapsible{};
|
|
bool collapsed{};
|
|
std::vector<Composition_Node> children;
|
|
Composition_Node titled(std::string value) const {
|
|
auto result = *this;
|
|
result.title = std::move(value);
|
|
return result;
|
|
}
|
|
Composition_Node described(std::string value) const {
|
|
auto result = *this;
|
|
result.description = std::move(value);
|
|
return result;
|
|
}
|
|
Composition_Node visible_when(std::string value) const {
|
|
auto result = *this;
|
|
result.visible_on = std::move(value);
|
|
return result;
|
|
}
|
|
Composition_Node accent(std::string value) const {
|
|
auto result = *this;
|
|
result.accent_color = std::move(value);
|
|
return result;
|
|
}
|
|
Composition_Node collapsible_when(bool value = true, bool initially_collapsed = false) const {
|
|
auto result = *this;
|
|
result.collapsible = value;
|
|
result.collapsed = value && initially_collapsed;
|
|
return result;
|
|
}
|
|
};
|
|
namespace compose {
|
|
inline Composition_Node slot(std::string name) {
|
|
Composition_Node result;
|
|
result.kind = Composition_Node_Kind::slot;
|
|
result.slot = std::move(name);
|
|
return result;
|
|
}
|
|
inline Composition_Node heading(std::string title) {
|
|
Composition_Node result;
|
|
result.kind = Composition_Node_Kind::heading;
|
|
result.title = std::move(title);
|
|
return result;
|
|
}
|
|
template <class... Children>
|
|
Composition_Node make_container(Composition_Node_Kind kind, Children&&... children) {
|
|
Composition_Node result;
|
|
result.kind = kind;
|
|
result.children.reserve(sizeof...(Children));
|
|
(result.children.push_back(std::forward<Children>(children)), ...);
|
|
return result;
|
|
}
|
|
template <class... Children>
|
|
Composition_Node frontend(Children&&... children) {
|
|
return make_container(Composition_Node_Kind::frontend, std::forward<Children>(children)...);
|
|
}
|
|
template <class... Children>
|
|
Composition_Node vertical(Children&&... children) {
|
|
return make_container(Composition_Node_Kind::vertical, std::forward<Children>(children)...);
|
|
}
|
|
template <class... Children>
|
|
Composition_Node horizontal(Children&&... children) {
|
|
return make_container(Composition_Node_Kind::horizontal, std::forward<Children>(children)...);
|
|
}
|
|
template <class... Children>
|
|
Composition_Node flow(Children&&... children) {
|
|
return make_container(Composition_Node_Kind::flow, std::forward<Children>(children)...);
|
|
}
|
|
template <class... Children>
|
|
Composition_Node grid(std::size_t columns, Children&&... children) {
|
|
auto result = make_container(Composition_Node_Kind::grid, std::forward<Children>(children)...);
|
|
result.columns = columns;
|
|
return result;
|
|
}
|
|
template <class... Children>
|
|
Composition_Node list(Children&&... children) {
|
|
return make_container(Composition_Node_Kind::list, std::forward<Children>(children)...);
|
|
}
|
|
template <class... Children>
|
|
Composition_Node group(std::string title, Children&&... children) {
|
|
auto result = make_container(Composition_Node_Kind::group, std::forward<Children>(children)...);
|
|
result.title = std::move(title);
|
|
return result;
|
|
}
|
|
template <class... Children>
|
|
Composition_Node card(std::string title, Children&&... children) {
|
|
auto result = make_container(Composition_Node_Kind::card, std::forward<Children>(children)...);
|
|
result.title = std::move(title);
|
|
return result;
|
|
}
|
|
template <class... Children>
|
|
Composition_Node tab(std::string title, Children&&... children) {
|
|
auto result = make_container(Composition_Node_Kind::tab, std::forward<Children>(children)...);
|
|
result.title = std::move(title);
|
|
return result;
|
|
}
|
|
template <class... Children>
|
|
Composition_Node tabs(Children&&... children) {
|
|
return make_container(Composition_Node_Kind::tabs, std::forward<Children>(children)...);
|
|
}
|
|
}
|
|
struct Composition_View {
|
|
std::string name;
|
|
std::string title;
|
|
Composition_Node body;
|
|
Composition_View titled(std::string value) const {
|
|
auto result = *this;
|
|
result.title = std::move(value);
|
|
return result;
|
|
}
|
|
};
|
|
struct Composition_Slot_Contract {
|
|
std::string name;
|
|
std::string component_kind;
|
|
std::string descriptor_api;
|
|
std::string view_api;
|
|
std::string data_api;
|
|
std::string amis_api;
|
|
};
|
|
inline Composition_View composition_view(std::string name, Composition_Node body) {
|
|
return Composition_View{std::move(name), {}, std::move(body)};
|
|
}
|
|
inline void validate_composition_node_impl(const Composition_Node& node, bool tab_allowed) {
|
|
if(node.kind == Composition_Node_Kind::slot && node.slot.empty()) {
|
|
throw std::invalid_argument("composition slot name must not be empty");
|
|
}
|
|
if(node.kind == Composition_Node_Kind::grid && node.columns == 0) {
|
|
throw std::invalid_argument("composition grid requires at least one column");
|
|
}
|
|
if(node.kind == Composition_Node_Kind::tab && !tab_allowed) {
|
|
throw std::invalid_argument("composition tab can only be used inside tabs");
|
|
}
|
|
if(node.kind == Composition_Node_Kind::tabs) {
|
|
for(const auto& child : node.children) {
|
|
if(child.kind != Composition_Node_Kind::tab) {
|
|
throw std::invalid_argument("composition tabs accepts only tab children");
|
|
}
|
|
}
|
|
}
|
|
for(const auto& child : node.children) {
|
|
validate_composition_node_impl(child, node.kind == Composition_Node_Kind::tabs);
|
|
}
|
|
}
|
|
inline void validate_composition_node(const Composition_Node& node) {
|
|
validate_composition_node_impl(node, false);
|
|
}
|
|
inline void validate_composition_view(const Composition_View& view) {
|
|
if(view.name.empty()) {
|
|
throw std::invalid_argument("composition view name must not be empty");
|
|
}
|
|
validate_composition_node(view.body);
|
|
}
|
|
inline void collect_composition_slots(const Composition_Node& node, std::vector<std::string>& result) {
|
|
if(node.kind == Composition_Node_Kind::slot) {
|
|
result.push_back(node.slot);
|
|
}
|
|
for(const auto& child : node.children) {
|
|
collect_composition_slots(child, result);
|
|
}
|
|
}
|
|
inline void validate_composition_slot_contracts(const Composition_View& view, const std::vector<Composition_Slot_Contract>& contracts) {
|
|
validate_composition_view(view);
|
|
std::vector<std::string> slots;
|
|
collect_composition_slots(view.body, slots);
|
|
for(std::size_t index = 0; index < slots.size(); ++index) {
|
|
for(std::size_t other = index + 1; other < slots.size(); ++other) {
|
|
if(slots[index] == slots[other]) {
|
|
throw std::invalid_argument("duplicate composition slot reference: " + slots[index]);
|
|
}
|
|
}
|
|
}
|
|
for(const auto& slot_name : slots) {
|
|
const auto iterator = std::find_if(contracts.begin(), contracts.end(), [&](const Composition_Slot_Contract& contract) {
|
|
return contract.name == slot_name;
|
|
});
|
|
if(iterator == contracts.end()) {
|
|
throw std::invalid_argument("composition slot contract is not provided: " + slot_name);
|
|
}
|
|
}
|
|
for(std::size_t index = 0; index < contracts.size(); ++index) {
|
|
const auto& contract = contracts[index];
|
|
if(contract.name.empty()) {
|
|
throw std::invalid_argument("composition slot contract name must not be empty");
|
|
}
|
|
if(contract.component_kind.empty()) {
|
|
throw std::invalid_argument("composition slot contract component kind must not be empty: " + contract.name);
|
|
}
|
|
for(std::size_t other = index + 1; other < contracts.size(); ++other) {
|
|
if(contracts[other].name == contract.name) {
|
|
throw std::invalid_argument("duplicate composition slot contract: " + contract.name);
|
|
}
|
|
}
|
|
if(std::find(slots.begin(), slots.end(), contract.name) == slots.end()) {
|
|
throw std::invalid_argument("composition slot contract is not referenced by view: " + contract.name);
|
|
}
|
|
}
|
|
}
|
|
}
|