UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

404 lines (403 loc) 15.4 kB
"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;