@mendix/pluggable-widgets-tools
Version:
Mendix Pluggable Widgets Tools
289 lines (288 loc) • 13.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateClientTypes = generateClientTypes;
exports.hasOptionalDataSource = hasOptionalDataSource;
exports.toAttributeClientType = toAttributeClientType;
exports.toExpressionClientType = toExpressionClientType;
exports.toAssociationOutputType = toAssociationOutputType;
exports.toAttributeOutputType = toAttributeOutputType;
exports.toUniqueUnionType = toUniqueUnionType;
const helpers_1 = require("./helpers");
function generateClientTypes(widgetName, properties, systemProperties, isNative) {
function resolveProp(key) {
return properties.find(p => p.$.key === key);
}
const isLabeled = systemProperties.some(p => p.$.key === "Label");
const results = Array.of();
results.push(isNative
? `export interface ${widgetName}Props<Style> {
name: string;
style: Style[];
${generateClientTypeBody(properties, true, results, resolveProp)}
}`
: `export interface ${widgetName}ContainerProps {
name: string;${!isLabeled
? `
class: string;
style?: CSSProperties;`
: ""}
tabIndex?: number;${isLabeled
? `
id: string;`
: ""}
${generateClientTypeBody(properties, false, results, resolveProp)}
}`);
return results;
}
function isEmbeddedOnChangeAction(propertyPath, properties) {
return properties.some(prop => {
if (prop.$.onChange === propertyPath) {
return true;
}
if (prop.$.type === "object" && prop.properties && prop.properties.length > 0) {
return prop.properties.some(prop => isEmbeddedOnChangeAction(`../${propertyPath}`, (0, helpers_1.extractProperties)(prop)));
}
return false;
});
}
function generateClientTypeBody(properties, isNative, generatedTypes, resolveProp) {
return properties
.filter(prop => {
if (prop.$.type === "action" && isEmbeddedOnChangeAction(prop.$.key, properties)) {
return false;
}
if (prop.$.type === "datasource" && prop.$.isLinked) {
return false;
}
return true;
})
.map(prop => ` ${prop.$.key}${isOptionalProp(prop, resolveProp) ? "?" : ""}: ${toClientPropType(prop, isNative, generatedTypes, resolveProp)};`)
.join("\n");
}
function isOptionalProp(prop, resolveProp) {
var _a, _b;
switch (prop.$.type) {
case "string":
case "object":
return false;
case "action":
return true;
case "selection":
return (((_b = (_a = prop.selectionTypes) === null || _a === void 0 ? void 0 : _a.flatMap(t => t.selectionType.map(t => t.$.name))) !== null && _b !== void 0 ? _b : []).includes("None") ||
hasOptionalDataSource(prop, resolveProp));
default:
return prop.$.required === "false" || hasOptionalDataSource(prop, resolveProp);
}
}
function hasOptionalDataSource(prop, resolveProp) {
var _a;
return prop.$.dataSource && ((_a = resolveProp(prop.$.dataSource)) === null || _a === void 0 ? void 0 : _a.$.required) === "false";
}
function isLinkedToListDataSource(prop, resolveProp) {
var _a;
return prop.$.dataSource != null && ((_a = resolveProp(prop.$.dataSource)) === null || _a === void 0 ? void 0 : _a.$.isList) === "true";
}
function toActionVariablesOutputType(actionVariables) {
const types = actionVariables === null || actionVariables === void 0 ? void 0 : actionVariables.flatMap(av => av.actionVariable).map(avt => `${avt.$.key}: ${toOption(toAttributeClientType(avt.$.type))}`).join("; ");
return types ? `<{ ${types} }>` : "";
}
function toClientPropType(prop, isNative, generatedTypes, resolveProp) {
var _a, _b, _c, _d, _e;
switch (prop.$.type) {
case "boolean":
return "boolean";
case "string":
return "string";
case "action":
const variableTypes = toActionVariablesOutputType(prop.actionVariables);
return (isLinkedToListDataSource(prop, resolveProp) ? "ListActionValue" : "ActionValue") + variableTypes;
case "textTemplate":
return isLinkedToListDataSource(prop, resolveProp) ? "ListExpressionValue<string>" : "DynamicValue<string>";
case "integer":
return "number";
case "decimal":
return "Big";
case "icon":
return isNative ? "DynamicValue<NativeIcon>" : "DynamicValue<WebIcon>";
case "image":
const imageType = isNative ? "NativeImage" : "WebImage";
return `${prop.$.allowUpload === "true" ? "EditableImageValue" : "DynamicValue"}<${imageType}>`;
case "file":
return prop.$.allowUpload ? "EditableFileValue" : "DynamicValue<FileValue>";
case "datasource":
return prop.$.isList === "true" ? "ListValue" : "DynamicValue<ObjectItem>";
case "attribute": {
if (!((_a = prop.attributeTypes) === null || _a === void 0 ? void 0 : _a.length)) {
throw new Error("[XML] Attribute property requires attributeTypes element");
}
const types = prop.attributeTypes
.flatMap(ats => ats.attributeType)
.map(at => toAttributeClientType(at.$.name));
const unionType = toUniqueUnionType(types);
const linkedToListDS = isLinkedToListDataSource(prop, resolveProp);
if (prop.$.isMetaData === "true") {
if (!prop.$.dataSource) {
throw new Error(`[XML] Attribute property can only have isMetaData="true" when linked to a datasource`);
}
return `AttributeMetaData<${unionType}>`;
}
if (!((_b = prop.associationTypes) === null || _b === void 0 ? void 0 : _b.length)) {
return toAttributeOutputType("Reference", linkedToListDS, unionType);
}
else {
const reftypes = prop.associationTypes
.flatMap(ats => ats.associationType)
.map(at => toAttributeOutputType(at.$.name, linkedToListDS, unionType));
return toUniqueUnionType(reftypes);
}
}
case "association": {
if (!((_c = prop.associationTypes) === null || _c === void 0 ? void 0 : _c.length)) {
throw new Error("[XML] Association property requires associationTypes element");
}
const linkedToListDS = isLinkedToListDataSource(prop, resolveProp);
if (prop.$.isMetaData === "true") {
if (!prop.$.dataSource) {
throw new Error(`[XML] Association property can only have isMetaData="true" when linked to a datasource`);
}
return "AssociationMetaData";
}
const types = prop.associationTypes
.flatMap(ats => ats.associationType)
.map(at => toAssociationOutputType(at.$.name, linkedToListDS));
return toUniqueUnionType(types);
}
case "expression":
if (!prop.returnType || prop.returnType.length === 0) {
throw new Error("[XML] Expression property requires returnType element");
}
const type = toExpressionClientType(prop.returnType[0], resolveProp);
return isLinkedToListDataSource(prop, resolveProp)
? `ListExpressionValue<${type}>`
: `DynamicValue<${type}>`;
case "enumeration":
const typeName = (0, helpers_1.capitalizeFirstLetter)(prop.$.key) + "Enum";
generatedTypes.push(generateEnum(typeName, prop));
return typeName;
case "object":
if (!((_d = prop.properties) === null || _d === void 0 ? void 0 : _d.length)) {
throw new Error("[XML] Object property requires properties element");
}
const childType = (0, helpers_1.capitalizeFirstLetter)(prop.$.key) + "Type";
const childProperties = (0, helpers_1.extractProperties)(prop.properties[0]);
const resolveChildProp = (key) => key.startsWith("../") ? resolveProp(key.substring(3)) : childProperties.find(p => p.$.key === key);
generatedTypes.push(`export interface ${childType} {
${generateClientTypeBody(childProperties, isNative, generatedTypes, resolveChildProp)}
}`);
return prop.$.isList === "true" ? `${childType}[]` : childType;
case "widgets":
return isLinkedToListDataSource(prop, resolveProp) ? "ListWidgetValue" : "ReactNode";
case "selection":
if (!((_e = prop.selectionTypes) === null || _e === void 0 ? void 0 : _e.length)) {
throw new Error("[XML] Selection property requires selectionTypes element");
}
const selectionTypes = prop.selectionTypes.flatMap(s => s.selectionType).map(s => s.$.name);
const clientTypes = selectionTypes.filter(s => s !== "None").map(toSelectionClientType);
return toUniqueUnionType(clientTypes);
default:
return "any";
}
}
function generateEnum(typeName, prop) {
var _a, _b;
if (!((_a = prop.enumerationValues) === null || _a === void 0 ? void 0 : _a.length) || !((_b = prop.enumerationValues[0].enumerationValue) === null || _b === void 0 ? void 0 : _b.length)) {
throw new Error("[XML] Enumeration property requires enumerations element");
}
const members = prop.enumerationValues[0].enumerationValue.map(type => `"${type.$.key}"`);
return `export type ${typeName} = ${members.join(" | ")};`;
}
function toAttributeClientType(xmlType) {
switch (xmlType) {
case "Boolean":
return "boolean";
case "DateTime":
return "Date";
case "AutoNumber":
case "Decimal":
case "Integer":
case "Long":
return "Big";
case "HashString":
case "String":
case "Enum":
return "string";
default:
return "any";
}
}
function toExpressionClientType(returnTypeProp, resolveProp) {
var _a, _b;
const { type, assignableTo } = (_a = returnTypeProp.$) !== null && _a !== void 0 ? _a : {};
if ((type && assignableTo) || (!type && !assignableTo)) {
throw new Error("[XML] Invalid return type for expression property: either type or assignableTo must be specified.");
}
if (type) {
return toAttributeClientType(type);
}
const resolvedProperty = resolveProp(assignableTo);
if (!resolvedProperty) {
throw new Error(`[XML] Invalid return type for expression property: invalid property path '${assignableTo}' in assignableTo attribute.`);
}
if (resolvedProperty.$.type !== "attribute") {
throw new Error(`[XML] Invalid return type for expression property: assignableTo property '${assignableTo}' must be of type Attribute.`);
}
const { attributeTypes } = resolvedProperty;
if (!((_b = attributeTypes === null || attributeTypes === void 0 ? void 0 : attributeTypes[0]) === null || _b === void 0 ? void 0 : _b.attributeType)) {
throw new Error(`[XML] Invalid return type for expression property: assignableTo property '${assignableTo}' must have attribute types.`);
}
const allowedTypes = ["Boolean", "DateTime", "Enum", "Integer", "Long", "String", "Decimal"];
const types = attributeTypes
.map(ats => ats.attributeType)
.reduce((a, i) => a.concat(i), [])
.map(at => at.$.name);
const unsupportedTypes = types.filter(t => !allowedTypes.includes(t));
if (unsupportedTypes.length !== 0) {
throw new Error(`[XML] Invalid return type for expression property: assignableTo property '${assignableTo}' has unsupported attribute type ${(0, helpers_1.commasAnd)(unsupportedTypes)}.`);
}
return toUniqueUnionType(types.map(toAttributeClientType));
}
function toAssociationOutputType(xmlType, linkedToDataSource) {
switch (xmlType) {
case "Reference":
return linkedToDataSource ? "ListReferenceValue" : "ReferenceValue";
case "ReferenceSet":
return linkedToDataSource ? "ListReferenceSetValue" : "ReferenceSetValue";
default:
return "any";
}
}
function toAttributeOutputType(xmlType, linkedToDataSource, unionAttributeType) {
switch (xmlType) {
case "Reference":
return linkedToDataSource
? `ListAttributeValue<${unionAttributeType}>`
: `EditableValue<${unionAttributeType}>`;
case "ReferenceSet":
return linkedToDataSource
? `ListAttributeListValue<${unionAttributeType}>`
: `EditableListValue<${unionAttributeType}>`;
default:
return "any";
}
}
function toSelectionClientType(xmlType) {
switch (xmlType) {
case "Single":
return "SelectionSingleValue";
case "Multi":
return "SelectionMultiValue";
default:
return "any";
}
}
function toOption(type) {
return `Option<${type}>`;
}
function toUniqueUnionType(types) {
return types.length ? Array.from(new Set(types)).join(" | ") : "any";
}