438 lines
40 KiB
C++
438 lines
40 KiB
C++
#include "gallery.hpp"
|
||
#include <map>
|
||
#include <set>
|
||
#include <stdexcept>
|
||
#include <string>
|
||
#include <string_view>
|
||
#include <type_traits>
|
||
#include <utility>
|
||
#include <vector>
|
||
namespace adminive::example {
|
||
namespace {
|
||
Json make_gallery_note(std::string title, std::string text) {
|
||
return Json{{"type", "tpl"}, {"tpl", "<strong>" + std::move(title) + "</strong><div class=\"text-muted\" style=\"margin-top:6px\">" + std::move(text) + "</div>"}};
|
||
}
|
||
Composition_View make_backend_layout_view() {
|
||
return composition_view("component_gallery", compose::tabs(
|
||
compose::tab("字段表单", compose::slot("form")),
|
||
compose::tab("后端组合定位", compose::vertical(
|
||
compose::heading("后端 Composition 决定组件位置").accent("#2563eb"),
|
||
compose::group("横向 horizontal", compose::horizontal(compose::card("横向 A", compose::slot("horizontal_a")), compose::card("横向 B", compose::slot("horizontal_b")), compose::card("横向 C", compose::slot("horizontal_c")))),
|
||
compose::group("竖向 vertical", compose::vertical(compose::card("第一项", compose::slot("vertical_a")), compose::card("第二项", compose::slot("vertical_b")), compose::card("第三项", compose::slot("vertical_c")))),
|
||
compose::group("列表 list", compose::list(compose::slot("list_a"), compose::slot("list_b"), compose::slot("list_c"))),
|
||
compose::group("卡片 + 网格", compose::grid(3, compose::card("Card A", compose::slot("card_a")), compose::card("Card B", compose::slot("card_b")), compose::card("Card C", compose::slot("card_c")))),
|
||
compose::group("Flow 自动换行", compose::flow(compose::card("Flow A", compose::slot("flow_a")), compose::card("Flow B", compose::slot("flow_b")), compose::card("Flow C", compose::slot("flow_c")), compose::card("Flow D", compose::slot("flow_d")))).collapsible_when(),
|
||
compose::group("Tabs 嵌套", compose::tabs(compose::tab("说明", compose::slot("tabs_a")), compose::tab("嵌套卡片", compose::card("Tab Card", compose::slot("tabs_b"))))))),
|
||
compose::tab("列表 / 卡片 / 表格", compose::vertical(compose::heading("同一 Collection View 的三种展示"), compose::tabs(compose::tab("列表", compose::slot("collection_list")), compose::tab("卡片", compose::slot("collection_cards")), compose::tab("表格", compose::slot("collection_table"))))),
|
||
compose::tab("高级 Adapter", compose::grid(4, compose::card("Value / Control / Sensitive", compose::slot("advanced_adapter")), compose::card("Reflection", compose::slot("advanced_reflection")), compose::card("Polymorphic", compose::slot("advanced_polymorphic")), compose::card("Object Adapter", compose::slot("advanced_object")))),
|
||
compose::tab("前端决定定位", compose::frontend(compose::slot("frontend_a"), compose::slot("frontend_b"), compose::slot("frontend_c")).described("frontend 节点只声明组件成员与顺序,具体横排、竖排、列表、卡片或表格由前端决定。"))
|
||
)).titled("Adminive 组件画廊");
|
||
}
|
||
Composition_View make_frontend_layout_view() {
|
||
return composition_view("frontend_owned_layout", compose::frontend(compose::slot("alpha"), compose::slot("beta"), compose::slot("gamma"))).titled("前端定位演示");
|
||
}
|
||
Collection_Item_View make_gallery_item_view() {
|
||
using T = Gallery_Row;
|
||
return collection_item<&T::name>().subtitle<&T::category>().accent<&T::color>().body<&T::score, &T::enabled, &T::color>();
|
||
}
|
||
Collection_View make_gallery_collection_view(Collection_Mode mode) {
|
||
auto result = describe_collection_view<Gallery_Row>();
|
||
if(mode == Collection_Mode::list) {
|
||
return result.as_list(make_gallery_item_view());
|
||
}
|
||
if(mode == Collection_Mode::cards) {
|
||
return result.as_cards(make_gallery_item_view(), 3);
|
||
}
|
||
return result.as_table();
|
||
}
|
||
Json string_array(std::initializer_list<const char*> values) {
|
||
Json result = Json::array();
|
||
for(const char* value : values) {
|
||
result.push_back(value);
|
||
}
|
||
return result;
|
||
}
|
||
struct Capability_Case {
|
||
std::string id;
|
||
std::string group;
|
||
std::string title;
|
||
std::string summary;
|
||
std::string cpp;
|
||
Json view;
|
||
Json amis;
|
||
Json preview;
|
||
Json tags;
|
||
Json endpoints;
|
||
};
|
||
Capability_Case make_doc_case(std::string id, std::string group, std::string title, std::string summary, std::string cpp, Json view, Json amis, Json preview, Json tags, Json endpoints = Json::array()) {
|
||
return Capability_Case{std::move(id), std::move(group), std::move(title), std::move(summary), std::move(cpp), std::move(view), std::move(amis), std::move(preview), std::move(tags), std::move(endpoints)};
|
||
}
|
||
Json capability_case_json(const Capability_Case& item) {
|
||
return Json{{"id", item.id}, {"group", item.group}, {"title", item.title}, {"summary", item.summary}, {"cpp", item.cpp}, {"view", item.view}, {"amis", item.amis}, {"preview", item.preview}, {"tags", item.tags}, {"endpoints", item.endpoints}};
|
||
}
|
||
Json capability_cases_json(const std::vector<Capability_Case>& cases) {
|
||
Json result = Json::array();
|
||
for(const auto& item : cases) {
|
||
result.push_back(capability_case_json(item));
|
||
}
|
||
return result;
|
||
}
|
||
Request_Context gallery_request_context(const httplib::Request& request) {
|
||
Request_Context context;
|
||
context.user = request.get_header_value("X-Adminive-User");
|
||
context.request_id = request.get_header_value("X-Request-Id");
|
||
context.remote_address = request.remote_addr;
|
||
context.attributes.emplace("transport", "httplib");
|
||
return context;
|
||
}
|
||
template <Described_Type T>
|
||
Json make_field_policy_snapshot() {
|
||
const auto descriptor = describe<T>();
|
||
Json fields = Json::array();
|
||
std::apply([&](const auto&... item) {
|
||
([&] {
|
||
using Field = std::remove_cvref_t<decltype(item)>;
|
||
fields.push_back(Json{{"name", item.name()}, {"readable", item.is_readable()}, {"writable", Field::managed_writable}, {"editable", item.is_editable()}, {"creatable", item.is_creatable()}, {"sensitive", item.is_sensitive()}, {"required", item.is_required()}, {"include_default", item.includes_default()}, {"synchronized", Field::managed_writable && Field::synchronized}});
|
||
}(), ...);
|
||
}, descriptor.fields());
|
||
return fields;
|
||
}
|
||
Json make_managed_snapshot(const Managed_Value<Gallery_Config>& value) {
|
||
const Json descriptor = to_descriptor_json<Json, Gallery_Config>();
|
||
const auto resolved = value.resolved_synchronization();
|
||
Json fields = Json::array();
|
||
for(std::size_t index = 0; index < descriptor.at("fields").size(); ++index) {
|
||
const auto& field = descriptor.at("fields").at(index);
|
||
Json item{{"name", field.at("name")}, {"writable", field.at("writable")}, {"editable", field.at("editable")}, {"creatable", field.at("creatable")}, {"synchronized", field.at("synchronized")}};
|
||
const auto slot = resolved.slot(index);
|
||
if(slot == structive::Resolved_Synchronization_View::unsynchronized_slot) {
|
||
item["synchronization_slot"] = "unsynchronized";
|
||
} else {
|
||
item["synchronization_slot"] = slot;
|
||
}
|
||
fields.push_back(std::move(item));
|
||
}
|
||
return Json{{"lock_count", resolved.lock_count}, {"fields", std::move(fields)}, {"note", "managed writable 是后端写能力,editable/creatable 是前端提交能力;两者不是同一概念。"}};
|
||
}
|
||
template <class Function>
|
||
Json rejected_case(std::string id, std::string cpp, Function&& function) {
|
||
try {
|
||
std::forward<Function>(function)();
|
||
return Json{{"id", std::move(id)}, {"cpp", std::move(cpp)}, {"rejected", false}, {"error", "no error"}};
|
||
} catch(const std::exception& error) {
|
||
return Json{{"id", std::move(id)}, {"cpp", std::move(cpp)}, {"rejected", true}, {"error", error.what()}};
|
||
}
|
||
}
|
||
Json protocol_boundaries() {
|
||
Json result = Json::array();
|
||
result.push_back(rejected_case("grid-zero", "compose::grid(0, compose::slot(\"a\"))", [] {
|
||
to_view_json<Json>(composition_view("invalid_grid", compose::grid(0, compose::slot("a"))));
|
||
}));
|
||
result.push_back(rejected_case("tab-outside-tabs", "composition_view(\"invalid_tab\", compose::tab(\"x\", compose::slot(\"a\")))", [] {
|
||
to_view_json<Json>(composition_view("invalid_tab", compose::tab("x", compose::slot("a"))));
|
||
}));
|
||
result.push_back(rejected_case("manifest-missing-contract", "to_composition_manifest_json(view, {}, {})", [] {
|
||
const auto view = composition_view("missing_contract", compose::slot("a"));
|
||
to_composition_manifest_json<Json>(view, {}, {});
|
||
}));
|
||
result.push_back(rejected_case("manifest-duplicate-contract", "contracts = {a, a}", [] {
|
||
const auto view = composition_view("duplicate_contract", compose::slot("a"));
|
||
const std::vector<Composition_Slot_Contract> contracts{{"a", "amis_schema", {}, {}, {}, {}}, {"a", "amis_schema", {}, {}, {}, {}}};
|
||
const std::map<std::string, Json> schemas{{"a", make_gallery_note("a", "a")}};
|
||
to_composition_manifest_json<Json>(view, contracts, schemas);
|
||
}));
|
||
result.push_back(rejected_case("manifest-duplicate-slot", "compose::frontend(compose::slot(\"a\"), compose::slot(\"a\"))", [] {
|
||
const auto view = composition_view("duplicate_slot", compose::frontend(compose::slot("a"), compose::slot("a")));
|
||
const std::vector<Composition_Slot_Contract> contracts{{"a", "amis_schema", {}, {}, {}, {}}};
|
||
const std::map<std::string, Json> schemas{{"a", make_gallery_note("a", "a")}};
|
||
to_composition_manifest_json<Json>(view, contracts, schemas);
|
||
}));
|
||
Gallery_Config readonly_value;
|
||
const auto readonly_update = apply_frontend_patch(readonly_value, Json{{"read_only_text", "forbidden"}});
|
||
result.push_back(Json{{"id", "readonly-submit"}, {"request", Json{{"read_only_text", "forbidden"}}}, {"status", 422}, {"response", make_http_error<Json>(422, readonly_update).body}});
|
||
Gallery_Config invalid_value;
|
||
const auto multi_update = apply_frontend_patch(invalid_value, Json{{"even_number", 3}, {"number_value", 101}, {"nested", Json{{"port", 70000}}}});
|
||
result.push_back(Json{{"id", "multi-field-validation"}, {"request", Json{{"even_number", 3}, {"number_value", 101}, {"nested", Json{{"port", 70000}}}}}, {"status", 422}, {"response", make_http_error<Json>(422, multi_update).body}});
|
||
Radio_State invalid_radio;
|
||
const auto object_update = apply_frontend_patch(invalid_radio, Json{{"low_watermark", 20}, {"high_watermark", 10}});
|
||
result.push_back(Json{{"id", "object-validator"}, {"request", Json{{"low_watermark", 20}, {"high_watermark", 10}}}, {"status", object_update.success ? 200 : 422}, {"response", object_update.success ? make_http_success<Json>(to_frontend_json<Json>(invalid_radio)).body : make_http_error<Json>(422, object_update).body}});
|
||
Gallery_Polymorphic_Config invalid_polymorphic;
|
||
const auto polymorphic_update = apply_frontend_patch(invalid_polymorphic, Json{{"target", Json{{"type", "unknown"}}}});
|
||
result.push_back(Json{{"id", "polymorphic-discriminator"}, {"request", Json{{"target", Json{{"type", "unknown"}}}}}, {"status", 422}, {"response", make_http_error<Json>(422, polymorphic_update).body}});
|
||
Collection_Service<Gallery_Row, Json> collection("/boundary", {Gallery_Row{"Alpha", Gallery_Category::primary, Range_Value<int, 0, 100>{92}, true, "#2563eb"}});
|
||
Collection_Query invalid_filter;
|
||
invalid_filter.fields.emplace("score", "92");
|
||
const auto filter_response = collection.list_response(std::move(invalid_filter));
|
||
result.push_back(Json{{"id", "collection-invalid-filter"}, {"status", filter_response.status}, {"response", filter_response.body}});
|
||
Collection_Query invalid_sort;
|
||
invalid_sort.order_by = "color";
|
||
invalid_sort.order_dir = "asc";
|
||
const auto sort_response = collection.list_response(std::move(invalid_sort));
|
||
result.push_back(Json{{"id", "collection-invalid-sort"}, {"status", sort_response.status}, {"response", sort_response.body}});
|
||
return result;
|
||
}
|
||
Http_Response<Json> transaction_scenario(std::string_view scenario, const httplib::Request& request) {
|
||
Gallery_Runtime_Config runtime;
|
||
std::vector<std::string> events;
|
||
Resource_Transaction<Gallery_Runtime_Model> transaction;
|
||
transaction.prepare = [&](const Gallery_Runtime_Model&, const Request_Context&) {
|
||
events.push_back("prepare");
|
||
if(scenario == "prepare-reject") {
|
||
throw std::runtime_error("prepare rejected by demo transaction");
|
||
}
|
||
};
|
||
transaction.commit = [&](const Gallery_Runtime_Model&, const Request_Context&) {
|
||
events.push_back("commit");
|
||
if(scenario == "commit-fail" || scenario == "rollback-fail") {
|
||
throw std::runtime_error("external commit failed");
|
||
}
|
||
};
|
||
transaction.rollback = [&](const Gallery_Runtime_Model&, const Request_Context&) {
|
||
events.push_back("rollback");
|
||
if(scenario == "rollback-fail") {
|
||
throw std::runtime_error("external rollback failed");
|
||
}
|
||
};
|
||
Resource_Service<Gallery_Runtime_Config, Json> service(runtime, "/transaction", std::move(transaction));
|
||
const std::string body = request.body.empty() ? R"({"name":"Transaction Scenario"})" : request.body;
|
||
const auto context = gallery_request_context(request);
|
||
auto response = service.update_response(body, context);
|
||
response.body["demo"] = Json{{"scenario", scenario}, {"events", events}, {"runtime", to_frontend_json<Json>(runtime)}, {"context", Json{{"user", context.user}, {"request_id", context.request_id}, {"remote_address", context.remote_address}, {"attributes", context.attributes}}}};
|
||
return response;
|
||
}
|
||
Json derive_capabilities(const std::vector<Capability_Case>& cases) {
|
||
Json capabilities = Json::object();
|
||
std::map<std::string, std::set<std::string>> values;
|
||
for(const auto& item : cases) {
|
||
for(const auto& tag : item.tags) {
|
||
values[item.group].insert(tag.get<std::string>());
|
||
}
|
||
}
|
||
for(const auto& [group, tags] : values) {
|
||
Json array = Json::array();
|
||
for(const auto& tag : tags) {
|
||
array.push_back(tag);
|
||
}
|
||
capabilities[group] = std::move(array);
|
||
}
|
||
return capabilities;
|
||
}
|
||
Json derive_endpoints(const std::vector<Capability_Case>& cases) {
|
||
std::set<std::string> values;
|
||
values.insert("/admin/amis");
|
||
values.insert("/admin/status");
|
||
values.insert("/admin/gallery/docs");
|
||
for(const auto& item : cases) {
|
||
for(const auto& endpoint : item.endpoints) {
|
||
values.insert(endpoint.get<std::string>());
|
||
}
|
||
}
|
||
Json result = Json::array();
|
||
for(const auto& value : values) {
|
||
result.push_back(value);
|
||
}
|
||
return result;
|
||
}
|
||
}
|
||
Component_Gallery::Component_Gallery() : config_resource_(config_, "/admin/gallery/config"), rows_("/admin/gallery/items", make_rows()), adapter_resource_(adapter_config_, "/admin/gallery/advanced/adapter"), reflected_resource_(reflected_config_, "/admin/gallery/advanced/reflection"), polymorphic_resource_(polymorphic_config_, "/admin/gallery/advanced/polymorphic"), runtime_resource_(runtime_config_, "/admin/gallery/advanced/object", make_resource_transaction<Gallery_Runtime_Model>([this](const Gallery_Runtime_Model&, const Request_Context&) { runtime_config_.transaction_committed(); })) {
|
||
rows_.register_overview_status<Server_Status>("/admin/status", 2000);
|
||
rows_.register_status<&Gallery_Row::status>(2000);
|
||
}
|
||
Json Component_Gallery::amis_schema() const {
|
||
const Json form = config_resource_.amis_schema();
|
||
const auto composition = make_backend_layout_view();
|
||
std::map<std::string, Json> slots;
|
||
slots.emplace("form", form);
|
||
slots.emplace("horizontal_a", make_gallery_note("horizontal", "后端明确声明横向组合,Adapter 翻译为 AMIS grid。"));
|
||
slots.emplace("horizontal_b", make_gallery_note("slot", "Slot 可以放 Form、Collection、Status 或任意已生成组件。"));
|
||
slots.emplace("horizontal_c", make_gallery_note("responsive", "后端声明组合语义,Adapter 负责断点和具体 renderer。"));
|
||
slots.emplace("vertical_a", make_gallery_note("vertical", "竖向只表达先后顺序。"));
|
||
slots.emplace("vertical_b", make_gallery_note("group", "Group 支持标题、折叠和继续嵌套。"));
|
||
slots.emplace("vertical_c", make_gallery_note("heading", "Heading 可以带强调色。"));
|
||
slots.emplace("list_a", make_gallery_note("list / 1", "列表节点把多个业务组件表达为线性条目。"));
|
||
slots.emplace("list_b", make_gallery_note("list / 2", "列表与 vertical 不同,它保留条目语义。"));
|
||
slots.emplace("list_c", make_gallery_note("list / 3", "AMIS Adapter 给条目添加明确的视觉分隔。"));
|
||
slots.emplace("card_a", make_gallery_note("card", "Card 是组合容器。"));
|
||
slots.emplace("card_b", make_gallery_note("grid", "Grid 由后端声明逻辑列数。"));
|
||
slots.emplace("card_c", make_gallery_note("nest", "Card、Grid、Group、Tabs 可以互相嵌套。"));
|
||
slots.emplace("flow_a", make_gallery_note("flow", "Flow 使用可换行 Flex。"));
|
||
slots.emplace("flow_b", make_gallery_note("flow", "适合宽度不固定的组件。"));
|
||
slots.emplace("flow_c", make_gallery_note("flow", "每个子项都是合法 renderer。"));
|
||
slots.emplace("flow_d", make_gallery_note("flow", "窗口变窄时自动换行。"));
|
||
slots.emplace("tabs_a", make_gallery_note("tabs", "Tabs 是一等组合节点。"));
|
||
slots.emplace("tabs_b", make_gallery_note("tab", "Tab 只能作为 Tabs 的直接子节点,验证阶段会拒绝非法位置。"));
|
||
slots.emplace("collection_list", collection_schema(Collection_Mode::list).at("body"));
|
||
slots.emplace("collection_cards", collection_schema(Collection_Mode::cards).at("body"));
|
||
slots.emplace("collection_table", collection_schema(Collection_Mode::table).at("body"));
|
||
slots.emplace("advanced_adapter", adapter_resource_.amis_schema());
|
||
slots.emplace("advanced_reflection", reflected_resource_.amis_schema());
|
||
slots.emplace("advanced_polymorphic", polymorphic_resource_.amis_schema());
|
||
slots.emplace("advanced_object", runtime_resource_.amis_schema());
|
||
slots.emplace("frontend_a", make_gallery_note("frontend", "后端只声明成员和顺序,不声明横向、竖向、列表、卡片或表格。"));
|
||
slots.emplace("frontend_b", make_gallery_note("slot registry", "manifest 明确给出每个 slot 的类型、来源接口和 renderer schema。"));
|
||
slots.emplace("frontend_c", make_gallery_note("fallback", "AMIS Adapter 对 frontend 节点提供中性的竖向 fallback,不偷做业务布局决策。"));
|
||
return to_amis_composition_schema<Json>(composition, slots);
|
||
}
|
||
Json Component_Gallery::backend_layout_view_json() const {
|
||
return to_view_json<Json>(make_backend_layout_view());
|
||
}
|
||
Json Component_Gallery::frontend_layout_manifest() const {
|
||
const auto view = make_frontend_layout_view();
|
||
const std::vector<Composition_Slot_Contract> contracts{
|
||
{"alpha", "amis_schema", "/admin/gallery/config/descriptor", "/admin/gallery/config/view", "/admin/gallery/config/data", "/admin/gallery/config/amis"},
|
||
{"beta", "amis_schema", "/admin/gallery/items/descriptor", "/admin/gallery/items/list/view", "/admin/gallery/items", "/admin/gallery/items/list/amis"},
|
||
{"gamma", "amis_schema", "/admin/gallery/items/descriptor", "/admin/gallery/items/cards/view", "/admin/gallery/items", "/admin/gallery/items/cards/amis"}
|
||
};
|
||
const std::map<std::string, Json> schemas{{"alpha", config_resource_.amis_schema()}, {"beta", collection_schema(Collection_Mode::list)}, {"gamma", collection_schema(Collection_Mode::cards)}};
|
||
return to_composition_manifest_json<Json>(view, contracts, schemas);
|
||
}
|
||
Json Component_Gallery::collection_view_json(Collection_Mode mode) const {
|
||
return to_view_json<Json, Gallery_Row>(make_gallery_collection_view(mode));
|
||
}
|
||
Json Component_Gallery::collection_schema(Collection_Mode mode) const {
|
||
const Json item_status = to_status_descriptor_json<Json, Gallery_Row_Status>();
|
||
const Json overview_status = to_status_descriptor_json<Json, Server_Status>();
|
||
return to_amis_collection_schema<Json, Gallery_Row>("/admin/gallery/items", make_gallery_collection_view(mode), &item_status, 2000, &overview_status, "/admin/status", 2000);
|
||
}
|
||
Json Component_Gallery::documentation() const {
|
||
Gallery_Config config_value;
|
||
config_.read([&](const Gallery_Config& value) {
|
||
config_value = value;
|
||
});
|
||
const Form_View display_view = detail_view<Gallery_Config>();
|
||
const Form_View create_view = create_form<Gallery_Config>();
|
||
const Form_View edit_view = describe_edit_view<Gallery_Config>();
|
||
Gallery_Config invalid_config = config_value;
|
||
const Update_Result invalid_update = apply_frontend_patch(invalid_config, Json{{"even_number", 3}});
|
||
const Json validation_error = make_http_error<Json>(422, invalid_update).body;
|
||
const Json status_descriptor = to_status_descriptor_json<Json, Server_Status>();
|
||
const Json status_amis = make_amis_status_service<Json>(status_descriptor, "/admin/status", 2000);
|
||
Json examples = Json::object();
|
||
examples["descriptor"] = to_descriptor_json<Json, Gallery_Config>();
|
||
examples["field_policies"] = make_field_policy_snapshot<Gallery_Config>();
|
||
examples["form_view"] = config_resource_.view_schema();
|
||
examples["form_display_view"] = to_view_json<Json, Gallery_Config>(display_view);
|
||
examples["form_create_view"] = to_view_json<Json, Gallery_Config>(create_view);
|
||
examples["form_edit_view"] = to_view_json<Json, Gallery_Config>(edit_view);
|
||
examples["form_display_amis"] = to_amis_form_schema<Json>(config_value, display_view);
|
||
examples["form_create_amis"] = to_amis_form_schema<Json>(Gallery_Config{}, create_view);
|
||
examples["form_edit_amis"] = config_resource_.amis_schema();
|
||
examples["validation_422"] = validation_error;
|
||
examples["collection_list_view"] = collection_view_json(Collection_Mode::list);
|
||
examples["collection_cards_view"] = collection_view_json(Collection_Mode::cards);
|
||
examples["collection_table_view"] = collection_view_json(Collection_Mode::table);
|
||
examples["backend_composition_view"] = backend_layout_view_json();
|
||
examples["frontend_manifest"] = frontend_layout_manifest();
|
||
examples["backend_amis"] = amis_schema();
|
||
examples["list_amis"] = collection_schema(Collection_Mode::list);
|
||
examples["cards_amis"] = collection_schema(Collection_Mode::cards);
|
||
examples["table_amis"] = collection_schema(Collection_Mode::table);
|
||
examples["adapter_descriptor"] = to_descriptor_json<Json, Gallery_Adapter_Config>();
|
||
examples["adapter_view"] = adapter_resource_.view_schema();
|
||
examples["adapter_data"] = to_frontend_json<Json>(adapter_config_);
|
||
examples["adapter_amis"] = adapter_resource_.amis_schema();
|
||
examples["reflection_descriptor"] = to_descriptor_json<Json, Gallery_Reflected_Config>();
|
||
examples["reflection_view"] = reflected_resource_.view_schema();
|
||
examples["reflection_data"] = to_frontend_json<Json>(reflected_config_);
|
||
examples["reflection_amis"] = reflected_resource_.amis_schema();
|
||
examples["enum_values"] = Json{{"gallery_category", string_array({"primary", "secondary", "experimental"})}, {"encoded_row", to_frontend_json<Json>(make_rows().front())}};
|
||
examples["json_adapter_roundtrip"] = parse_json<Json>(dump_json(Json{{"adapter", "nlohmann_json"}, {"value", 7}}));
|
||
examples["polymorphic_descriptor"] = to_descriptor_json<Json, Gallery_Polymorphic_Config>();
|
||
examples["polymorphic_view"] = polymorphic_resource_.view_schema();
|
||
examples["polymorphic_data"] = to_frontend_json<Json>(polymorphic_config_);
|
||
examples["polymorphic_amis"] = polymorphic_resource_.amis_schema();
|
||
examples["object_adapter_descriptor"] = to_descriptor_json<Json, Gallery_Runtime_Config>();
|
||
examples["object_adapter_view"] = runtime_resource_.view_schema();
|
||
examples["object_adapter_data"] = to_frontend_json<Json>(runtime_config_);
|
||
examples["object_adapter_amis"] = runtime_resource_.amis_schema();
|
||
examples["managed_synchronization"] = make_managed_snapshot(config_);
|
||
examples["protocol_boundaries"] = protocol_boundaries();
|
||
examples["status_descriptor"] = status_descriptor;
|
||
examples["status_amis"] = status_amis;
|
||
examples["data"] = to_frontend_json<Json>(config_value);
|
||
examples["items"] = rows_.items_json();
|
||
std::vector<Capability_Case> cases;
|
||
cases.push_back(make_doc_case("form-layout", "fields", "Form View:字段、权限与组合布局", "字段事实来自 Descriptor,Form View 只负责字段选择、顺序与组合;readable、read_write、editable、creatable、required、include_default 和 sensitive 各自保持独立语义。", "field(...).read_write().readable(false).include_default(false); edit_form<Gallery_Config>(vertical(group(...), grid(...), flow(...), tabs(...)))", Json{{"form_view", config_resource_.view_schema()}, {"field_policies", examples["field_policies"]}}, config_resource_.amis_schema(), config_resource_.amis_schema(), string_array({"automatic", "text", "multiline", "number", "range", "validated", "boolean", "select", "date", "color", "optional", "readonly", "sensitive", "visible_on", "nested", "readable", "read_write", "editable", "creatable", "required", "include_default", "enum_label"}), string_array({"/admin/gallery/config/descriptor", "/admin/gallery/config/view", "/admin/gallery/config/data", "/admin/gallery/config/amis"})));
|
||
cases.push_back(make_doc_case("form-modes", "fields", "Form Mode:display / create / edit", "三种模式使用同一 Descriptor,但根据字段权限和 mode 生成不同 View、提交字段和 AMIS。", "detail_view<T>(); create_form<T>(); describe_edit_view<T>();", Json{{"display", examples["form_display_view"]}, {"create", examples["form_create_view"]}, {"edit", examples["form_edit_view"]}}, Json{{"display", examples["form_display_amis"]}, {"create", examples["form_create_amis"]}, {"edit", examples["form_edit_amis"]}}, examples["form_edit_amis"], string_array({"display", "create", "edit", "writable_submit_filter"}), string_array({"/admin/gallery/config/data"})));
|
||
cases.push_back(make_doc_case("backend-composition", "composition", "Composition:后端决定定位", "horizontal / vertical / list / grid / card / flow / tabs 都由后端 Composition JSON 明确表达。", "composition_view(\"component_gallery\", compose::tabs(...))", backend_layout_view_json(), amis_schema(), amis_schema(), string_array({"slot", "heading", "vertical", "horizontal", "flow", "grid", "list", "group", "card", "tabs", "tab"}), string_array({"/admin/gallery/backend/view", "/admin/gallery/backend/amis"})));
|
||
cases.push_back(make_doc_case("frontend-composition", "composition", "Composition Manifest:前端决定定位", "frontend 节点只保留成员与顺序;slot registry 明确组件类型和来源,前端用同一批真实组件做五种布局。", "to_composition_manifest_json(view, contracts, schemas)", frontend_layout_manifest(), Json{{"renderer_registry", "component_kind -> renderer"}}, make_gallery_note("前端定位 Preview", "同一 manifest 的五种真实布局在前端定位页由通用 renderer 展示。"), string_array({"frontend", "slot_contract", "composition_manifest", "renderer_registry"}), string_array({"/admin/gallery/frontend/manifest"})));
|
||
cases.push_back(make_doc_case("collection-modes", "collection", "Collection View:table / list / cards", "三种展示是 adminive.view 的一等 Collection mode,复用同一 Descriptor、查询、CRUD、重排和状态事实。", "describe_collection_view<Gallery_Row>().as_table(); .as_list(item); .as_cards(item, 3);", Json{{"list", examples["collection_list_view"]}, {"cards", examples["collection_cards_view"]}, {"table", examples["collection_table_view"]}}, Json{{"list", examples["list_amis"]}, {"cards", examples["cards_amis"]}, {"table", examples["table_amis"]}}, examples["table_amis"], string_array({"collection_view", "table", "list", "cards", "server_paging", "sort", "search", "filter", "create", "edit", "delete", "row_reorder", "column_reorder", "overview_status", "item_status"}), string_array({"/admin/gallery/items", "/admin/gallery/items/{id}", "/admin/gallery/items/order", "/admin/gallery/items/{id}/status", "/admin/status"})));
|
||
cases.push_back(make_doc_case("value-control-sensitive", "adapters", "Value / Control Adapter + Sensitive", "自定义值类型通过 Value_Adapter 映射数据,Control_Adapter 决定控件;sensitive 字段不会进入 frontend data。", "Value_Adapter<Gallery_Color_Code, Json>; Control_Adapter<Gallery_Color_Code, Json>; field.sensitive()", Json{{"descriptor", examples["adapter_descriptor"]}, {"frontend_data", examples["adapter_data"]}}, examples["adapter_amis"], examples["adapter_amis"], string_array({"value_adapter", "control_adapter", "sensitive_output_filter"}), string_array({"/admin/gallery/advanced/adapter/descriptor", "/admin/gallery/advanced/adapter/data", "/admin/gallery/advanced/adapter/amis"})));
|
||
cases.push_back(make_doc_case("json-enum-reflection", "adapters", "Json / Enum / Reflection Adapter", "JSON、枚举和无成员指针反射都使用正式 Adapter 契约;Reflection 示例由字段索引和名字驱动 Descriptor。", "Json_Adapter<Json>; Enum_Adapter<Gallery_Category>; Reflection_Adapter<Gallery_Reflected_Config>", Json{{"reflection", examples["reflection_descriptor"]}, {"enum", examples["enum_values"]}, {"json", examples["json_adapter_roundtrip"]}}, examples["reflection_amis"], examples["reflection_amis"], string_array({"json_adapter", "enum_adapter", "reflection_adapter"}), string_array({"/admin/gallery/advanced/reflection/descriptor", "/admin/gallery/advanced/reflection/data", "/admin/gallery/advanced/reflection/amis"})));
|
||
cases.push_back(make_doc_case("polymorphic", "adapters", "Polymorphic Adapter", "variant 的 discriminator、可选类型和每个子类型 Descriptor 都进入协议,AMIS 自动生成类型切换与子表单。", "Polymorphic_Adapter<Gallery_Target, Json>::variants()", examples["polymorphic_descriptor"], examples["polymorphic_amis"], examples["polymorphic_amis"], string_array({"polymorphic_adapter", "discriminator", "variant"}), string_array({"/admin/gallery/advanced/polymorphic/descriptor", "/admin/gallery/advanced/polymorphic/data", "/admin/gallery/advanced/polymorphic/amis"})));
|
||
cases.push_back(make_doc_case("object-transaction", "runtime", "Object Adapter + Resource Transaction", "运行时对象通过 Object_Adapter snapshot/commit 映射到可描述 Model;事务实验台覆盖 success、prepare reject、commit fail、rollback fail。", "Object_Adapter<Gallery_Runtime_Config>; Resource_Transaction<Gallery_Runtime_Model>", examples["object_adapter_view"], examples["object_adapter_amis"], examples["object_adapter_amis"], string_array({"object_adapter", "resource_transaction", "prepare", "commit", "rollback", "request_context"}), string_array({"/admin/gallery/advanced/object/data", "/admin/gallery/transaction/{scenario}"})));
|
||
cases.push_back(make_doc_case("managed-synchronization", "runtime", "Managed_Value 与同步拓扑", "Managed_Value 使用 Structive 的同步拓扑;后端 writable、前端 editable/creatable 和 synchronized 分别展示,避免把权限和锁混为一谈。", "Managed_Value<Gallery_Config>; value.resolved_synchronization(); value.member<&T::field>().write(...)", examples["managed_synchronization"], Json{{"transport", "Resource_Service<Managed_Value<T>>"}}, make_gallery_note("Managed / Synchronization", "同步拓扑和字段能力来自运行中的 Managed_Value,而不是第二套前端字段表。"), string_array({"managed_value", "managed_update", "synchronization_topology", "unsynchronized_field", "backend_writable", "frontend_editable"}), string_array({"/admin/gallery/config/data", "/admin/gallery/managed"})));
|
||
cases.push_back(make_doc_case("validation-boundaries", "safety", "Validation 与协议边界", "字段校验、nested path、object validator、readonly 提交、多态 discriminator、非法 Composition/Manifest 和非法 Collection query 都有可重复的标准错误。", "apply_frontend_patch(...); validate_composition_view(...); validate_composition_slot_contracts(...)", examples["protocol_boundaries"], examples["validation_422"], make_gallery_note("协议边界", "在“协议边界”页面可以逐项执行真实请求并查看标准错误。"), string_array({"validation_422", "field_errors", "nested_path", "object_validator", "readonly_reject", "invalid_grid", "invalid_tab", "missing_slot_contract", "duplicate_slot", "invalid_sort", "invalid_filter"}), string_array({"/admin/gallery/boundaries", "/admin/gallery/items?orderBy=color&orderDir=asc", "/admin/gallery/items?score=92"})));
|
||
Json status_endpoints = Json::array({"/admin/status", std::string(radio_states_api) + "/{id}/status"});
|
||
cases.push_back(make_doc_case("status", "runtime", "Status Polling:overview + item", "状态 Descriptor 与轮询 renderer 分离;完整运行时同时展示 overview status 和 Collection item status。", "make_amis_status_service<Json>(descriptor, api, interval); collection.register_status<&T::status>()", status_descriptor, status_amis, status_amis, string_array({"status_polling", "overview_status", "item_status"}), std::move(status_endpoints)));
|
||
cases.push_back(make_doc_case("http-adapters", "adapters", "HTTP Adapter:Httplib + Drogon", "Httplib 与 Drogon 都从 transport 构造 Request_Context,并把非保留 Collection query 完整交给同一 Service 校验;Httplib 也提供独立 Http_Status_Resource。真实黑盒测试覆盖 CRUD、严格查询、状态、异常边界和上下文传递。", "resource.bind(server, Httplib_Bind_Options{.context_factory = ...}); Http_Status_Resource<Status, Json>(...); Drogon_Bind_Options{...}", Json{{"httplib", "context_factory + strict query + standalone status"}, {"drogon", "context_factory + strict query + executor/filter"}}, Json{{"tests", string_array({"Adminive_Httplib_Adapter_Test", "Adminive_Drogon_Adapter_Test"})}}, make_gallery_note("Transport Adapter", "当前 Gallery 本身运行在 Httplib;两种 transport 对共享 Service 的 query 与错误语义保持一致,Drogon 由独立 adapter test 验证。"), string_array({"httplib", "drogon", "request_context", "strict_query", "standalone_status", "real_http_test"}), string_array({"/admin/gallery/config/data"})));
|
||
const Json verification_view = Json{{"ctest_labels", string_array({"unit", "protocol", "http", "install", "package", "header", "concurrency"})}, {"sanitizer_matrix", Json{{"clang_gnu", string_array({"ASan", "UBSan", "TSan"})}, {"msvc", string_array({"ASan"})}, {"unsupported_configuration", "configure_error"}}}, {"configurations", string_array({"Debug", "Release"})}};
|
||
const Json verification_runtime = Json{{"headers", "all public Adminive headers compile standalone"}, {"install_consumers", string_array({"Adminive::Core", "Adminive::Default + Httplib"})}, {"package_zip", "exact root files + no stale nested project"}, {"frontend", string_array({"node_test", "playwright_chromium", "playwright_msedge"})}};
|
||
cases.push_back(make_doc_case("verification", "engineering", "工程验证 Gate", "Release-safe test checks、公共头独立编译、源码包结构、Core + Default/Httplib 安装消费、真实 HTTP、Debug/Release 和平台真实支持的 Sanitizer 都有正式入口。", "ctest -L header; ctest -L package; ctest -L install; python scripts/verify.py", verification_view, verification_runtime, make_gallery_note("Verification Gate", "测试断言在 Release 下不会被 NDEBUG 清除;不支持的 sanitizer 配置会明确失败或 SKIP,不会假通过。"), string_array({"release_safe_checks", "public_header_compile", "package_zip", "install_consumer", "debug", "release", "asan", "ubsan", "tsan", "frontend_unit", "playwright", "edge"})));
|
||
Json layers = Json::array();
|
||
layers.push_back(Json{{"name", "Object Schema"}, {"purpose", "字段结构、读写能力、约束与同步事实"}});
|
||
layers.push_back(Json{{"name", "Field Presentation"}, {"purpose", "单字段 label、description、control、options、visible_on"}});
|
||
layers.push_back(Json{{"name", "View Schema"}, {"purpose", "Form/Collection 字段选择、顺序、查询能力与字段组合"}});
|
||
layers.push_back(Json{{"name", "Composition Manifest"}, {"purpose", "跨组件组合、slot contract 以及 frontend 布局授权"}});
|
||
layers.push_back(Json{{"name", "Adapter / Runtime"}, {"purpose", "AMIS、HTTP、Managed、Transaction、Status 和浏览器 renderer"}});
|
||
return Json{{"title", "Adminive 协议驱动能力画廊"}, {"summary", "Capability Case Registry 是唯一能力清单:每一项都绑定真实 C++ 声明、协议 JSON、renderer/HTTP 入口或自动测试,不再靠静态 capability 字符串宣称支持。"}, {"layers", std::move(layers)}, {"capabilities", derive_capabilities(cases)}, {"endpoints", derive_endpoints(cases)}, {"examples", std::move(examples)}, {"cases", capability_cases_json(cases)}};
|
||
}
|
||
void Component_Gallery::bind(httplib::Server& server) {
|
||
config_resource_.bind(server, Httplib_Bind_Options{gallery_request_context});
|
||
rows_.bind(server);
|
||
adapter_resource_.bind(server, Httplib_Bind_Options{gallery_request_context});
|
||
reflected_resource_.bind(server, Httplib_Bind_Options{gallery_request_context});
|
||
polymorphic_resource_.bind(server, Httplib_Bind_Options{gallery_request_context});
|
||
runtime_resource_.bind(server, Httplib_Bind_Options{gallery_request_context});
|
||
server.Get("/admin/gallery/backend/view", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(backend_layout_view_json()));
|
||
});
|
||
server.Get("/admin/gallery/backend/amis", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(amis_schema()));
|
||
});
|
||
server.Get("/admin/gallery/frontend/manifest", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(frontend_layout_manifest()));
|
||
});
|
||
server.Get("/admin/gallery/items/list/view", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(collection_view_json(Collection_Mode::list)));
|
||
});
|
||
server.Get("/admin/gallery/items/cards/view", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(collection_view_json(Collection_Mode::cards)));
|
||
});
|
||
server.Get("/admin/gallery/items/table/view", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(collection_view_json(Collection_Mode::table)));
|
||
});
|
||
server.Get("/admin/gallery/items/list/amis", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(collection_schema(Collection_Mode::list)));
|
||
});
|
||
server.Get("/admin/gallery/items/cards/amis", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(collection_schema(Collection_Mode::cards)));
|
||
});
|
||
server.Get("/admin/gallery/items/table/amis", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(collection_schema(Collection_Mode::table)));
|
||
});
|
||
server.Get("/admin/gallery/managed", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(make_managed_snapshot(config_)));
|
||
});
|
||
server.Get("/admin/gallery/boundaries", [](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(protocol_boundaries()));
|
||
});
|
||
server.Post(R"(/admin/gallery/transaction/(success|prepare-reject|commit-fail|rollback-fail))", [](const httplib::Request& request, httplib::Response& response) {
|
||
write_http_response(response, transaction_scenario(request.matches[1].str(), request));
|
||
});
|
||
server.Get("/admin/gallery/docs", [this](const httplib::Request&, httplib::Response& response) {
|
||
write_http_response(response, make_http_success<Json>(documentation()));
|
||
});
|
||
}
|
||
std::vector<Gallery_Row> Component_Gallery::make_rows() {
|
||
return {
|
||
Gallery_Row{"Alpha", Gallery_Category::primary, Range_Value<int, 0, 100>{92}, true, "#2563eb"},
|
||
Gallery_Row{"Beta", Gallery_Category::secondary, Range_Value<int, 0, 100>{76}, true, "#16a34a"},
|
||
Gallery_Row{"Gamma", Gallery_Category::experimental, Range_Value<int, 0, 100>{58}, false, "#d97706"},
|
||
Gallery_Row{"Delta", Gallery_Category::primary, Range_Value<int, 0, 100>{84}, true, "#7c3aed"},
|
||
Gallery_Row{"Epsilon", Gallery_Category::secondary, Range_Value<int, 0, 100>{67}, false, "#0891b2"},
|
||
Gallery_Row{"Zeta", Gallery_Category::experimental, Range_Value<int, 0, 100>{49}, true, "#dc2626"}
|
||
};
|
||
}
|
||
}
|