#pragma once #include "adminive/status.hpp" #include "adminive/view_json.hpp" #include #include #include #include #include #include #include #include namespace adminive { inline std::string make_field_name(std::string_view prefix, std::string_view name) { if(prefix.empty()) { return std::string(name); } return std::string(prefix) + "." + std::string(name); } inline std::string escape_amis_string_literal(std::string_view value) { std::string result; result.reserve(value.size()); for(const char character : value) { if(character == '\\' || character == '\'') { result.push_back('\\'); } if(character == '\n') { result += "\\n"; } else if(character == '\r') { result += "\\r"; } else { result.push_back(character); } } return result; } inline std::string resolve_visible_on(std::string expression, std::string_view prefix) { constexpr std::string_view marker{"$self"}; std::size_t position{}; while((position = expression.find(marker, position)) != std::string::npos) { const bool dotted = position + marker.size() < expression.size() && expression[position + marker.size()] == '.'; std::string replacement(prefix); if(!replacement.empty() && dotted) { replacement.push_back('.'); } const std::size_t count = marker.size() + (dotted ? 1 : 0); expression.replace(position, count, replacement); position += replacement.size(); } return expression; } template const Json& field_presentation(const Json& field) { return Json_Adapter::at(field, "presentation"); } template std::string field_label(const Json& field) { return json_get(Json_Adapter::at(field_presentation(field), "label")); } template void apply_visible_on(Json& control, const Json& field, std::string_view prefix) { const auto& presentation = field_presentation(field); if(Json_Adapter::contains(presentation, "visible_on")) { json_set(control, "visibleOn", resolve_visible_on(json_get(Json_Adapter::at(presentation, "visible_on")), prefix)); } } template Json make_amis_form_actions(std::string submit_label = "Apply") { Json reset = json_object(); json_set(reset, "type", "reset"); json_set(reset, "label", "Reset"); Json submit = json_object(); json_set(submit, "type", "submit"); json_set(submit, "label", std::move(submit_label)); json_set(submit, "level", "primary"); Json result = json_array(); json_append(result, std::move(reset)); json_append(result, std::move(submit)); return result; } template Json make_amis_form_body(const Json& descriptor, std::string_view permission, std::string_view prefix = {}); template Json make_typed_amis_form_body(const Json& descriptor, std::string_view permission, std::string_view prefix = {}); template requires Polymorphic_Type Json make_typed_amis_polymorphic_control(const Json& field, std::string_view permission, std::string_view prefix); template Json make_amis_polymorphic_control(const Json& field, std::string_view permission, std::string_view prefix) { const std::string name = json_get(Json_Adapter::at(field, "name")); const std::string full_name = make_field_name(prefix, name); const auto& metadata = Json_Adapter::at(field, "polymorphic"); const std::string discriminator = json_get(Json_Adapter::at(metadata, "discriminator")); Json selector = json_object(); json_set(selector, "type", "select"); json_set(selector, "name", make_field_name(full_name, discriminator)); json_set(selector, "label", Json_Adapter::at(metadata, "discriminator_label")); json_set(selector, "options", Json_Adapter::at(metadata, "options")); json_set(selector, "required", true); Json body = json_array(); json_append(body, std::move(selector)); const auto& variants = Json_Adapter::at(metadata, "variants"); for(std::size_t index = 0; index < Json_Adapter::size(variants); ++index) { const auto& variant = Json_Adapter::at(variants, index); const std::string value = json_get(Json_Adapter::at(variant, "value")); Json group = json_object(); json_set(group, "type", "container"); json_set(group, "visibleOn", "${" + make_field_name(full_name, discriminator) + " == '" + escape_amis_string_literal(value) + "'}"); json_set(group, "body", make_amis_form_body(Json_Adapter::at(variant, "descriptor"), permission, full_name)); json_append(body, std::move(group)); } Json result = json_object(); json_set(result, "type", "fieldset"); json_set(result, "title", Json_Adapter::at(field_presentation(field), "label")); json_set(result, "body", std::move(body)); json_set(result, "collapsable", true); json_set(result, "collapsed", false); if(Json_Adapter::contains(field_presentation(field), "description")) { json_set(result, "description", Json_Adapter::at(field_presentation(field), "description")); } apply_visible_on(result, field, prefix); return result; } template requires Polymorphic_Type Json make_typed_amis_polymorphic_control(const Json& field, std::string_view permission, std::string_view prefix) { using Adapter = Polymorphic_Adapter, Json>; const std::string name = json_get(Json_Adapter::at(field, "name")); const std::string full_name = make_field_name(prefix, name); const auto& metadata = Json_Adapter::at(field, "polymorphic"); const std::string discriminator(Adapter::discriminator()); Json selector = json_object(); json_set(selector, "type", "select"); json_set(selector, "name", make_field_name(full_name, discriminator)); json_set(selector, "label", Json_Adapter::at(metadata, "discriminator_label")); json_set(selector, "options", Json_Adapter::at(metadata, "options")); json_set(selector, "required", true); Json body = json_array(); json_append(body, std::move(selector)); std::apply([&](const auto&... variant) { ([&] { using Variant = typename std::remove_cvref_t::type; const Json descriptor = to_descriptor_json(); Json group = json_object(); json_set(group, "type", "container"); json_set(group, "visibleOn", "${" + make_field_name(full_name, discriminator) + " == '" + escape_amis_string_literal(variant.value) + "'}"); json_set(group, "body", make_typed_amis_form_body>(descriptor, permission, full_name)); json_append(body, std::move(group)); }(), ...); }, Adapter::variants()); Json result = json_object(); json_set(result, "type", "fieldset"); json_set(result, "title", Json_Adapter::at(field_presentation(field), "label")); json_set(result, "body", std::move(body)); json_set(result, "collapsable", true); json_set(result, "collapsed", false); if(Json_Adapter::contains(field_presentation(field), "description")) { json_set(result, "description", Json_Adapter::at(field_presentation(field), "description")); } apply_visible_on(result, field, prefix); return result; } inline std::string_view amis_control_type(std::string_view control, std::string_view value_type) { if(control == "text") { return "input-text"; } if(control == "multiline_text") { return "textarea"; } if(control == "number") { return "input-number"; } if(control == "boolean") { return "switch"; } if(control == "select") { return "select"; } if(control == "date") { return "input-date"; } if(control == "color") { return "input-color"; } if(control != "automatic") { throw std::invalid_argument("unknown field control: " + std::string(control)); } if(value_type == "boolean") { return "switch"; } if(value_type == "integer" || value_type == "number") { return "input-number"; } if(value_type == "enum") { return "select"; } if(value_type == "string") { return "input-text"; } return {}; } template Json make_default_amis_control(const Json& field, std::string_view permission, std::string_view prefix = {}) { const std::string name = json_get(Json_Adapter::at(field, "name")); const std::string full_name = make_field_name(prefix, name); const std::string value_type = json_get(Json_Adapter::at(field, "value_type")); if(value_type == "object" && Json_Adapter::contains(field, "children")) { Json descriptor = json_object(); json_set(descriptor, "fields", Json_Adapter::at(field, "children")); Json control = json_object(); json_set(control, "type", "fieldset"); json_set(control, "title", Json_Adapter::at(field_presentation(field), "label")); json_set(control, "body", make_amis_form_body(descriptor, permission, full_name)); json_set(control, "collapsable", true); json_set(control, "collapsed", false); if(Json_Adapter::contains(field_presentation(field), "description")) { json_set(control, "description", Json_Adapter::at(field_presentation(field), "description")); } apply_visible_on(control, field, prefix); return control; } if(value_type == "polymorphic" && Json_Adapter::contains(field, "polymorphic")) { return make_amis_polymorphic_control(field, permission, prefix); } Json control = json_object(); json_set(control, "name", full_name); json_set(control, "label", Json_Adapter::at(field_presentation(field), "label")); if(Json_Adapter::contains(field_presentation(field), "description")) { json_set(control, "description", Json_Adapter::at(field_presentation(field), "description")); } apply_visible_on(control, field, prefix); if(!json_get(Json_Adapter::at(field, permission))) { json_set(control, "type", "static"); return control; } const auto control_type = amis_control_type(json_get(Json_Adapter::at(field_presentation(field), "control")), value_type); if(control_type.empty()) { throw std::invalid_argument("field '" + full_name + "' requires an explicit field control or Control_Adapter"); } json_set(control, "type", std::string(control_type)); const std::string resolved_control_type = json_get(Json_Adapter::at(control, "type")); if(Json_Adapter::contains(field, "nullable") && json_get(Json_Adapter::at(field, "nullable"))) { json_set(control, "clearable", true); } if(resolved_control_type == "input-date") { json_set(control, "valueFormat", "YYYY-MM-DD"); json_set(control, "displayFormat", "YYYY-MM-DD"); json_set(control, "clearable", true); } else if(resolved_control_type == "input-color") { json_set(control, "clearable", true); } if(json_get(Json_Adapter::at(field, "required"))) { json_set(control, "required", true); } if(Json_Adapter::contains(field, "minimum")) { json_set(control, "min", Json_Adapter::at(field, "minimum")); } if(Json_Adapter::contains(field, "maximum")) { json_set(control, "max", Json_Adapter::at(field, "maximum")); } if(Json_Adapter::contains(field, "multiple_of")) { json_set(control, "step", Json_Adapter::at(field, "multiple_of")); } if(Json_Adapter::contains(field, "validation_message")) { Json errors = json_object(); json_set(errors, "isInt", Json_Adapter::at(field, "validation_message")); json_set(control, "validationErrors", std::move(errors)); } if(Json_Adapter::contains(field_presentation(field), "options")) { json_set(control, "options", Json_Adapter::at(field_presentation(field), "options")); } return control; } template Json make_amis_control(const Json& field, std::string_view permission, std::string_view prefix = {}) { return make_default_amis_control(field, permission, prefix); } template Json make_typed_amis_control(const Json& field, std::string_view permission, std::string_view prefix = {}) { using Storage = std::remove_cvref_t; using Value = Adapted_Value_Type; using Control_Value = Optional_Unwrapped_Type; const Control_Context context{permission, prefix}; if constexpr(Control_Adapter_With_Control) { return Control_Adapter::make_control(field, context); } else if constexpr(!std::same_as && Control_Adapter_With_Control) { return Control_Adapter::make_control(field, context); } else if constexpr(!std::same_as && Control_Adapter_With_Control) { return Control_Adapter::make_control(field, context); } else if constexpr(Polymorphic_Type) { return make_typed_amis_polymorphic_control(field, permission, prefix); } else if constexpr(Described_Type || Snapshot_Adapted_Object) { using Model = Object_Model_Type; const std::string name = json_get(Json_Adapter::at(field, "name")); const std::string full_name = make_field_name(prefix, name); const Json descriptor = to_descriptor_json(); Json control = json_object(); json_set(control, "type", "fieldset"); json_set(control, "title", Json_Adapter::at(field_presentation(field), "label")); json_set(control, "body", make_typed_amis_form_body(descriptor, permission, full_name)); json_set(control, "collapsable", true); json_set(control, "collapsed", false); if(Json_Adapter::contains(field_presentation(field), "description")) { json_set(control, "description", Json_Adapter::at(field_presentation(field), "description")); } apply_visible_on(control, field, prefix); return control; } else { return make_default_amis_control(field, permission, prefix); } } template Json make_amis_form_body(const Json& descriptor, std::string_view permission, std::string_view prefix) { Json body = json_array(); const auto& fields = Json_Adapter::at(descriptor, "fields"); for(std::size_t index = 0; index < Json_Adapter::size(fields); ++index) { const auto& field = Json_Adapter::at(fields, index); json_append(body, make_default_amis_control(field, permission, prefix)); } return body; } template void append_typed_amis_form_fields(Json& body, const Json& fields, const Descriptor& descriptor, std::string_view permission, std::string_view prefix) { using Fields = std::remove_cvref_t; if constexpr(Index < std::tuple_size_v) { const auto& item = std::get(descriptor.fields()); const auto& field = Json_Adapter::at(fields, Index); using Field = std::remove_cvref_t; json_append(body, make_typed_amis_control(field, permission, prefix)); append_typed_amis_form_fields(body, fields, descriptor, permission, prefix); } } template Json make_typed_amis_form_body(const Json& descriptor, std::string_view permission, std::string_view prefix) { Json body = json_array(); const auto& fields = Json_Adapter::at(descriptor, "fields"); const auto object_descriptor = describe(); append_typed_amis_form_fields<0>(body, fields, object_descriptor, permission, prefix); return body; } template Json make_default_amis_column(const Json& field) { Json column = json_object(); json_set(column, "name", Json_Adapter::at(field, "name")); json_set(column, "label", Json_Adapter::at(field_presentation(field), "label")); const std::string value_type = json_get(Json_Adapter::at(field, "value_type")); if(value_type == "boolean") { json_set(column, "type", "status"); } else if(value_type == "enum" && Json_Adapter::contains(field_presentation(field), "options")) { json_set(column, "type", "mapping"); Json map = json_object(); const auto& options = Json_Adapter::at(field_presentation(field), "options"); for(std::size_t index = 0; index < Json_Adapter::size(options); ++index) { const auto& option = Json_Adapter::at(options, index); json_set(map, json_get(Json_Adapter::at(option, "value")), Json_Adapter::at(option, "label")); } json_set(column, "map", std::move(map)); } return column; } template Json make_typed_amis_column(const Json& field) { using Storage = std::remove_cvref_t; using Value = Adapted_Value_Type; using Control_Value = Optional_Unwrapped_Type; if constexpr(Control_Adapter_With_Column) { return Control_Adapter::make_column(field); } else if constexpr(!std::same_as && Control_Adapter_With_Column) { return Control_Adapter::make_column(field); } else if constexpr(!std::same_as && Control_Adapter_With_Column) { return Control_Adapter::make_column(field); } else { return make_default_amis_column(field); } } template Json make_typed_amis_control_by_name(const Json& descriptor_json, const Descriptor& descriptor, std::string_view field_name, std::string_view permission, std::string_view prefix) { using Fields = std::remove_cvref_t; if constexpr(Index < std::tuple_size_v) { const auto& item = std::get(descriptor.fields()); if(item.name() == field_name) { using Field = std::remove_cvref_t; return make_typed_amis_control(Json_Adapter::at(Json_Adapter::at(descriptor_json, "fields"), Index), permission, prefix); } return make_typed_amis_control_by_name(descriptor_json, descriptor, field_name, permission, prefix); } else { throw std::invalid_argument("view field does not exist: " + std::string(field_name)); } } template Json make_typed_amis_column_by_name(const Json& descriptor_json, const Descriptor& descriptor, std::string_view field_name) { using Fields = std::remove_cvref_t; if constexpr(Index < std::tuple_size_v) { const auto& item = std::get(descriptor.fields()); if(item.name() == field_name) { using Field = std::remove_cvref_t; return make_typed_amis_column(Json_Adapter::at(Json_Adapter::at(descriptor_json, "fields"), Index)); } return make_typed_amis_column_by_name(descriptor_json, descriptor, field_name); } else { throw std::invalid_argument("table column field does not exist: " + std::string(field_name)); } } inline std::string_view form_permission(Form_Mode mode) noexcept { switch(mode) { case Form_Mode::display: return "readable"; case Form_Mode::edit: return "editable"; case Form_Mode::create: return "creatable"; } return "readable"; } inline std::size_t amis_grid_width(std::size_t index, std::size_t count) noexcept { if(count == 0) { return 12; } if(count >= 12) { return 1; } const std::size_t width = 12 / count; return width + (index % count < 12 % count ? 1 : 0); } inline void collect_amis_submit_fields(const View_Node& node, std::vector& fields, bool& all_fields_selected) { if(all_fields_selected) { return; } if(node.kind == View_Node_Kind::all_fields) { all_fields_selected = true; fields.clear(); return; } if(node.kind == View_Node_Kind::field && std::find(fields.begin(), fields.end(), node.field) == fields.end()) { fields.push_back(node.field); } for(const auto& child : node.children) { collect_amis_submit_fields(child, fields, all_fields_selected); } } template Json make_amis_submit_data(const Json& descriptor, const View_Node& body, std::string_view permission) { std::vector selected_fields; bool all_fields_selected{}; collect_amis_submit_fields(body, selected_fields, all_fields_selected); Json result = json_object(); const auto& fields = Json_Adapter::at(descriptor, "fields"); for(std::size_t index = 0; index < Json_Adapter::size(fields); ++index) { const auto& field = Json_Adapter::at(fields, index); const std::string name = json_get(Json_Adapter::at(field, "name")); const bool selected = all_fields_selected || std::find(selected_fields.begin(), selected_fields.end(), name) != selected_fields.end(); if(selected && json_get(Json_Adapter::at(field, permission))) { json_set(result, name, "${" + name + "}"); } } return result; } template void apply_view_node_options(Json& result, const View_Node& node, std::string_view prefix) { if(!node.description.empty()) { json_set(result, "description", node.description); } if(!node.visible_on.empty()) { json_set(result, "visibleOn", resolve_visible_on(node.visible_on, prefix)); } } template Json make_amis_view_body(const View_Node& node, const Json& descriptor_json, std::string_view permission, std::string_view prefix); template Json make_amis_view_node(const View_Node& node, const Json& descriptor_json, std::string_view permission, std::string_view prefix) { const auto descriptor = describe(); if(node.kind == View_Node_Kind::field) { return make_typed_amis_control_by_name(descriptor_json, descriptor, node.field, permission, prefix); } if(node.kind == View_Node_Kind::frontend) { Json result = json_object(); json_set(result, "type", "container"); json_set(result, "body", make_amis_view_body(View_Node{View_Node_Kind::vertical, {}, {}, {}, {}, 0, false, false, node.children}, descriptor_json, permission, prefix)); json_set(result, "className", "adminive-frontend-placement"); apply_view_node_options(result, node, prefix); return result; } if(node.kind == View_Node_Kind::list) { Json body = json_array(); for(const auto& child : node.children) { Json item = json_object(); json_set(item, "type", "container"); json_set(item, "body", make_amis_view_body(child, descriptor_json, permission, prefix)); Json style = json_object(); json_set(style, "padding", "10px 0"); json_set(style, "borderBottom", "1px solid #e5e7eb"); json_set(item, "style", std::move(style)); json_append(body, std::move(item)); } Json result = json_object(); json_set(result, "type", "container"); json_set(result, "body", std::move(body)); apply_view_node_options(result, node, prefix); return result; } if(node.kind == View_Node_Kind::group) { Json result = json_object(); json_set(result, "type", "fieldset"); json_set(result, "title", node.title); json_set(result, "body", make_amis_view_body(View_Node{View_Node_Kind::vertical, {}, {}, {}, {}, 0, false, false, node.children}, descriptor_json, permission, prefix)); if(node.collapsible) { json_set(result, "collapsable", true); json_set(result, "collapsed", node.collapsed); } apply_view_node_options(result, node, prefix); return result; } if(node.kind == View_Node_Kind::card) { Json result = json_object(); json_set(result, "type", "card"); if(!node.title.empty()) { Json header = json_object(); json_set(header, "title", node.title); json_set(result, "header", std::move(header)); } json_set(result, "body", make_amis_view_body(View_Node{View_Node_Kind::vertical, {}, {}, {}, {}, 0, false, false, node.children}, descriptor_json, permission, prefix)); apply_view_node_options(result, node, prefix); return result; } if(node.kind == View_Node_Kind::horizontal || node.kind == View_Node_Kind::grid) { Json columns = json_array(); const std::size_t count = node.kind == View_Node_Kind::grid && node.columns != 0 ? node.columns : std::max(1, node.children.size()); for(std::size_t index = 0; index < node.children.size(); ++index) { Json column = json_object(); json_set(column, "md", amis_grid_width(index, count)); json_set(column, "body", make_amis_view_body(node.children[index], descriptor_json, permission, prefix)); json_append(columns, std::move(column)); } Json result = json_object(); json_set(result, "type", "grid"); json_set(result, "columns", std::move(columns)); apply_view_node_options(result, node, prefix); return result; } if(node.kind == View_Node_Kind::flow) { Json items = json_array(); for(const auto& child : node.children) { Json item = json_object(); json_set(item, "type", "container"); json_set(item, "body", make_amis_view_body(child, descriptor_json, permission, prefix)); Json item_style = json_object(); json_set(item_style, "flex", "1 1 280px"); json_set(item, "style", std::move(item_style)); json_append(items, std::move(item)); } Json result = json_object(); json_set(result, "type", "flex"); json_set(result, "direction", "row"); json_set(result, "items", std::move(items)); Json style = json_object(); json_set(style, "flexWrap", "wrap"); json_set(style, "gap", "12px"); json_set(result, "style", std::move(style)); apply_view_node_options(result, node, prefix); return result; } if(node.kind == View_Node_Kind::tabs) { Json tab_items = json_array(); for(const auto& child : node.children) { Json item = json_object(); json_set(item, "title", child.title); json_set(item, "body", make_amis_view_body(View_Node{View_Node_Kind::vertical, {}, {}, {}, {}, 0, false, false, child.children}, descriptor_json, permission, prefix)); if(!child.visible_on.empty()) { json_set(item, "visibleOn", resolve_visible_on(child.visible_on, prefix)); } json_append(tab_items, std::move(item)); } Json result = json_object(); json_set(result, "type", "tabs"); json_set(result, "tabs", std::move(tab_items)); apply_view_node_options(result, node, prefix); return result; } if(node.kind == View_Node_Kind::tab) { throw std::invalid_argument("tab view can only be used inside tabs"); } Json result = json_object(); json_set(result, "type", "container"); json_set(result, "body", make_amis_view_body(View_Node{View_Node_Kind::vertical, {}, {}, {}, {}, 0, false, false, node.children}, descriptor_json, permission, prefix)); apply_view_node_options(result, node, prefix); return result; } template void append_amis_view_node(Json& body, const View_Node& node, const Json& descriptor_json, std::string_view permission, std::string_view prefix) { if(node.kind == View_Node_Kind::all_fields) { Json fields = make_typed_amis_form_body(descriptor_json, permission, prefix); for(std::size_t index = 0; index < Json_Adapter::size(fields); ++index) { json_append(body, Json_Adapter::at(fields, index)); } return; } if(node.kind == View_Node_Kind::vertical) { for(const auto& child : node.children) { append_amis_view_node(body, child, descriptor_json, permission, prefix); } return; } json_append(body, make_amis_view_node(node, descriptor_json, permission, prefix)); } template Json make_amis_view_body(const View_Node& node, const Json& descriptor_json, std::string_view permission, std::string_view prefix) { Json result = json_array(); append_amis_view_node(result, node, descriptor_json, permission, prefix); return result; } template void apply_composition_node_options(Json& result, const Composition_Node& node) { if(!node.description.empty()) { json_set(result, "description", node.description); } if(!node.visible_on.empty()) { json_set(result, "visibleOn", node.visible_on); } } template Json make_amis_composition_body(const Composition_Node& node, const std::map& slots); template Json make_amis_composition_node(const Composition_Node& node, const std::map& slots) { if(node.kind == Composition_Node_Kind::slot) { const auto iterator = slots.find(node.slot); if(iterator == slots.end()) { throw std::invalid_argument("composition slot is not provided: " + node.slot); } return iterator->second; } if(node.kind == Composition_Node_Kind::heading) { Json result = json_object(); json_set(result, "type", "tpl"); std::string html = "

