1129 lines
56 KiB
C++
1129 lines
56 KiB
C++
#pragma once
|
||
#include "adminive/status.hpp"
|
||
#include "adminive/view_json.hpp"
|
||
#include <algorithm>
|
||
#include <cstdint>
|
||
#include <map>
|
||
#include <stdexcept>
|
||
#include <string>
|
||
#include <string_view>
|
||
#include <utility>
|
||
#include <vector>
|
||
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 <Json_Type Json>
|
||
const Json& field_presentation(const Json& field) {
|
||
return Json_Adapter<Json>::at(field, "presentation");
|
||
}
|
||
template <Json_Type Json>
|
||
std::string field_label(const Json& field) {
|
||
return json_get<Json, std::string>(Json_Adapter<Json>::at(field_presentation<Json>(field), "label"));
|
||
}
|
||
template <Json_Type Json>
|
||
void apply_visible_on(Json& control, const Json& field, std::string_view prefix) {
|
||
const auto& presentation = field_presentation<Json>(field);
|
||
if(Json_Adapter<Json>::contains(presentation, "visible_on")) {
|
||
json_set(control, "visibleOn", resolve_visible_on(json_get<Json, std::string>(Json_Adapter<Json>::at(presentation, "visible_on")), prefix));
|
||
}
|
||
}
|
||
template <Json_Type Json>
|
||
Json make_amis_form_actions(std::string submit_label = "Apply") {
|
||
Json reset = json_object<Json>();
|
||
json_set(reset, "type", "reset");
|
||
json_set(reset, "label", "Reset");
|
||
Json submit = json_object<Json>();
|
||
json_set(submit, "type", "submit");
|
||
json_set(submit, "label", std::move(submit_label));
|
||
json_set(submit, "level", "primary");
|
||
Json result = json_array<Json>();
|
||
json_append(result, std::move(reset));
|
||
json_append(result, std::move(submit));
|
||
return result;
|
||
}
|
||
template <Json_Type Json>
|
||
Json make_amis_form_body(const Json& descriptor, std::string_view permission, std::string_view prefix = {});
|
||
template <Json_Type Json, Described_Type T>
|
||
Json make_typed_amis_form_body(const Json& descriptor, std::string_view permission, std::string_view prefix = {});
|
||
template <Json_Type Json, class T>
|
||
requires Polymorphic_Type<T, Json>
|
||
Json make_typed_amis_polymorphic_control(const Json& field, std::string_view permission, std::string_view prefix);
|
||
template <Json_Type Json>
|
||
Json make_amis_polymorphic_control(const Json& field, std::string_view permission, std::string_view prefix) {
|
||
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
|
||
const std::string full_name = make_field_name(prefix, name);
|
||
const auto& metadata = Json_Adapter<Json>::at(field, "polymorphic");
|
||
const std::string discriminator = json_get<Json, std::string>(Json_Adapter<Json>::at(metadata, "discriminator"));
|
||
Json selector = json_object<Json>();
|
||
json_set(selector, "type", "select");
|
||
json_set(selector, "name", make_field_name(full_name, discriminator));
|
||
json_set(selector, "label", Json_Adapter<Json>::at(metadata, "discriminator_label"));
|
||
json_set(selector, "options", Json_Adapter<Json>::at(metadata, "options"));
|
||
json_set(selector, "required", true);
|
||
Json body = json_array<Json>();
|
||
json_append(body, std::move(selector));
|
||
const auto& variants = Json_Adapter<Json>::at(metadata, "variants");
|
||
for(std::size_t index = 0; index < Json_Adapter<Json>::size(variants); ++index) {
|
||
const auto& variant = Json_Adapter<Json>::at(variants, index);
|
||
const std::string value = json_get<Json, std::string>(Json_Adapter<Json>::at(variant, "value"));
|
||
Json group = json_object<Json>();
|
||
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>(Json_Adapter<Json>::at(variant, "descriptor"), permission, full_name));
|
||
json_append(body, std::move(group));
|
||
}
|
||
Json result = json_object<Json>();
|
||
json_set(result, "type", "fieldset");
|
||
json_set(result, "title", Json_Adapter<Json>::at(field_presentation<Json>(field), "label"));
|
||
json_set(result, "body", std::move(body));
|
||
json_set(result, "collapsable", true);
|
||
json_set(result, "collapsed", false);
|
||
if(Json_Adapter<Json>::contains(field_presentation<Json>(field), "description")) {
|
||
json_set(result, "description", Json_Adapter<Json>::at(field_presentation<Json>(field), "description"));
|
||
}
|
||
apply_visible_on(result, field, prefix);
|
||
return result;
|
||
}
|
||
template <Json_Type Json, class T>
|
||
requires Polymorphic_Type<T, Json>
|
||
Json make_typed_amis_polymorphic_control(const Json& field, std::string_view permission, std::string_view prefix) {
|
||
using Adapter = Polymorphic_Adapter<std::remove_cvref_t<T>, Json>;
|
||
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
|
||
const std::string full_name = make_field_name(prefix, name);
|
||
const auto& metadata = Json_Adapter<Json>::at(field, "polymorphic");
|
||
const std::string discriminator(Adapter::discriminator());
|
||
Json selector = json_object<Json>();
|
||
json_set(selector, "type", "select");
|
||
json_set(selector, "name", make_field_name(full_name, discriminator));
|
||
json_set(selector, "label", Json_Adapter<Json>::at(metadata, "discriminator_label"));
|
||
json_set(selector, "options", Json_Adapter<Json>::at(metadata, "options"));
|
||
json_set(selector, "required", true);
|
||
Json body = json_array<Json>();
|
||
json_append(body, std::move(selector));
|
||
std::apply([&](const auto&... variant) {
|
||
([&] {
|
||
using Variant = typename std::remove_cvref_t<decltype(variant)>::type;
|
||
const Json descriptor = to_descriptor_json<Json, Variant>();
|
||
Json group = json_object<Json>();
|
||
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<Json, Object_Model_Type<Variant>>(descriptor, permission, full_name));
|
||
json_append(body, std::move(group));
|
||
}(), ...);
|
||
}, Adapter::variants());
|
||
Json result = json_object<Json>();
|
||
json_set(result, "type", "fieldset");
|
||
json_set(result, "title", Json_Adapter<Json>::at(field_presentation<Json>(field), "label"));
|
||
json_set(result, "body", std::move(body));
|
||
json_set(result, "collapsable", true);
|
||
json_set(result, "collapsed", false);
|
||
if(Json_Adapter<Json>::contains(field_presentation<Json>(field), "description")) {
|
||
json_set(result, "description", Json_Adapter<Json>::at(field_presentation<Json>(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_Type Json>
|
||
Json make_default_amis_control(const Json& field, std::string_view permission, std::string_view prefix = {}) {
|
||
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
|
||
const std::string full_name = make_field_name(prefix, name);
|
||
const std::string value_type = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "value_type"));
|
||
if(value_type == "object" && Json_Adapter<Json>::contains(field, "children")) {
|
||
Json descriptor = json_object<Json>();
|
||
json_set(descriptor, "fields", Json_Adapter<Json>::at(field, "children"));
|
||
Json control = json_object<Json>();
|
||
json_set(control, "type", "fieldset");
|
||
json_set(control, "title", Json_Adapter<Json>::at(field_presentation<Json>(field), "label"));
|
||
json_set(control, "body", make_amis_form_body<Json>(descriptor, permission, full_name));
|
||
json_set(control, "collapsable", true);
|
||
json_set(control, "collapsed", false);
|
||
if(Json_Adapter<Json>::contains(field_presentation<Json>(field), "description")) {
|
||
json_set(control, "description", Json_Adapter<Json>::at(field_presentation<Json>(field), "description"));
|
||
}
|
||
apply_visible_on(control, field, prefix);
|
||
return control;
|
||
}
|
||
if(value_type == "polymorphic" && Json_Adapter<Json>::contains(field, "polymorphic")) {
|
||
return make_amis_polymorphic_control<Json>(field, permission, prefix);
|
||
}
|
||
Json control = json_object<Json>();
|
||
json_set(control, "name", full_name);
|
||
json_set(control, "label", Json_Adapter<Json>::at(field_presentation<Json>(field), "label"));
|
||
if(Json_Adapter<Json>::contains(field_presentation<Json>(field), "description")) {
|
||
json_set(control, "description", Json_Adapter<Json>::at(field_presentation<Json>(field), "description"));
|
||
}
|
||
apply_visible_on(control, field, prefix);
|
||
if(!json_get<Json, bool>(Json_Adapter<Json>::at(field, permission))) {
|
||
json_set(control, "type", "static");
|
||
return control;
|
||
}
|
||
const auto control_type = amis_control_type(json_get<Json, std::string>(Json_Adapter<Json>::at(field_presentation<Json>(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, std::string>(Json_Adapter<Json>::at(control, "type"));
|
||
if(Json_Adapter<Json>::contains(field, "nullable") && json_get<Json, bool>(Json_Adapter<Json>::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, bool>(Json_Adapter<Json>::at(field, "required"))) {
|
||
json_set(control, "required", true);
|
||
}
|
||
if(Json_Adapter<Json>::contains(field, "minimum")) {
|
||
json_set(control, "min", Json_Adapter<Json>::at(field, "minimum"));
|
||
}
|
||
if(Json_Adapter<Json>::contains(field, "maximum")) {
|
||
json_set(control, "max", Json_Adapter<Json>::at(field, "maximum"));
|
||
}
|
||
if(Json_Adapter<Json>::contains(field, "multiple_of")) {
|
||
json_set(control, "step", Json_Adapter<Json>::at(field, "multiple_of"));
|
||
}
|
||
if(Json_Adapter<Json>::contains(field, "validation_message")) {
|
||
Json errors = json_object<Json>();
|
||
json_set(errors, "isInt", Json_Adapter<Json>::at(field, "validation_message"));
|
||
json_set(control, "validationErrors", std::move(errors));
|
||
}
|
||
if(Json_Adapter<Json>::contains(field_presentation<Json>(field), "options")) {
|
||
json_set(control, "options", Json_Adapter<Json>::at(field_presentation<Json>(field), "options"));
|
||
}
|
||
return control;
|
||
}
|
||
template <Json_Type Json>
|
||
Json make_amis_control(const Json& field, std::string_view permission, std::string_view prefix = {}) {
|
||
return make_default_amis_control<Json>(field, permission, prefix);
|
||
}
|
||
template <Json_Type Json, class T>
|
||
Json make_typed_amis_control(const Json& field, std::string_view permission, std::string_view prefix = {}) {
|
||
using Storage = std::remove_cvref_t<T>;
|
||
using Value = Adapted_Value_Type<Storage, Json>;
|
||
using Control_Value = Optional_Unwrapped_Type<Value>;
|
||
const Control_Context context{permission, prefix};
|
||
if constexpr(Control_Adapter_With_Control<Storage, Json>) {
|
||
return Control_Adapter<Storage, Json>::make_control(field, context);
|
||
} else if constexpr(!std::same_as<Storage, Value> && Control_Adapter_With_Control<Value, Json>) {
|
||
return Control_Adapter<Value, Json>::make_control(field, context);
|
||
} else if constexpr(!std::same_as<Value, Control_Value> && Control_Adapter_With_Control<Control_Value, Json>) {
|
||
return Control_Adapter<Control_Value, Json>::make_control(field, context);
|
||
} else if constexpr(Polymorphic_Type<Control_Value, Json>) {
|
||
return make_typed_amis_polymorphic_control<Json, Control_Value>(field, permission, prefix);
|
||
} else if constexpr(Described_Type<Control_Value> || Snapshot_Adapted_Object<Control_Value>) {
|
||
using Model = Object_Model_Type<Control_Value>;
|
||
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
|
||
const std::string full_name = make_field_name(prefix, name);
|
||
const Json descriptor = to_descriptor_json<Json, Control_Value>();
|
||
Json control = json_object<Json>();
|
||
json_set(control, "type", "fieldset");
|
||
json_set(control, "title", Json_Adapter<Json>::at(field_presentation<Json>(field), "label"));
|
||
json_set(control, "body", make_typed_amis_form_body<Json, Model>(descriptor, permission, full_name));
|
||
json_set(control, "collapsable", true);
|
||
json_set(control, "collapsed", false);
|
||
if(Json_Adapter<Json>::contains(field_presentation<Json>(field), "description")) {
|
||
json_set(control, "description", Json_Adapter<Json>::at(field_presentation<Json>(field), "description"));
|
||
}
|
||
apply_visible_on(control, field, prefix);
|
||
return control;
|
||
} else {
|
||
return make_default_amis_control<Json>(field, permission, prefix);
|
||
}
|
||
}
|
||
template <Json_Type Json>
|
||
Json make_amis_form_body(const Json& descriptor, std::string_view permission, std::string_view prefix) {
|
||
Json body = json_array<Json>();
|
||
const auto& fields = Json_Adapter<Json>::at(descriptor, "fields");
|
||
for(std::size_t index = 0; index < Json_Adapter<Json>::size(fields); ++index) {
|
||
const auto& field = Json_Adapter<Json>::at(fields, index);
|
||
json_append(body, make_default_amis_control<Json>(field, permission, prefix));
|
||
}
|
||
return body;
|
||
}
|
||
template <std::size_t Index, Json_Type Json, class Descriptor>
|
||
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<decltype(descriptor.fields())>;
|
||
if constexpr(Index < std::tuple_size_v<Fields>) {
|
||
const auto& item = std::get<Index>(descriptor.fields());
|
||
const auto& field = Json_Adapter<Json>::at(fields, Index);
|
||
using Field = std::remove_cvref_t<decltype(item)>;
|
||
json_append(body, make_typed_amis_control<Json, typename Field::member_type>(field, permission, prefix));
|
||
append_typed_amis_form_fields<Index + 1>(body, fields, descriptor, permission, prefix);
|
||
}
|
||
}
|
||
template <Json_Type Json, Described_Type T>
|
||
Json make_typed_amis_form_body(const Json& descriptor, std::string_view permission, std::string_view prefix) {
|
||
Json body = json_array<Json>();
|
||
const auto& fields = Json_Adapter<Json>::at(descriptor, "fields");
|
||
const auto object_descriptor = describe<T>();
|
||
append_typed_amis_form_fields<0>(body, fields, object_descriptor, permission, prefix);
|
||
return body;
|
||
}
|
||
template <Json_Type Json>
|
||
Json make_default_amis_column(const Json& field) {
|
||
Json column = json_object<Json>();
|
||
json_set(column, "name", Json_Adapter<Json>::at(field, "name"));
|
||
json_set(column, "label", Json_Adapter<Json>::at(field_presentation<Json>(field), "label"));
|
||
const std::string value_type = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "value_type"));
|
||
if(value_type == "boolean") {
|
||
json_set(column, "type", "status");
|
||
} else if(value_type == "enum" && Json_Adapter<Json>::contains(field_presentation<Json>(field), "options")) {
|
||
json_set(column, "type", "mapping");
|
||
Json map = json_object<Json>();
|
||
const auto& options = Json_Adapter<Json>::at(field_presentation<Json>(field), "options");
|
||
for(std::size_t index = 0; index < Json_Adapter<Json>::size(options); ++index) {
|
||
const auto& option = Json_Adapter<Json>::at(options, index);
|
||
json_set(map, json_get<Json, std::string>(Json_Adapter<Json>::at(option, "value")), Json_Adapter<Json>::at(option, "label"));
|
||
}
|
||
json_set(column, "map", std::move(map));
|
||
}
|
||
return column;
|
||
}
|
||
template <Json_Type Json, class T>
|
||
Json make_typed_amis_column(const Json& field) {
|
||
using Storage = std::remove_cvref_t<T>;
|
||
using Value = Adapted_Value_Type<Storage, Json>;
|
||
using Control_Value = Optional_Unwrapped_Type<Value>;
|
||
if constexpr(Control_Adapter_With_Column<Storage, Json>) {
|
||
return Control_Adapter<Storage, Json>::make_column(field);
|
||
} else if constexpr(!std::same_as<Storage, Value> && Control_Adapter_With_Column<Value, Json>) {
|
||
return Control_Adapter<Value, Json>::make_column(field);
|
||
} else if constexpr(!std::same_as<Value, Control_Value> && Control_Adapter_With_Column<Control_Value, Json>) {
|
||
return Control_Adapter<Control_Value, Json>::make_column(field);
|
||
} else {
|
||
return make_default_amis_column<Json>(field);
|
||
}
|
||
}
|
||
template <std::size_t Index = 0, Json_Type Json, class Descriptor>
|
||
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<decltype(descriptor.fields())>;
|
||
if constexpr(Index < std::tuple_size_v<Fields>) {
|
||
const auto& item = std::get<Index>(descriptor.fields());
|
||
if(item.name() == field_name) {
|
||
using Field = std::remove_cvref_t<decltype(item)>;
|
||
return make_typed_amis_control<Json, typename Field::member_type>(Json_Adapter<Json>::at(Json_Adapter<Json>::at(descriptor_json, "fields"), Index), permission, prefix);
|
||
}
|
||
return make_typed_amis_control_by_name<Index + 1>(descriptor_json, descriptor, field_name, permission, prefix);
|
||
} else {
|
||
throw std::invalid_argument("view field does not exist: " + std::string(field_name));
|
||
}
|
||
}
|
||
template <std::size_t Index = 0, Json_Type Json, class Descriptor>
|
||
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<decltype(descriptor.fields())>;
|
||
if constexpr(Index < std::tuple_size_v<Fields>) {
|
||
const auto& item = std::get<Index>(descriptor.fields());
|
||
if(item.name() == field_name) {
|
||
using Field = std::remove_cvref_t<decltype(item)>;
|
||
return make_typed_amis_column<Json, typename Field::member_type>(Json_Adapter<Json>::at(Json_Adapter<Json>::at(descriptor_json, "fields"), Index));
|
||
}
|
||
return make_typed_amis_column_by_name<Index + 1>(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<std::string>& 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_Type Json>
|
||
Json make_amis_submit_data(const Json& descriptor, const View_Node& body, std::string_view permission) {
|
||
std::vector<std::string> selected_fields;
|
||
bool all_fields_selected{};
|
||
collect_amis_submit_fields(body, selected_fields, all_fields_selected);
|
||
Json result = json_object<Json>();
|
||
const auto& fields = Json_Adapter<Json>::at(descriptor, "fields");
|
||
for(std::size_t index = 0; index < Json_Adapter<Json>::size(fields); ++index) {
|
||
const auto& field = Json_Adapter<Json>::at(fields, index);
|
||
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::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, bool>(Json_Adapter<Json>::at(field, permission))) {
|
||
json_set(result, name, "${" + name + "}");
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
template <Json_Type Json>
|
||
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_Type Json, Described_Type T>
|
||
Json make_amis_view_body(const View_Node& node, const Json& descriptor_json, std::string_view permission, std::string_view prefix);
|
||
template <Json_Type Json, Described_Type T>
|
||
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<T>();
|
||
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>();
|
||
json_set(result, "type", "container");
|
||
json_set(result, "body", make_amis_view_body<Json, T>(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<Json>();
|
||
for(const auto& child : node.children) {
|
||
Json item = json_object<Json>();
|
||
json_set(item, "type", "container");
|
||
json_set(item, "body", make_amis_view_body<Json, T>(child, descriptor_json, permission, prefix));
|
||
Json style = json_object<Json>();
|
||
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>();
|
||
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>();
|
||
json_set(result, "type", "fieldset");
|
||
json_set(result, "title", node.title);
|
||
json_set(result, "body", make_amis_view_body<Json, T>(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>();
|
||
json_set(result, "type", "card");
|
||
if(!node.title.empty()) {
|
||
Json header = json_object<Json>();
|
||
json_set(header, "title", node.title);
|
||
json_set(result, "header", std::move(header));
|
||
}
|
||
json_set(result, "body", make_amis_view_body<Json, T>(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<Json>();
|
||
const std::size_t count = node.kind == View_Node_Kind::grid && node.columns != 0 ? node.columns : std::max<std::size_t>(1, node.children.size());
|
||
for(std::size_t index = 0; index < node.children.size(); ++index) {
|
||
Json column = json_object<Json>();
|
||
json_set(column, "md", amis_grid_width(index, count));
|
||
json_set(column, "body", make_amis_view_body<Json, T>(node.children[index], descriptor_json, permission, prefix));
|
||
json_append(columns, std::move(column));
|
||
}
|
||
Json result = json_object<Json>();
|
||
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<Json>();
|
||
for(const auto& child : node.children) {
|
||
Json item = json_object<Json>();
|
||
json_set(item, "type", "container");
|
||
json_set(item, "body", make_amis_view_body<Json, T>(child, descriptor_json, permission, prefix));
|
||
Json item_style = json_object<Json>();
|
||
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>();
|
||
json_set(result, "type", "flex");
|
||
json_set(result, "direction", "row");
|
||
json_set(result, "items", std::move(items));
|
||
Json style = json_object<Json>();
|
||
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<Json>();
|
||
for(const auto& child : node.children) {
|
||
Json item = json_object<Json>();
|
||
json_set(item, "title", child.title);
|
||
json_set(item, "body", make_amis_view_body<Json, T>(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>();
|
||
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>();
|
||
json_set(result, "type", "container");
|
||
json_set(result, "body", make_amis_view_body<Json, T>(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 <Json_Type Json, Described_Type T>
|
||
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<Json, T>(descriptor_json, permission, prefix);
|
||
for(std::size_t index = 0; index < Json_Adapter<Json>::size(fields); ++index) {
|
||
json_append(body, Json_Adapter<Json>::at(fields, index));
|
||
}
|
||
return;
|
||
}
|
||
if(node.kind == View_Node_Kind::vertical) {
|
||
for(const auto& child : node.children) {
|
||
append_amis_view_node<Json, T>(body, child, descriptor_json, permission, prefix);
|
||
}
|
||
return;
|
||
}
|
||
json_append(body, make_amis_view_node<Json, T>(node, descriptor_json, permission, prefix));
|
||
}
|
||
template <Json_Type Json, Described_Type T>
|
||
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<Json>();
|
||
append_amis_view_node<Json, T>(result, node, descriptor_json, permission, prefix);
|
||
return result;
|
||
}
|
||
template <Json_Type Json>
|
||
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_Type Json>
|
||
Json make_amis_composition_body(const Composition_Node& node, const std::map<std::string, Json>& slots);
|
||
template <Json_Type Json>
|
||
Json make_amis_composition_node(const Composition_Node& node, const std::map<std::string, Json>& 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>();
|
||
json_set(result, "type", "tpl");
|
||
std::string html = "<h3 style=\"margin:0";
|
||
if(!node.accent_color.empty()) {
|
||
html += ";color:" + node.accent_color;
|
||
}
|
||
html += "\">" + node.title + "</h3>";
|
||
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>();
|
||
json_set(result, "type", "container");
|
||
json_set(result, "body", make_amis_composition_body<Json>(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<Json>();
|
||
for(const auto& child : node.children) {
|
||
Json item = json_object<Json>();
|
||
json_set(item, "type", "container");
|
||
json_set(item, "body", make_amis_composition_body<Json>(child, slots));
|
||
Json style = json_object<Json>();
|
||
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>();
|
||
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>();
|
||
json_set(result, "type", "panel");
|
||
json_set(result, "title", node.title);
|
||
json_set(result, "body", make_amis_composition_body<Json>(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>();
|
||
json_set(result, "type", "card");
|
||
if(!node.title.empty()) {
|
||
Json header = json_object<Json>();
|
||
json_set(header, "title", node.title);
|
||
json_set(result, "header", std::move(header));
|
||
}
|
||
json_set(result, "body", make_amis_composition_body<Json>(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<Json>();
|
||
const std::size_t count = node.kind == Composition_Node_Kind::grid && node.columns != 0 ? node.columns : std::max<std::size_t>(1, node.children.size());
|
||
for(std::size_t index = 0; index < node.children.size(); ++index) {
|
||
Json column = json_object<Json>();
|
||
json_set(column, "md", amis_grid_width(index, count));
|
||
json_set(column, "body", make_amis_composition_body<Json>(node.children[index], slots));
|
||
json_append(columns, std::move(column));
|
||
}
|
||
Json result = json_object<Json>();
|
||
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<Json>();
|
||
for(const auto& child : node.children) {
|
||
Json item = json_object<Json>();
|
||
json_set(item, "type", "container");
|
||
json_set(item, "body", make_amis_composition_body<Json>(child, slots));
|
||
Json item_style = json_object<Json>();
|
||
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>();
|
||
json_set(result, "type", "flex");
|
||
json_set(result, "direction", "row");
|
||
json_set(result, "items", std::move(items));
|
||
Json style = json_object<Json>();
|
||
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<Json>();
|
||
for(const auto& child : node.children) {
|
||
Json item = json_object<Json>();
|
||
json_set(item, "title", child.title);
|
||
json_set(item, "body", make_amis_composition_body<Json>(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>();
|
||
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>();
|
||
json_set(result, "type", "container");
|
||
json_set(result, "body", make_amis_composition_body<Json>(Composition_Node{Composition_Node_Kind::vertical, {}, {}, {}, {}, {}, 0, false, false, node.children}, slots));
|
||
apply_composition_node_options(result, node);
|
||
return result;
|
||
}
|
||
template <Json_Type Json>
|
||
void append_amis_composition_node(Json& body, const Composition_Node& node, const std::map<std::string, Json>& 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<Json>::is_array(iterator->second)) {
|
||
for(std::size_t index = 0; index < Json_Adapter<Json>::size(iterator->second); ++index) {
|
||
json_append(body, Json_Adapter<Json>::at(iterator->second, index));
|
||
}
|
||
} else {
|
||
json_append(body, iterator->second);
|
||
}
|
||
return;
|
||
}
|
||
json_append(body, make_amis_composition_node<Json>(node, slots));
|
||
}
|
||
template <Json_Type Json>
|
||
Json make_amis_composition_body(const Composition_Node& node, const std::map<std::string, Json>& slots) {
|
||
Json result = json_array<Json>();
|
||
append_amis_composition_node(result, node, slots);
|
||
return result;
|
||
}
|
||
template <Json_Type Json>
|
||
Json to_amis_composition_schema(const Composition_View& view, const std::map<std::string, Json>& slots) {
|
||
validate_composition_view(view);
|
||
Json result = json_object<Json>();
|
||
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<Json>(view.body, slots));
|
||
return result;
|
||
}
|
||
template <Json_Type Json>
|
||
Json make_amis_status_body(const Json& descriptor) {
|
||
Json body = json_array<Json>();
|
||
const auto& fields = Json_Adapter<Json>::at(descriptor, "fields");
|
||
for(std::size_t index = 0; index < Json_Adapter<Json>::size(fields); ++index) {
|
||
const auto& field = Json_Adapter<Json>::at(fields, index);
|
||
const std::string name = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "name"));
|
||
const std::string label = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "label"));
|
||
const bool color = json_get<Json, bool>(Json_Adapter<Json>::at(field, "color"));
|
||
std::string tpl = "<div class=\"adminive-object-status-row\"><span class=\"adminive-object-status-label\">" + label + "</span><strong";
|
||
if(color) {
|
||
tpl += " style=\"color: ${" + name + ".color}\"";
|
||
}
|
||
tpl += ">${" + name + ".value}</strong></div>";
|
||
Json item = json_object<Json>();
|
||
json_set(item, "type", "tpl");
|
||
json_set(item, "tpl", std::move(tpl));
|
||
json_append(body, std::move(item));
|
||
}
|
||
Json result = json_object<Json>();
|
||
json_set(result, "type", "container");
|
||
json_set(result, "className", "adminive-object-status");
|
||
json_set(result, "body", std::move(body));
|
||
return result;
|
||
}
|
||
template <Json_Type Json>
|
||
Json make_amis_status_service(const Json& descriptor, std::string status_api, std::uint64_t interval = 2000) {
|
||
Json panel = json_object<Json>();
|
||
json_set(panel, "type", "panel");
|
||
json_set(panel, "title", Json_Adapter<Json>::at(descriptor, "label"));
|
||
json_set(panel, "body", make_amis_status_body<Json>(descriptor));
|
||
Json api = json_object<Json>();
|
||
json_set(api, "method", "get");
|
||
json_set(api, "url", std::move(status_api));
|
||
Json result = json_object<Json>();
|
||
json_set(result, "type", "service");
|
||
json_set(result, "name", json_get<Json, std::string>(Json_Adapter<Json>::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_Type Json>
|
||
Json make_amis_object_status_service(const Json& descriptor, std::string status_api, std::uint64_t interval) {
|
||
Json api = json_object<Json>();
|
||
json_set(api, "method", "get");
|
||
json_set(api, "url", std::move(status_api));
|
||
Json result = json_object<Json>();
|
||
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<Json>(descriptor));
|
||
return result;
|
||
}
|
||
template <Json_Type Json>
|
||
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>();
|
||
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<Json>(descriptor, std::move(status_api), interval));
|
||
Json result = json_object<Json>();
|
||
json_set(result, "type", "tpl");
|
||
json_set(result, "label", Json_Adapter<Json>::at(descriptor, "label"));
|
||
json_set(result, "tpl", "<span class=\"text-primary\">" + std::move(action_label) + "</span>");
|
||
json_set(result, "popOver", std::move(pop_over));
|
||
return result;
|
||
}
|
||
template <Json_Type Json, class T>
|
||
requires Described_Type<std::remove_cvref_t<T>> || Snapshot_Adapted_Object<T>
|
||
Json to_amis_form_schema(const T& value, const Form_View& view, std::string submit_api = {}) {
|
||
using Model = Object_Model_Type<T>;
|
||
validate_form_view<Model>(view);
|
||
const Json descriptor = to_descriptor_json<Json, T>();
|
||
Json result = json_object<Json>();
|
||
json_set(result, "type", "form");
|
||
json_set(result, "title", view.title);
|
||
json_set(result, "data", to_frontend_json<Json>(value));
|
||
json_set(result, "body", make_amis_view_body<Json, Model>(view.body, descriptor, form_permission(view.mode), {}));
|
||
if(view.mode != Form_Mode::display) {
|
||
json_set(result, "actions", make_amis_form_actions<Json>(view.submit_label));
|
||
json_set(result, "affixFooter", view.affix_footer);
|
||
if(!submit_api.empty()) {
|
||
Json api = json_object<Json>();
|
||
json_set(api, "method", "post");
|
||
json_set(api, "url", std::move(submit_api));
|
||
json_set(api, "data", make_amis_submit_data<Json>(descriptor, view.body, form_permission(view.mode)));
|
||
json_set(result, "api", std::move(api));
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
template <Json_Type Json, Managed_Value_Type T>
|
||
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<Json>(item, view, std::move(submit_api));
|
||
});
|
||
}
|
||
template <Json_Type Json, Managed_Field_Type T>
|
||
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<Json>(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_Type Json>
|
||
Json make_amis_semantic_collection_item(const Collection_View& view) {
|
||
Json body = json_array<Json>();
|
||
for(const auto& field : view.item.fields) {
|
||
Json line = json_object<Json>();
|
||
json_set(line, "type", "tpl");
|
||
json_set(line, "tpl", "<span class=\"text-muted\">" + collection_field_label(view, field) + ":</span><strong>${" + field + "}</strong>");
|
||
json_append(body, std::move(line));
|
||
}
|
||
if(!view.item.accent_field.empty()) {
|
||
Json accent = json_object<Json>();
|
||
json_set(accent, "type", "tpl");
|
||
json_set(accent, "tpl", "<div style=\"height:6px;border-radius:999px;background:${" + view.item.accent_field + "};margin-top:10px\"></div>");
|
||
json_append(body, std::move(accent));
|
||
}
|
||
if(view.mode == Collection_Mode::list) {
|
||
Json result = json_object<Json>();
|
||
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>();
|
||
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>();
|
||
json_set(result, "header", std::move(header));
|
||
json_set(result, "body", std::move(body));
|
||
return result;
|
||
}
|
||
template <Json_Type Json, class T>
|
||
requires Described_Type<Object_Model_Type<T>>
|
||
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<T>;
|
||
validate_table_view<Model>(view);
|
||
const Json descriptor = to_descriptor_json<Json, T>();
|
||
const auto object_descriptor = describe<Model>();
|
||
std::string crud_id = view.name + "_crud";
|
||
if(mode != "table") {
|
||
crud_id += "_" + mode;
|
||
}
|
||
Json create_api = json_object<Json>();
|
||
json_set(create_api, "method", "post");
|
||
json_set(create_api, "url", base_api);
|
||
json_set(create_api, "data", make_amis_submit_data<Json>(descriptor, view.create_body, "creatable"));
|
||
Json create_form = json_object<Json>();
|
||
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<Json, Model>(view.create_body, descriptor, "creatable", {}));
|
||
json_set(create_form, "actions", make_amis_form_actions<Json>(view.create_label));
|
||
Json edit_api = json_object<Json>();
|
||
json_set(edit_api, "method", "put");
|
||
json_set(edit_api, "url", base_api + "/${id}");
|
||
json_set(edit_api, "data", make_amis_submit_data<Json>(descriptor, view.edit_body, "editable"));
|
||
Json edit_form = json_object<Json>();
|
||
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<Json, Model>(view.edit_body, descriptor, "editable", {}));
|
||
json_set(edit_form, "actions", make_amis_form_actions<Json>(view.confirm_label));
|
||
Json create_dialog = json_object<Json>();
|
||
json_set(create_dialog, "title", view.create_label + " " + json_get<Json, std::string>(Json_Adapter<Json>::at(descriptor, "label")));
|
||
json_set(create_dialog, "body", std::move(create_form));
|
||
Json create_button = json_object<Json>();
|
||
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>();
|
||
json_set(edit_dialog, "title", view.edit_label + " " + json_get<Json, std::string>(Json_Adapter<Json>::at(descriptor, "label")));
|
||
json_set(edit_dialog, "body", std::move(edit_form));
|
||
Json edit_button = json_object<Json>();
|
||
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>();
|
||
json_set(delete_api, "method", "delete");
|
||
json_set(delete_api, "url", base_api + "/${id}");
|
||
Json delete_button = json_object<Json>();
|
||
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>();
|
||
json_append(buttons, std::move(edit_button));
|
||
json_append(buttons, std::move(delete_button));
|
||
Json toolbar = json_array<Json>();
|
||
if(mode == "table") {
|
||
Json toggler = json_object<Json>();
|
||
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>();
|
||
json_set(crud_api, "method", "get");
|
||
json_set(crud_api, "url", base_api);
|
||
Json crud = json_object<Json>();
|
||
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>();
|
||
Json id_column = json_object<Json>();
|
||
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<Json>(*item_status_descriptor, base_api + "/${id}/status", item_status_interval, view.view_status_label));
|
||
}
|
||
Json operation = json_object<Json>();
|
||
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<Json>::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>();
|
||
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>();
|
||
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<Json>();
|
||
if(overview_status_descriptor && !overview_status_api.empty()) {
|
||
json_append(page_body, make_amis_status_service<Json>(*overview_status_descriptor, std::move(overview_status_api), overview_status_interval));
|
||
}
|
||
json_append(page_body, std::move(crud));
|
||
Json result = json_object<Json>();
|
||
json_set(result, "type", "page");
|
||
json_set(result, "title", view.title);
|
||
json_set(result, "body", std::move(page_body));
|
||
return result;
|
||
}
|
||
template <Json_Type Json, class T>
|
||
requires Described_Type<Object_Model_Type<T>>
|
||
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<Json, T>(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 <Json_Type Json, class T>
|
||
requires Described_Type<Object_Model_Type<T>>
|
||
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<Json, T>(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 <Json_Type Json, class T>
|
||
requires Described_Type<Object_Model_Type<T>>
|
||
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<Json, T>(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 <Json_Type Json, class T>
|
||
requires Described_Type<Object_Model_Type<T>>
|
||
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<T>;
|
||
validate_collection_view<Model>(view);
|
||
if(view.mode == Collection_Mode::table) {
|
||
return make_amis_collection_schema<Json, T>(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<Json>(view);
|
||
return make_amis_collection_schema<Json, T>(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);
|
||
}
|
||
|
||
}
|