@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
404 lines (403 loc) • 15.4 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const IField_1 = require("./IField");
const Log_1 = __importDefault(require("../core/Log"));
const Utilities_1 = __importDefault(require("../core/Utilities"));
const DataFormUtilities_1 = __importDefault(require("./DataFormUtilities"));
/**
* FormPropertyManager handles all property get/set operations for DataForm.
*
* It abstracts the complexity of:
* - Multiple backing stores (dataPropertyObject, getsetPropertyObject, directObject)
* - Scalar to object conversion (upscaling) and back (downscaling)
* - Type coercion from string inputs to typed values
* - Default value handling
*/
class FormPropertyManager {
_definition;
_dataPropertyObject;
_getsetPropertyObject;
constructor(definition, options = {}) {
this._definition = definition;
this._dataPropertyObject = options.dataPropertyObject;
this._getsetPropertyObject = options.getsetPropertyObject;
}
/**
* Gets the form definition.
*/
get definition() {
return this._definition;
}
/**
* Updates the form definition.
*/
set definition(value) {
this._definition = value;
}
/**
* Updates the backing store objects.
*/
updateBackingStores(options) {
this._dataPropertyObject = options.dataPropertyObject;
this._getsetPropertyObject = options.getsetPropertyObject;
}
/**
* Gets a field definition by its ID.
*
* @param id - The field ID to look up
* @returns The field definition, or undefined if not found
*/
getFieldById(id) {
const fields = this._definition.fields;
// Special case: __scalar represents the scalar field when no upgrade name is specified
if (id === "__scalar" && this._definition.scalarField && !this._definition.scalarFieldUpgradeName) {
return this._definition.scalarField;
}
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
if (field.id === id) {
return field;
}
}
return undefined;
}
/**
* Gets a property value from the backing stores.
*
* Checks all three backing stores in order:
* 1. dataPropertyObject
* 2. getsetPropertyObject
* 3. directObject
*
* @param name - The property name
* @param defaultValue - Default value if property not found
* @param directObject - The current direct object (may be updated state)
* @returns The property value, or defaultValue if not found
*/
getProperty(name, defaultValue, directObject) {
let value = undefined;
// Get default from field definition if not provided
if (defaultValue === undefined) {
const field = this.getFieldById(name);
if (field) {
defaultValue = field.defaultValue;
}
}
// Check dataPropertyObject
if (this._dataPropertyObject !== undefined) {
const prop = this._dataPropertyObject.getProperty(name);
if (prop !== undefined) {
value = prop.value;
}
}
// Check getsetPropertyObject
if (this._getsetPropertyObject !== undefined) {
value = this._getsetPropertyObject.getProperty(name);
}
// Check directObject (upscale first to handle scalar values)
if (directObject !== undefined) {
const upscaledObject = this.upscaleDirectObject(directObject);
value = upscaledObject[name];
}
// Return default if no value found
if (value === undefined) {
return defaultValue;
}
return value;
}
/**
* Gets a property value as an integer.
*
* @param name - The property name
* @param defaultValue - Default value if property not found or not convertible
* @param directObject - The current direct object
* @returns The property value as an integer
*/
getPropertyAsInt(name, defaultValue, directObject) {
const value = this.getProperty(name, defaultValue, directObject);
if (typeof value === "boolean") {
return value ? 1 : 0;
}
else if (typeof value === "number") {
return value;
}
else if (typeof value === "string") {
const parsed = parseInt(value, 10);
return isNaN(parsed) ? defaultValue : parsed;
}
return defaultValue;
}
/**
* Gets a property value as a boolean.
*
* @param name - The property name
* @param defaultValue - Default value if property not found
* @param directObject - The current direct object
* @returns The property value as a boolean
*/
getPropertyAsBoolean(name, defaultValue, directObject) {
const value = this.getProperty(name, defaultValue, directObject);
if (typeof value === "boolean") {
return value;
}
else if (typeof value === "number") {
return value !== 0;
}
else if (typeof value === "string") {
if (value !== undefined && value !== "" && value !== "false" && value !== "0") {
return true;
}
return false;
}
return defaultValue === true;
}
/**
* Converts a string value to the appropriate typed value based on field data type.
*
* @param field - The field definition
* @param value - The value to convert (typically a string from form input)
* @returns The typed value
*/
getTypedData(field, value) {
if (field.dataType === IField_1.FieldDataType.int ||
field.dataType === IField_1.FieldDataType.intBoolean ||
field.dataType === IField_1.FieldDataType.intEnum ||
field.dataType === IField_1.FieldDataType.intValueLookup) {
if (typeof value === "number") {
return value;
}
else if (typeof value === "string") {
if (value.length === 0) {
return undefined;
}
const parsed = parseInt(value, 10);
return isNaN(parsed) ? undefined : parsed;
}
else if (typeof value === "boolean") {
return value ? 1 : 0;
}
}
else if (field.dataType === IField_1.FieldDataType.number || field.dataType === IField_1.FieldDataType.float) {
if (typeof value === "number") {
return value;
}
else if (typeof value === "string") {
if (value.length === 0) {
return undefined;
}
const parsed = parseFloat(value);
return isNaN(parsed) ? undefined : parsed;
}
else if (typeof value === "boolean") {
return value ? 1 : 0;
}
}
else if (DataFormUtilities_1.default.isString(field.dataType)) {
if (typeof value === "string") {
return value.trim();
}
else {
return value.toString().trim();
}
}
return value;
}
/**
* Converts a scalar value to an object representation.
*
* This is needed when a field can be represented as either:
* - A scalar: 5
* - An object: { amount: 5, type: "fire" }
*
* @param directObject - The value to upscale (may be scalar or object)
* @returns An object representation
*/
upscaleDirectObject(directObject) {
if (typeof directObject === "string" || typeof directObject === "number" || typeof directObject === "boolean") {
// Use special __scalar key if no upgrade name specified
if (this._definition.scalarField && !this._definition.scalarFieldUpgradeName) {
return {
__scalar: directObject,
};
}
// Use the scalar upgrade field name if specified
if (this._definition.scalarFieldUpgradeName) {
const fi = DataFormUtilities_1.default.getFieldById(this._definition, this._definition.scalarFieldUpgradeName);
if (fi) {
const retObj = {};
if (Utilities_1.default.isUsableAsObjectKey(fi.id)) {
retObj[fi.id] = directObject;
}
else {
Log_1.default.unsupportedToken(fi.id);
}
return retObj;
}
}
return { value: directObject };
}
if (directObject === undefined) {
return {};
}
return directObject;
}
/**
* Checks if the object has any values besides scalar and default values.
*
* Used to determine if an object can be collapsed back to a scalar.
*
* @param directObject - The object to check
* @returns True if the object has unique values
*/
directObjectHasUniqueValuesBesidesScalar(directObject) {
const fields = this._definition.fields;
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
if (field.id !== "__scalar" &&
(this._definition.scalarFieldUpgradeName === undefined || field.id !== this._definition.scalarFieldUpgradeName)) {
if (directObject[field.id] !== undefined && directObject[field.id] !== field.defaultValue) {
return true;
}
}
}
return false;
}
/**
* Converts an object representation back to a scalar if possible.
*
* This collapses objects that only contain:
* - Empty values
* - Default values
* - The scalar field value
*
* @param directObject - The object to potentially downscale
* @returns Either the original object or a scalar value
*/
downscaleDirectObject(directObject) {
const fields = this._definition.fields;
// Collapse empty objects and default values
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
if (!field.retainIfEmptyOrDefault) {
let hasContent = false;
if (directObject[field.id] !== undefined) {
const fieldData = directObject[field.id];
if (typeof fieldData === "object") {
for (const propVal in directObject[field.id]) {
if (propVal.length !== undefined && propVal.length > 0) {
hasContent = true;
}
}
}
else {
if (field.defaultValue === undefined || fieldData !== field.defaultValue) {
hasContent = true;
}
}
}
if (!hasContent) {
if (Utilities_1.default.isUsableAsObjectKey(field.id)) {
directObject[field.id] = undefined;
}
}
}
}
const hasUniqueValuesBesidesScalar = this.directObjectHasUniqueValuesBesidesScalar(directObject);
if (!hasUniqueValuesBesidesScalar) {
// Convert down to a scalar value if possible
if (this._definition.scalarFieldUpgradeName) {
if (directObject[this._definition.scalarFieldUpgradeName] !== undefined) {
directObject = directObject[this._definition.scalarFieldUpgradeName];
}
}
else {
if (directObject["__scalar"] !== undefined) {
directObject = directObject["__scalar"];
}
}
}
return directObject;
}
/**
* Sets a property value across all applicable backing stores.
*
* @param id - The property ID to set
* @param val - The new value
* @param currentDirectObject - The current direct object state
* @returns The update result with the new direct object state
*/
setPropertyValue(id, val, currentDirectObject) {
const property = { id, value: val };
// Update dataPropertyObject if present
if (this._dataPropertyObject !== undefined) {
const prop = this._dataPropertyObject.ensureProperty(id);
prop.value = val;
}
// Update getsetPropertyObject if present
if (this._getsetPropertyObject !== undefined) {
if (id === "__scalar" && this._getsetPropertyObject.setBaseValue) {
this._getsetPropertyObject.setBaseValue(val);
}
else {
this._getsetPropertyObject.setProperty(id, val);
}
}
// Update directObject if present
let updatedDirectObject = currentDirectObject;
if (currentDirectObject !== undefined) {
updatedDirectObject = this.upscaleDirectObject(currentDirectObject);
updatedDirectObject[id] = val;
updatedDirectObject = this.downscaleDirectObject(updatedDirectObject);
}
return {
updatedDirectObject,
property,
newValue: val,
};
}
/**
* Processes an input update from a form control.
*
* This is the main entry point for handling user input. It:
* 1. Finds the field definition
* 2. Converts the string input to the appropriate type
* 3. Updates all backing stores
*
* @param id - The field ID being updated
* @param data - The string value from the form control
* @param currentDirectObject - The current direct object state
* @returns The update result, or undefined if the update failed
*/
processInputUpdate(id, data, currentDirectObject) {
if (!Utilities_1.default.isUsableAsObjectKey(id)) {
Log_1.default.unsupportedToken(id);
return undefined;
}
const field = this.getFieldById(id);
if (field === undefined) {
Log_1.default.fail("Could not find field " + id);
return undefined;
}
const val = this.getTypedData(field, data);
return this.setPropertyValue(id, val, currentDirectObject);
}
/**
* Toggles a boolean property value.
*
* @param id - The property ID to toggle
* @param defaultValue - The default value to use if property is not set
* @param currentDirectObject - The current direct object state
* @returns The update result
*/
toggleBooleanProperty(id, defaultValue, currentDirectObject) {
const currentVal = this.getPropertyAsBoolean(id, defaultValue, currentDirectObject);
const newVal = !currentVal;
return this.setPropertyValue(id, newVal, currentDirectObject);
}
}
exports.default = FormPropertyManager;