#pragma once #include #include #include #include #include #include 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 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 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)), ...); return result; } template Composition_Node frontend(Children&&... children) { return make_container(Composition_Node_Kind::frontend, std::forward(children)...); } template Composition_Node vertical(Children&&... children) { return make_container(Composition_Node_Kind::vertical, std::forward(children)...); } template Composition_Node horizontal(Children&&... children) { return make_container(Composition_Node_Kind::horizontal, std::forward(children)...); } template Composition_Node flow(Children&&... children) { return make_container(Composition_Node_Kind::flow, std::forward(children)...); } template Composition_Node grid(std::size_t columns, Children&&... children) { auto result = make_container(Composition_Node_Kind::grid, std::forward(children)...); result.columns = columns; return result; } template Composition_Node list(Children&&... children) { return make_container(Composition_Node_Kind::list, std::forward(children)...); } template Composition_Node group(std::string title, Children&&... children) { auto result = make_container(Composition_Node_Kind::group, std::forward(children)...); result.title = std::move(title); return result; } template Composition_Node card(std::string title, Children&&... children) { auto result = make_container(Composition_Node_Kind::card, std::forward(children)...); result.title = std::move(title); return result; } template Composition_Node tab(std::string title, Children&&... children) { auto result = make_container(Composition_Node_Kind::tab, std::forward(children)...); result.title = std::move(title); return result; } template Composition_Node tabs(Children&&... children) { return make_container(Composition_Node_Kind::tabs, std::forward(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& 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& contracts) { validate_composition_view(view); std::vector 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); } } } }