" + node.title + "

"; json_set(result, "tpl", std::move(html)); apply_composition_node_options(result, node); return result; } if(node.kind == Composition_Node_Kind::frontend) { Json result = json_object(); json_set(result, "type", "container"); json_set(result, "body", make_amis_composition_body(Composition_Node{Composition_Node_Kind::vertical, {}, {}, {}, {}, {}, 0, false, false, node.children}, slots)); json_set(result, "className", "adminive-frontend-placement"); apply_composition_node_options(result, node); return result; } if(node.kind == Composition_Node_Kind::list) { Json body = json_array(); for(const auto& child : node.children) { Json item = json_object(); json_set(item, "type", "container"); json_set(item, "body", make_amis_composition_body(child, slots)); Json style = json_object(); json_set(style, "padding", "10px 0"); json_set(style, "borderBottom", "1px solid #e5e7eb"); json_set(item, "style", std::move(style)); json_append(body, std::move(item)); } Json result = json_object(); json_set(result, "type", "container"); json_set(result, "body", std::move(body)); apply_composition_node_options(result, node); return result; } if(node.kind == Composition_Node_Kind::group) { Json result = json_object(); json_set(result, "type", "panel"); json_set(result, "title", node.title); json_set(result, "body", make_amis_composition_body(Composition_Node{Composition_Node_Kind::vertical, {}, {}, {}, {}, {}, 0, false, false, node.children}, slots)); if(node.collapsible) { json_set(result, "collapsable", true); json_set(result, "collapsed", node.collapsed); } apply_composition_node_options(result, node); return result; } if(node.kind == Composition_Node_Kind::card) { Json result = json_object(); json_set(result, "type", "card"); if(!node.title.empty()) { Json header = json_object(); json_set(header, "title", node.title); json_set(result, "header", std::move(header)); } json_set(result, "body", make_amis_composition_body(Composition_Node{Composition_Node_Kind::vertical, {}, {}, {}, {}, {}, 0, false, false, node.children}, slots)); apply_composition_node_options(result, node); return result; } if(node.kind == Composition_Node_Kind::horizontal || node.kind == Composition_Node_Kind::grid) { Json columns = json_array(); const std::size_t count = node.kind == Composition_Node_Kind::grid && node.columns != 0 ? node.columns : std::max(1, node.children.size()); for(std::size_t index = 0; index < node.children.size(); ++index) { Json column = json_object(); json_set(column, "md", amis_grid_width(index, count)); json_set(column, "body", make_amis_composition_body(node.children[index], slots)); json_append(columns, std::move(column)); } Json result = json_object(); json_set(result, "type", "grid"); json_set(result, "columns", std::move(columns)); apply_composition_node_options(result, node); return result; } if(node.kind == Composition_Node_Kind::flow) { Json items = json_array(); for(const auto& child : node.children) { Json item = json_object(); json_set(item, "type", "container"); json_set(item, "body", make_amis_composition_body(child, slots)); Json item_style = json_object(); json_set(item_style, "flex", "1 1 280px"); json_set(item, "style", std::move(item_style)); json_append(items, std::move(item)); } Json result = json_object(); json_set(result, "type", "flex"); json_set(result, "direction", "row"); json_set(result, "items", std::move(items)); Json style = json_object(); json_set(style, "flexWrap", "wrap"); json_set(style, "gap", "12px"); json_set(result, "style", std::move(style)); apply_composition_node_options(result, node); return result; } if(node.kind == Composition_Node_Kind::tabs) { Json tab_items = json_array(); for(const auto& child : node.children) { Json item = json_object(); json_set(item, "title", child.title); json_set(item, "body", make_amis_composition_body(Composition_Node{Composition_Node_Kind::vertical, {}, {}, {}, {}, {}, 0, false, false, child.children}, slots)); if(!child.visible_on.empty()) { json_set(item, "visibleOn", child.visible_on); } json_append(tab_items, std::move(item)); } Json result = json_object(); json_set(result, "type", "tabs"); json_set(result, "tabs", std::move(tab_items)); apply_composition_node_options(result, node); return result; } if(node.kind == Composition_Node_Kind::tab) { throw std::invalid_argument("composition tab can only be used inside tabs"); } Json result = json_object(); json_set(result, "type", "container"); json_set(result, "body", make_amis_composition_body(Composition_Node{Composition_Node_Kind::vertical, {}, {}, {}, {}, {}, 0, false, false, node.children}, slots)); apply_composition_node_options(result, node); return result; } template void append_amis_composition_node(Json& body, const Composition_Node& node, const std::map& slots) { if(node.kind == Composition_Node_Kind::vertical) { for(const auto& child : node.children) { append_amis_composition_node(body, child, slots); } return; } if(node.kind == Composition_Node_Kind::slot) { const auto iterator = slots.find(node.slot); if(iterator == slots.end()) { throw std::invalid_argument("composition slot is not provided: " + node.slot); } if(Json_Adapter::is_array(iterator->second)) { for(std::size_t index = 0; index < Json_Adapter::size(iterator->second); ++index) { json_append(body, Json_Adapter::at(iterator->second, index)); } } else { json_append(body, iterator->second); } return; } json_append(body, make_amis_composition_node(node, slots)); } template Json make_amis_composition_body(const Composition_Node& node, const std::map& slots) { Json result = json_array(); append_amis_composition_node(result, node, slots); return result; } template Json to_amis_composition_schema(const Composition_View& view, const std::map& slots) { validate_composition_view(view); Json result = json_object(); json_set(result, "type", view.title.empty() ? "container" : "panel"); if(!view.title.empty()) { json_set(result, "title", view.title); } json_set(result, "body", make_amis_composition_body(view.body, slots)); return result; } template Json make_amis_status_body(const Json& descriptor) { Json body = json_array(); const auto& fields = Json_Adapter::at(descriptor, "fields"); for(std::size_t index = 0; index < Json_Adapter::size(fields); ++index) { const auto& field = Json_Adapter::at(fields, index); const std::string name = json_get(Json_Adapter::at(field, "name")); const std::string label = json_get(Json_Adapter::at(field, "label")); const bool color = json_get(Json_Adapter::at(field, "color")); std::string tpl = "
" + label + "
"; Json item = json_object(); json_set(item, "type", "tpl"); json_set(item, "tpl", std::move(tpl)); json_append(body, std::move(item)); } Json result = json_object(); json_set(result, "type", "container"); json_set(result, "className", "adminive-object-status"); json_set(result, "body", std::move(body)); return result; } template Json make_amis_status_service(const Json& descriptor, std::string status_api, std::uint64_t interval = 2000) { Json panel = json_object(); json_set(panel, "type", "panel"); json_set(panel, "title", Json_Adapter::at(descriptor, "label")); json_set(panel, "body", make_amis_status_body(descriptor)); Json api = json_object(); json_set(api, "method", "get"); json_set(api, "url", std::move(status_api)); Json result = json_object(); json_set(result, "type", "service"); json_set(result, "name", json_get(Json_Adapter::at(descriptor, "name")) + "_status"); json_set(result, "api", std::move(api)); json_set(result, "initFetch", true); json_set(result, "interval", interval); json_set(result, "silentPolling", true); json_set(result, "showErrorMsg", true); json_set(result, "body", std::move(panel)); return result; } template Json make_amis_object_status_service(const Json& descriptor, std::string status_api, std::uint64_t interval) { Json api = json_object(); json_set(api, "method", "get"); json_set(api, "url", std::move(status_api)); Json result = json_object(); json_set(result, "type", "service"); json_set(result, "api", std::move(api)); json_set(result, "initFetch", true); json_set(result, "interval", interval); json_set(result, "silentPolling", true); json_set(result, "showErrorMsg", true); json_set(result, "body", make_amis_status_body(descriptor)); return result; } template Json make_amis_object_status_column(const Json& descriptor, std::string status_api, std::uint64_t interval, std::string action_label) { Json pop_over = json_object(); json_set(pop_over, "trigger", "click"); json_set(pop_over, "position", "left-top"); json_set(pop_over, "showIcon", false); json_set(pop_over, "body", make_amis_object_status_service(descriptor, std::move(status_api), interval)); Json result = json_object(); json_set(result, "type", "tpl"); json_set(result, "label", Json_Adapter::at(descriptor, "label")); json_set(result, "tpl", "" + std::move(action_label) + ""); json_set(result, "popOver", std::move(pop_over)); return result; } template requires Described_Type> || Snapshot_Adapted_Object Json to_amis_form_schema(const T& value, const Form_View& view, std::string submit_api = {}) { using Model = Object_Model_Type; validate_form_view(view); const Json descriptor = to_descriptor_json(); Json result = json_object(); json_set(result, "type", "form"); json_set(result, "title", view.title); json_set(result, "data", to_frontend_json(value)); json_set(result, "body", make_amis_view_body(view.body, descriptor, form_permission(view.mode), {})); if(view.mode != Form_Mode::display) { json_set(result, "actions", make_amis_form_actions(view.submit_label)); json_set(result, "affixFooter", view.affix_footer); if(!submit_api.empty()) { Json api = json_object(); json_set(api, "method", "post"); json_set(api, "url", std::move(submit_api)); json_set(api, "data", make_amis_submit_data(descriptor, view.body, form_permission(view.mode))); json_set(result, "api", std::move(api)); } } return result; } template Json to_amis_form_schema(const T& value, const Form_View& view, std::string submit_api = {}) { return value.read([&](const auto& item) { return adminive::to_amis_form_schema(item, view, std::move(submit_api)); }); } template Json to_amis_form_schema(const T& value, const Form_View& view, std::string submit_api = {}) { return value.read([&](const auto& item) { return adminive::to_amis_form_schema(item, view, std::move(submit_api)); }); } inline std::string collection_field_label(const Collection_View& view, std::string_view field) { for(const auto& column : view.table.columns) { if(column.field == field) { return column.label.empty() ? make_label(field) : column.label; } } return make_label(field); } template Json make_amis_semantic_collection_item(const Collection_View& view) { Json body = json_array(); for(const auto& field : view.item.fields) { Json line = json_object(); json_set(line, "type", "tpl"); json_set(line, "tpl", "" + collection_field_label(view, field) + ":${" + field + "}"); json_append(body, std::move(line)); } if(!view.item.accent_field.empty()) { Json accent = json_object(); json_set(accent, "type", "tpl"); json_set(accent, "tpl", "
"); json_append(body, std::move(accent)); } if(view.mode == Collection_Mode::list) { Json result = json_object(); json_set(result, "title", "${" + view.item.title_field + "}"); if(!view.item.subtitle_field.empty()) { json_set(result, "subTitle", "${" + view.item.subtitle_field + "}"); } json_set(result, "body", std::move(body)); return result; } Json header = json_object(); json_set(header, "title", "${" + view.item.title_field + "}"); if(!view.item.subtitle_field.empty()) { json_set(header, "subTitle", "${" + view.item.subtitle_field + "}"); } Json result = json_object(); json_set(result, "header", std::move(header)); json_set(result, "body", std::move(body)); return result; } template requires Described_Type> Json make_amis_collection_schema(std::string base_api, const Table_View& view, std::string mode, const Json* item, std::size_t cards_columns, const Json* item_status_descriptor, std::uint64_t item_status_interval, const Json* overview_status_descriptor, std::string overview_status_api, std::uint64_t overview_status_interval) { using Model = Object_Model_Type; validate_table_view(view); const Json descriptor = to_descriptor_json(); const auto object_descriptor = describe(); std::string crud_id = view.name + "_crud"; if(mode != "table") { crud_id += "_" + mode; } Json create_api = json_object(); json_set(create_api, "method", "post"); json_set(create_api, "url", base_api); json_set(create_api, "data", make_amis_submit_data(descriptor, view.create_body, "creatable")); Json create_form = json_object(); json_set(create_form, "type", "form"); json_set(create_form, "api", std::move(create_api)); json_set(create_form, "reload", crud_id); json_set(create_form, "body", make_amis_view_body(view.create_body, descriptor, "creatable", {})); json_set(create_form, "actions", make_amis_form_actions(view.create_label)); Json edit_api = json_object(); json_set(edit_api, "method", "put"); json_set(edit_api, "url", base_api + "/${id}"); json_set(edit_api, "data", make_amis_submit_data(descriptor, view.edit_body, "editable")); Json edit_form = json_object(); json_set(edit_form, "type", "form"); json_set(edit_form, "api", std::move(edit_api)); json_set(edit_form, "reload", crud_id); json_set(edit_form, "body", make_amis_view_body(view.edit_body, descriptor, "editable", {})); json_set(edit_form, "actions", make_amis_form_actions(view.confirm_label)); Json create_dialog = json_object(); json_set(create_dialog, "title", view.create_label + " " + json_get(Json_Adapter::at(descriptor, "label"))); json_set(create_dialog, "body", std::move(create_form)); Json create_button = json_object(); json_set(create_button, "type", "button"); json_set(create_button, "label", view.create_label); json_set(create_button, "level", "primary"); json_set(create_button, "actionType", "dialog"); json_set(create_button, "dialog", std::move(create_dialog)); Json edit_dialog = json_object(); json_set(edit_dialog, "title", view.edit_label + " " + json_get(Json_Adapter::at(descriptor, "label"))); json_set(edit_dialog, "body", std::move(edit_form)); Json edit_button = json_object(); json_set(edit_button, "type", "button"); json_set(edit_button, "label", view.edit_label); json_set(edit_button, "level", "link"); json_set(edit_button, "actionType", "dialog"); json_set(edit_button, "dialog", std::move(edit_dialog)); Json delete_api = json_object(); json_set(delete_api, "method", "delete"); json_set(delete_api, "url", base_api + "/${id}"); Json delete_button = json_object(); json_set(delete_button, "type", "button"); json_set(delete_button, "label", view.delete_label); json_set(delete_button, "level", "link"); json_set(delete_button, "className", "text-danger"); json_set(delete_button, "actionType", "ajax"); json_set(delete_button, "confirmText", view.delete_confirm); json_set(delete_button, "api", std::move(delete_api)); json_set(delete_button, "reload", crud_id); Json buttons = json_array(); json_append(buttons, std::move(edit_button)); json_append(buttons, std::move(delete_button)); Json toolbar = json_array(); if(mode == "table") { Json toggler = json_object(); json_set(toggler, "type", "columns-toggler"); json_set(toggler, "draggable", view.column_reorderable); json_append(toolbar, std::move(toggler)); } json_append(toolbar, "reload"); json_append(toolbar, std::move(create_button)); Json crud_api = json_object(); json_set(crud_api, "method", "get"); json_set(crud_api, "url", base_api); Json crud = json_object(); json_set(crud, "type", "crud"); json_set(crud, "id", crud_id); json_set(crud, "api", std::move(crud_api)); json_set(crud, "syncLocation", false); json_set(crud, "primaryField", "id"); json_set(crud, "headerToolbar", std::move(toolbar)); if(mode == "table") { Json columns = json_array(); Json id_column = json_object(); json_set(id_column, "name", "id"); json_set(id_column, "label", view.id_label); json_set(id_column, "sortable", true); json_append(columns, std::move(id_column)); for(const auto& definition : view.columns) { Json column_json = make_typed_amis_column_by_name(descriptor, object_descriptor, definition.field); if(!definition.label.empty()) { json_set(column_json, "label", definition.label); } json_set(column_json, "sortable", definition.sortable); if(definition.searchable) { json_set(column_json, "searchable", true); } if(definition.filterable) { json_set(column_json, "filterable", true); } if(definition.fixed == Table_Fixed::left) { json_set(column_json, "fixed", "left"); } else if(definition.fixed == Table_Fixed::right) { json_set(column_json, "fixed", "right"); } json_append(columns, std::move(column_json)); } if(item_status_descriptor) { json_append(columns, make_amis_object_status_column(*item_status_descriptor, base_api + "/${id}/status", item_status_interval, view.view_status_label)); } Json operation = json_object(); json_set(operation, "type", "operation"); json_set(operation, "label", view.actions_label); json_set(operation, "buttons", buttons); json_append(columns, std::move(operation)); json_set(crud, "columns", std::move(columns)); } else { Json item_schema = *item; if(!Json_Adapter::contains(item_schema, "actions")) { json_set(item_schema, "actions", buttons); } json_set(crud, "mode", mode); json_set(crud, mode == "list" ? "listItem" : "card", std::move(item_schema)); if(mode == "cards") { json_set(crud, "columnsCount", cards_columns); } } if(!view.default_order_by.empty()) { Json params = json_object(); json_set(params, "orderBy", view.default_order_by); json_set(params, "orderDir", view.default_order_dir); json_set(crud, "defaultParams", std::move(params)); } if(view.user_reorderable) { json_set(crud, "draggable", true); Json order_api = json_object(); json_set(order_api, "method", "post"); json_set(order_api, "url", base_api + "/order"); json_set(crud, "saveOrderApi", std::move(order_api)); } Json page_body = json_array(); if(overview_status_descriptor && !overview_status_api.empty()) { json_append(page_body, make_amis_status_service(*overview_status_descriptor, std::move(overview_status_api), overview_status_interval)); } json_append(page_body, std::move(crud)); Json result = json_object(); json_set(result, "type", "page"); json_set(result, "title", view.title); json_set(result, "body", std::move(page_body)); return result; } template requires Described_Type> Json to_amis_table_schema(std::string base_api, const Table_View& view, const Json* item_status_descriptor = nullptr, std::uint64_t item_status_interval = 2000, const Json* overview_status_descriptor = nullptr, std::string overview_status_api = {}, std::uint64_t overview_status_interval = 2000) { return make_amis_collection_schema(std::move(base_api), view, "table", nullptr, 0, item_status_descriptor, item_status_interval, overview_status_descriptor, std::move(overview_status_api), overview_status_interval); } template requires Described_Type> Json to_amis_list_schema(std::string base_api, const Table_View& view, Json list_item, const Json* item_status_descriptor = nullptr, std::uint64_t item_status_interval = 2000, const Json* overview_status_descriptor = nullptr, std::string overview_status_api = {}, std::uint64_t overview_status_interval = 2000) { return make_amis_collection_schema(std::move(base_api), view, "list", &list_item, 0, item_status_descriptor, item_status_interval, overview_status_descriptor, std::move(overview_status_api), overview_status_interval); } template requires Described_Type> Json to_amis_cards_schema(std::string base_api, const Table_View& view, Json card, std::size_t columns_count = 3, const Json* item_status_descriptor = nullptr, std::uint64_t item_status_interval = 2000, const Json* overview_status_descriptor = nullptr, std::string overview_status_api = {}, std::uint64_t overview_status_interval = 2000) { return make_amis_collection_schema(std::move(base_api), view, "cards", &card, columns_count, item_status_descriptor, item_status_interval, overview_status_descriptor, std::move(overview_status_api), overview_status_interval); } template requires Described_Type> Json to_amis_collection_schema(std::string base_api, const Collection_View& view, const Json* item_status_descriptor = nullptr, std::uint64_t item_status_interval = 2000, const Json* overview_status_descriptor = nullptr, std::string overview_status_api = {}, std::uint64_t overview_status_interval = 2000) { using Model = Object_Model_Type; validate_collection_view(view); if(view.mode == Collection_Mode::table) { return make_amis_collection_schema(std::move(base_api), view.table, "table", nullptr, 0, item_status_descriptor, item_status_interval, overview_status_descriptor, std::move(overview_status_api), overview_status_interval); } Json item = make_amis_semantic_collection_item(view); return make_amis_collection_schema(std::move(base_api), view.table, std::string(collection_mode_name(view.mode)), &item, view.cards_columns, item_status_descriptor, item_status_interval, overview_status_descriptor, std::move(overview_status_api), overview_status_interval); } }