608 lines
32 KiB
C++
608 lines
32 KiB
C++
#pragma once
|
|
#include "adminive/status.hpp"
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#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>
|
|
void apply_visible_on(Json& control, const Json& field, std::string_view prefix) {
|
|
if(Json_Adapter<Json>::contains(field, "visible_on")) {
|
|
json_set(control, "visibleOn", resolve_visible_on(json_get<Json, std::string>(Json_Adapter<Json>::at(field, "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, "label"));
|
|
json_set(result, "body", std::move(body));
|
|
json_set(result, "collapsable", true);
|
|
json_set(result, "collapsed", false);
|
|
if(Json_Adapter<Json>::contains(field, "description")) {
|
|
json_set(result, "description", Json_Adapter<Json>::at(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, "label"));
|
|
json_set(result, "body", std::move(body));
|
|
json_set(result, "collapsable", true);
|
|
json_set(result, "collapsed", false);
|
|
if(Json_Adapter<Json>::contains(field, "description")) {
|
|
json_set(result, "description", Json_Adapter<Json>::at(field, "description"));
|
|
}
|
|
apply_visible_on(result, field, prefix);
|
|
return result;
|
|
}
|
|
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, "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, "description")) {
|
|
json_set(control, "description", Json_Adapter<Json>::at(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, "label"));
|
|
if(Json_Adapter<Json>::contains(field, "description")) {
|
|
json_set(control, "description", Json_Adapter<Json>::at(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;
|
|
}
|
|
if(Json_Adapter<Json>::contains(field, "widget")) {
|
|
json_set(control, "type", Json_Adapter<Json>::at(field, "widget"));
|
|
} else if(value_type == "boolean") {
|
|
json_set(control, "type", "switch");
|
|
} else if(value_type == "integer" || value_type == "number") {
|
|
json_set(control, "type", "input-number");
|
|
} else if(value_type == "enum") {
|
|
json_set(control, "type", "select");
|
|
} else if(value_type == "string") {
|
|
json_set(control, "type", "input-text");
|
|
} else {
|
|
throw std::invalid_argument("field '" + full_name + "' requires an explicit widget or Control_Adapter");
|
|
}
|
|
const std::string 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(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(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, "options")) {
|
|
json_set(control, "options", Json_Adapter<Json>::at(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, "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, "description")) {
|
|
json_set(control, "description", Json_Adapter<Json>::at(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);
|
|
if(json_get<Json, bool>(Json_Adapter<Json>::at(field, "visible"))) {
|
|
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);
|
|
if(json_get<Json, bool>(Json_Adapter<Json>::at(field, "visible"))) {
|
|
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, "list_label"));
|
|
if(Json_Adapter<Json>::contains(field, "order")) {
|
|
json_set(column, "order", Json_Adapter<Json>::at(field, "order"));
|
|
}
|
|
if(Json_Adapter<Json>::contains(field, "sortable")) {
|
|
json_set(column, "sortable", Json_Adapter<Json>::at(field, "sortable"));
|
|
}
|
|
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, "options")) {
|
|
json_set(column, "type", "mapping");
|
|
Json map = json_object<Json>();
|
|
const auto& options = Json_Adapter<Json>::at(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 <Json_Type Json>
|
|
struct Amis_Ordered_Column {
|
|
int order{};
|
|
Json column;
|
|
};
|
|
template <std::size_t Index, Json_Type Json, class Descriptor>
|
|
void append_typed_amis_columns(std::vector<Amis_Ordered_Column<Json>>& columns, const Json& fields, const Descriptor& descriptor) {
|
|
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)>;
|
|
using Member = typename Field::member_type;
|
|
using Storage = std::remove_cvref_t<Member>;
|
|
using Value = Adapted_Value_Type<Storage, Json>;
|
|
using Control_Value = Optional_Unwrapped_Type<Value>;
|
|
constexpr bool custom_column = Control_Adapter_With_Column<Storage, Json> || (!std::same_as<Storage, Value> && Control_Adapter_With_Column<Value, Json>) || (!std::same_as<Value, Control_Value> && Control_Adapter_With_Column<Control_Value, Json>);
|
|
const std::string value_type = json_get<Json, std::string>(Json_Adapter<Json>::at(field, "value_type"));
|
|
const bool structural = value_type == "object" || value_type == "array" || value_type == "map" || value_type == "polymorphic";
|
|
if(json_get<Json, bool>(Json_Adapter<Json>::at(field, "readable")) && json_get<Json, bool>(Json_Adapter<Json>::at(field, "list_visible")) && (!structural || custom_column)) {
|
|
columns.push_back(Amis_Ordered_Column<Json>{json_get<Json, int>(Json_Adapter<Json>::at(field, "order")), make_typed_amis_column<Json, Member>(field)});
|
|
}
|
|
append_typed_amis_columns<Index + 1>(columns, fields, descriptor);
|
|
}
|
|
}
|
|
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, std::string submit_api = {}, std::string submit_label = "Apply") {
|
|
using Model = Object_Model_Type<T>;
|
|
const Json descriptor = to_descriptor_json<Json, T>();
|
|
Json result = json_object<Json>();
|
|
json_set(result, "type", "form");
|
|
json_set(result, "title", Json_Adapter<Json>::at(descriptor, "label"));
|
|
json_set(result, "data", to_frontend_json<Json>(value));
|
|
json_set(result, "body", make_typed_amis_form_body<Json, Model>(descriptor, "editable"));
|
|
json_set(result, "actions", make_amis_form_actions<Json>(std::move(submit_label)));
|
|
json_set(result, "affixFooter", true);
|
|
if(!submit_api.empty()) {
|
|
Json api = json_object<Json>();
|
|
json_set(api, "method", "post");
|
|
json_set(api, "url", std::move(submit_api));
|
|
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, std::string submit_api = {}, std::string submit_label = "Apply") {
|
|
return value.read([&](const auto& item) {
|
|
return adminive::to_amis_form_schema<Json>(item, std::move(submit_api), std::move(submit_label));
|
|
});
|
|
}
|
|
template <Json_Type Json, Managed_Field_Type T>
|
|
Json to_amis_form_schema(const T& value, std::string submit_api = {}, std::string submit_label = "Apply") {
|
|
return value.read([&](const auto& item) {
|
|
return adminive::to_amis_form_schema<Json>(item, std::move(submit_api), std::move(submit_label));
|
|
});
|
|
}
|
|
template <Json_Type Json, class T>
|
|
requires Described_Type<Object_Model_Type<T>>
|
|
Json to_amis_crud_schema(std::string base_api, const Json* status_descriptor = nullptr, std::uint64_t status_interval = 2000) {
|
|
using Model = Object_Model_Type<T>;
|
|
const Json descriptor = to_descriptor_json<Json, T>();
|
|
const Json& list = Json_Adapter<Json>::at(descriptor, "list");
|
|
const std::string crud_id = json_get<Json, std::string>(Json_Adapter<Json>::at(descriptor, "name")) + "_crud";
|
|
Json columns = json_array<Json>();
|
|
Json id_column = json_object<Json>();
|
|
json_set(id_column, "name", "id");
|
|
json_set(id_column, "label", Json_Adapter<Json>::at(list, "id_label"));
|
|
json_set(id_column, "sortable", true);
|
|
json_append(columns, std::move(id_column));
|
|
std::vector<Amis_Ordered_Column<Json>> ordered_columns;
|
|
const auto& fields = Json_Adapter<Json>::at(descriptor, "fields");
|
|
const auto object_descriptor = describe<Model>();
|
|
append_typed_amis_columns<0>(ordered_columns, fields, object_descriptor);
|
|
std::stable_sort(ordered_columns.begin(), ordered_columns.end(), [](const Amis_Ordered_Column<Json>& left, const Amis_Ordered_Column<Json>& right) {
|
|
return left.order < right.order;
|
|
});
|
|
for(auto& column : ordered_columns) {
|
|
json_append(columns, std::move(column.column));
|
|
}
|
|
if(status_descriptor) {
|
|
json_append(columns, make_amis_object_status_column<Json>(*status_descriptor, base_api + "/${id}/status", status_interval, json_get<Json, std::string>(Json_Adapter<Json>::at(list, "view_status_label"))));
|
|
}
|
|
const std::string create_label = json_get<Json, std::string>(Json_Adapter<Json>::at(list, "create_label"));
|
|
const std::string edit_label = json_get<Json, std::string>(Json_Adapter<Json>::at(list, "edit_label"));
|
|
Json create_api = json_object<Json>();
|
|
json_set(create_api, "method", "post");
|
|
json_set(create_api, "url", base_api);
|
|
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_typed_amis_form_body<Json, Model>(descriptor, "creatable"));
|
|
json_set(create_form, "actions", make_amis_form_actions<Json>(create_label));
|
|
Json edit_api = json_object<Json>();
|
|
json_set(edit_api, "method", "put");
|
|
json_set(edit_api, "url", base_api + "/${id}");
|
|
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_typed_amis_form_body<Json, Model>(descriptor, "editable"));
|
|
json_set(edit_form, "actions", make_amis_form_actions<Json>(json_get<Json, std::string>(Json_Adapter<Json>::at(list, "confirm_label"))));
|
|
Json create_dialog = json_object<Json>();
|
|
json_set(create_dialog, "title", 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", 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", 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", 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", Json_Adapter<Json>::at(list, "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", Json_Adapter<Json>::at(list, "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 operation = json_object<Json>();
|
|
json_set(operation, "type", "operation");
|
|
json_set(operation, "label", Json_Adapter<Json>::at(list, "actions_label"));
|
|
json_set(operation, "buttons", std::move(buttons));
|
|
json_append(columns, std::move(operation));
|
|
Json toggler = json_object<Json>();
|
|
json_set(toggler, "type", "columns-toggler");
|
|
json_set(toggler, "draggable", Json_Adapter<Json>::at(list, "column_reorderable"));
|
|
Json toolbar = json_array<Json>();
|
|
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));
|
|
json_set(crud, "columns", std::move(columns));
|
|
const std::string default_order_by = json_get<Json, std::string>(Json_Adapter<Json>::at(list, "default_order_by"));
|
|
if(!default_order_by.empty()) {
|
|
Json params = json_object<Json>();
|
|
json_set(params, "orderBy", default_order_by);
|
|
json_set(params, "orderDir", Json_Adapter<Json>::at(list, "default_order_dir"));
|
|
json_set(crud, "defaultParams", std::move(params));
|
|
}
|
|
if(json_get<Json, bool>(Json_Adapter<Json>::at(list, "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 result = json_object<Json>();
|
|
json_set(result, "type", "page");
|
|
json_set(result, "title", Json_Adapter<Json>::at(list, "management_label"));
|
|
json_set(result, "body", std::move(crud));
|
|
return result;
|
|
}
|
|
template <Json_Type Json, class T>
|
|
requires Described_Type<Object_Model_Type<T>>
|
|
Json to_amis_crud_status_schema(std::string base_api, std::string status_api, const Json& status_descriptor, std::uint64_t status_interval = 2000, const Json* object_status_descriptor = nullptr, std::uint64_t object_status_interval = 2000) {
|
|
Json result = to_amis_crud_schema<Json, T>(std::move(base_api), object_status_descriptor, object_status_interval);
|
|
Json crud = std::move(Json_Adapter<Json>::at(result, "body"));
|
|
Json body = json_array<Json>();
|
|
json_append(body, make_amis_status_service<Json>(status_descriptor, std::move(status_api), status_interval));
|
|
json_append(body, std::move(crud));
|
|
json_set(result, "body", std::move(body));
|
|
return result;
|
|
}
|
|
}
|