UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

1,215 lines (1,214 loc) 55.9 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 IProjectItemData_1 = require("./IProjectItemData"); const Log_1 = __importDefault(require("./../core/Log")); const IProjectItemData_2 = require("./IProjectItemData"); const ste_events_1 = require("ste-events"); const IFile_1 = require("../storage/IFile"); const StorageUtilities_1 = __importDefault(require("../storage/StorageUtilities")); const EntityTypeDefinition_1 = __importDefault(require("../minecraft/EntityTypeDefinition")); const IProjectData_1 = require("./IProjectData"); const MCWorld_1 = __importDefault(require("../minecraft/MCWorld")); const ZipStorage_1 = __importDefault(require("../storage/ZipStorage")); const Utilities_1 = __importDefault(require("../core/Utilities")); const IStorage_1 = require("../storage/IStorage"); const ProjectItemUtilities_1 = __importDefault(require("./ProjectItemUtilities")); const ProjectAutogeneration_1 = __importDefault(require("./ProjectAutogeneration")); const ProjectItemRelations_1 = __importDefault(require("./ProjectItemRelations")); const ProjectItemVariant_1 = __importDefault(require("./ProjectItemVariant")); const IProjectItemVariant_1 = require("./IProjectItemVariant"); const Database_1 = __importDefault(require("../minecraft/Database")); const ProjectVariant_1 = __importDefault(require("./ProjectVariant")); const MinecraftUtilities_1 = __importDefault(require("../minecraft/MinecraftUtilities")); class ProjectItem { _data; _project; _onPropertyChanged = new ste_events_1.EventDispatcher(); _onFileRetrieved = new ste_events_1.EventDispatcher(); _onFolderRetrieved = new ste_events_1.EventDispatcher(); _onLoaded = new ste_events_1.EventDispatcher(); _defaultFile; _defaultFolder; _pendingLoadRequests = []; _isLoading = false; _isFileContentProcessed = false; _imageUrlBase64Cache; _pack; _primaryFile; _accessoryFolder; parentItems; childItems; unfulfilledRelationships; _variants; constructor(parent, incomingData) { this._project = parent; this._defaultFile = null; this._defaultFolder = null; this._accessoryFolder = null; this._isFileContentProcessed = false; this._variants = {}; this._handleMCWorldLoaded = this._handleMCWorldLoaded.bind(this); this.sortVariantsMostImportantFirst = this.sortVariantsMostImportantFirst.bind(this); this.sortVariantsMostImportantLast = this.sortVariantsMostImportantLast.bind(this); if (incomingData) { this._data = incomingData; // Migration: older projects may have persisted an empty-label default variant // (label: "", variantType: general). The empty-label variant is purely a runtime // placeholder for the item's default file and should not be persisted; leaving // it in #data.variants makes _getVariantList() report a phantom entry that other // code paths (e.g. variant.ensureFileStorage subpack lookup) can mishandle. if (this._data.variants && this._data.variants[""] !== undefined) { delete this._data.variants[""]; } } else { this._data = { variants: {}, itemType: IProjectItemData_2.ProjectItemType.unknown, projectPath: null, storageType: IProjectItemData_1.ProjectItemStorageType.singleFile, tags: [], name: "", }; } } get parentItemCount() { if (this.parentItems === undefined) { return 0; } return this.parentItems.length; } get childItemCount() { if (this.childItems === undefined) { return 0; } return this.childItems.length; } get unfulfilledRelationshipsCount() { if (this.unfulfilledRelationships === undefined) { return 0; } return this.unfulfilledRelationships.length; } get isInWorld() { return this._data.isInWorld; } set isInWorld(isInWorld) { this._data.isInWorld = isInWorld; } get project() { return this._project; } get onPropertyChanged() { return this._onPropertyChanged.asEvent(); } get onLoaded() { return this._onLoaded.asEvent(); } get onFileRetrieved() { return this._onFileRetrieved.asEvent(); } get onFolderRetrieved() { return this._onFolderRetrieved.asEvent(); } get gitHubReference() { return this._data.gitHubReference; } get isInFileContainer() { if (!this.projectPath) { return false; } return this.projectPath.indexOf("#") >= 0; } get isFileContainerStorageItem() { return (this.itemType === IProjectItemData_2.ProjectItemType.zip || this.itemType === IProjectItemData_2.ProjectItemType.MCWorld || this.itemType === IProjectItemData_2.ProjectItemType.MCProject || this.itemType === IProjectItemData_2.ProjectItemType.MCAddon || this.itemType === IProjectItemData_2.ProjectItemType.MCPack || this.itemType === IProjectItemData_2.ProjectItemType.MCTemplate); } hasCustomVariants() { for (const key in this._data.variants) { if (key !== "") { return true; } } return false; } hasVersionSliceCustomVariants() { for (const key in this._data.variants) { const varType = this._data.variants[key].variantType; if (key !== "" && (varType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAltPacks || varType === IProjectItemVariant_1.ProjectItemVariantType.versionSlice || varType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAlt)) { return true; } } return false; } _shouldEnsureDefaultVariant() { return (this.defaultFile && (this.defaultFile.content || !this.hasCustomVariants())) || this.defaultFolder; } getVariant(variantName) { if (variantName === "" && this._shouldEnsureDefaultVariant()) { return this.ensureDefaultVariant(); } return this._variants[variantName]; } get isWorld() { return (this.itemType === IProjectItemData_2.ProjectItemType.MCProject || this.itemType === IProjectItemData_2.ProjectItemType.MCWorld || this.itemType === IProjectItemData_2.ProjectItemType.MCTemplate || this.itemType === IProjectItemData_2.ProjectItemType.worldFolder); } static getGitHubSignature(info) { let sig = info.owner + "|" + info.repoName + "|"; if (info.branch !== undefined) { sig += info.branch; } if (info.folder !== undefined) { sig += "|" + info.folder; } return sig; } get defaultVariant() { if (this._shouldEnsureDefaultVariant()) { return this.ensureDefaultVariant(); } return this._variants[""]; } async ensureAccessoryFolder() { if (this._accessoryFolder) { return this._accessoryFolder; } const rootAccessoryFolder = await this.project.ensureProjectItemAccessoryFolder(); const folderPath = ProjectItemUtilities_1.default.getAccessoryFolderPathFromFilePath(this); this._accessoryFolder = await rootAccessoryFolder.ensureFolderFromRelativePath(folderPath); return this._accessoryFolder; } ensureDefaultVariant() { return this.ensureVariant(""); } getVariantList() { if (this.defaultFile || this.defaultFolder) { this.ensureDefaultVariant(); } return this._getVariantList(); } _getVariantList() { const vararr = []; // The empty-label default variant is intentionally NOT persisted in // _data.variants (see ensureVariant). If it has been instantiated as a // runtime placeholder, include it first so callers iterating the variant // list still see the item's default file/content. if (this._variants[""] !== undefined) { vararr.push(this._variants[""]); } for (const key in this._data.variants) { if (key === "") { continue; } const variant = this.ensureVariant(key); vararr.push(variant); } return vararr; } getVariantListMostImportantLast() { const vararr = []; const varKeys = Object.keys(this._data.variants); varKeys.sort(this.sortVariantsMostImportantLast); for (const key of varKeys) { const variant = this.ensureVariant(key); vararr.push(variant); } return vararr; } getVariantListMostImportantFirst() { const vararr = []; const varKeys = Object.keys(this._data.variants); varKeys.sort(this.sortVariantsMostImportantFirst); for (const key of varKeys) { const variant = this.ensureVariant(key); vararr.push(variant); } return vararr; } hasNonDefaultVariant() { const vararr = this._getVariantList(); if (vararr.length === 0) { return false; } if (vararr.length > 1) { return true; } if (vararr[0].label !== "") { return true; } return false; } ensureVariant(label) { label = ProjectVariant_1.default.canonicalizeVariantLabel(label); if (!Utilities_1.default.isUsableAsObjectKey(label)) { Log_1.default.unsupportedToken(label); throw new Error(); } if (!this._variants[label]) { const pv = this.project.ensureVariant(label); if (!this._data.variants) { this._data.variants = {}; } // The empty-label default variant is a runtime placeholder for the item's // default file and is not persisted in #data.variants. Use a transient data // object so the runtime ProjectItemVariant still works without leaking a // phantom entry into the saved project (which historically caused // _getVariantList() to report count=1 on items that have no real variants). let variantData = this._data.variants[label]; if (variantData === undefined) { variantData = { label: label, variantType: IProjectItemVariant_1.ProjectItemVariantType.general }; if (label !== "") { this._data.variants[label] = variantData; } } this._primaryFile = undefined; this._variants[label] = new ProjectItemVariant_1.default(this, variantData, pv); } return this._variants[label]; } /// NOTE: ProjectItem.ensureFileStorage or ensureStorage should be called before this method. getFile(variantName) { if (variantName === undefined || variantName === "") { return this._defaultFile; } const variant = this.ensureVariant(variantName); return variant.file; } /// NOTE: ProjectItem.ensureFolderStorage or ensureStorage should be called before this method. getFolder(variantName) { if (variantName === undefined || variantName === "") { return this._defaultFolder; } const variant = this.ensureVariant(variantName); return variant.folder; } /// NOTE: ProjectItem.ensure*Storage should be called before this method. async getPack() { if (this._pack) { return this._pack; } let thisPath = undefined; if (this._defaultFile) { thisPath = this._defaultFile.storageRelativePath; } else if (this._defaultFolder) { thisPath = this._defaultFolder.storageRelativePath; } if (thisPath === undefined) { return undefined; } thisPath = StorageUtilities_1.default.canonicalizePath(thisPath); await this.project.ensurePacks(); for (const pack of this.project.packs) { if (thisPath.startsWith(StorageUtilities_1.default.canonicalizePath(pack.folder.storageRelativePath))) { this._pack = pack; return this._pack; } } return undefined; } addUnfulfilledRelationship(path, itemType, isVanillaDependent) { let pir = { parentItem: this, path: path, itemType: itemType, isVanillaDependent: isVanillaDependent === true, }; if (this.unfulfilledRelationships === undefined) { this.unfulfilledRelationships = []; } this.unfulfilledRelationships.push(pir); } async ensureDependencies() { await ProjectItemRelations_1.default.calculateForItem(this); } addChildItem(childItem) { if (this.childItems) { for (const rel of this.childItems) { if (rel.childItem === childItem && rel.parentItem === this) { return; } } } if (childItem === this) { return; } if (ProjectItemUtilities_1.default.wouldBeCircular(childItem)) { return; } let hasChild = false; let childHasParent = false; if (this.childItems === undefined) { this.childItems = []; } if (childItem.parentItems === undefined) { childItem.parentItems = []; } for (const existingRelation of this.childItems) { if (existingRelation.childItem === childItem && existingRelation.parentItem === this) { hasChild = true; } } for (const existingRelation of childItem.parentItems) { if (existingRelation.childItem === childItem && existingRelation.parentItem === this) { childHasParent = true; } } const pir = { parentItem: this, childItem: childItem, }; if (!hasChild) { this.childItems.push(pir); } if (!childHasParent) { childItem.parentItems.push(pir); } } addParentItem(parentItem) { if (ProjectItemUtilities_1.default.wouldBeCircular(parentItem)) { return; } if (parentItem === this) { return; } if (this.parentItems === undefined) { this.parentItems = []; } if (parentItem.childItems === undefined) { parentItem.childItems = []; } let hasParent = false; let parentHasChild = false; for (const existingRelation of this.parentItems) { if (existingRelation.parentItem === parentItem && existingRelation.childItem === this) { hasParent = true; } } for (const existingRelation of parentItem.childItems) { if (existingRelation.parentItem === parentItem && existingRelation.childItem === this) { parentHasChild = true; } } const pir = { parentItem: parentItem, childItem: this, }; if (!hasParent) { this.parentItems.push(pir); } if (!parentHasChild) { parentItem.childItems.push(pir); } } toString() { return this.itemType + ": " + this.projectPath; } /// NOTE: ProjectItem.ensure*Storage should be called before this method. async getPackRelativePath() { const pack = await this.getPack(); if (!pack) { return undefined; } if (this._defaultFile) { return this._defaultFile.getFolderRelativePath(pack.folder); } else if (this._defaultFolder) { return this._defaultFolder.getFolderRelativePath(pack.folder); } return undefined; } static gitHubReferencesEqual(refA, refB) { if (refA === refB) { return true; } if (refA === undefined && refB === undefined) { return true; } if (refA !== undefined && refB !== undefined && refA.owner === refB.owner && refA.repoName === refB.repoName && refA.folder === refB.folder && refA.branch === refB.branch) { return true; } return false; } set gitHubReference(value) { if (ProjectItem.gitHubReferencesEqual(this._data.gitHubReference, value)) { return; } this._data.gitHubReference = value; this._project.notifyProjectItemChanged(this); } get title() { return (StorageUtilities_1.default.getContaineredFileLeafPath(this.projectPath) + " (" + ProjectItemUtilities_1.default.getDescriptionForType(this._data.itemType).toLowerCase() + ")"); } get typeTitle() { return ProjectItemUtilities_1.default.getDescriptionForType(this._data.itemType); } getPackFolderName() { if (this.projectPath === undefined || this.projectPath === null) { return undefined; } let folderStoragePath = StorageUtilities_1.default.getFolderPath(this.projectPath); if (folderStoragePath === undefined) { return undefined; } let folderStoragePathLower = folderStoragePath.toLowerCase(); if (this.project.packs.length > 2) { const endOfPacks = folderStoragePathLower.indexOf("_packs/"); if (endOfPacks > 0) { const endOfPacksSegment = folderStoragePathLower.indexOf("/", endOfPacks + 7); if (endOfPacksSegment > 0) { return folderStoragePath.substring(endOfPacks + 7, endOfPacksSegment); } } } return undefined; } get folderPath() { if (this.projectPath === undefined || this.projectPath === null) { return undefined; } return StorageUtilities_1.default.getFolderPath(this.projectPath); } getFolderGroupingPath() { if (this.projectPath === undefined || this.projectPath === null) { return undefined; } let folderStoragePath = StorageUtilities_1.default.getFolderPath(this.projectPath); if (folderStoragePath === undefined) { return undefined; } folderStoragePath = MinecraftUtilities_1.default.clearCommonTerms(folderStoragePath); let folderStoragePathLower = folderStoragePath.toLowerCase(); const folderTypeRoots = [...ProjectItemUtilities_1.default.getFolderRootsForType(this.itemType)]; folderTypeRoots.push("zip"); for (const folderTypeRoot of folderTypeRoots) { if (this.project.hasMultiplePacksOfSameType) { folderStoragePath = Utilities_1.default.replaceAllCaseInsensitive(folderStoragePath, "/" + folderTypeRoot + "/", "/"); } else { const start = folderStoragePathLower.indexOf("/" + folderTypeRoot + "/"); if (start >= 0) { let packPrefix = this.getPackFolderName(); if (packPrefix === undefined) { packPrefix = ""; } else { packPrefix += " "; } folderStoragePath = packPrefix + folderStoragePath.substring(start + 2 + folderTypeRoot.length); folderStoragePathLower = folderStoragePath.toLowerCase(); } } } folderStoragePath = folderStoragePath.replace(/#/gi, " ").trim(); return folderStoragePath; } getCommunitySchemaPath() { return ProjectItemUtilities_1.default.getCommunitySchemaPathForType(this.itemType); } getOfficialSchemaPath() { return ProjectItemUtilities_1.default.getOfficialSchemaPathForType(this.itemType); } getFormPath() { // For geometry files, check if this is a legacy format (format_version 1.8.0 with "geometry.<name>" keys) // vs modern format (format_version 1.21.0 with "minecraft:geometry") if (this.itemType === IProjectItemData_2.ProjectItemType.modelGeometryJson && this.primaryFile) { const content = StorageUtilities_1.default.getJsonObject(this.primaryFile); if (content) { // Check if any root key starts with "geometry." (legacy format) const rootKeys = Object.keys(content); const hasLegacyGeometry = rootKeys.some((key) => key.toLowerCase().startsWith("geometry.")); if (hasLegacyGeometry) { return "visual/geometry.v1.8.0"; } } } return ProjectItemUtilities_1.default.getFormPathForType(this.itemType); } get errorStatus() { return this._data.errorStatus; } set errorStatus(newErrorStatus) { this._data.errorStatus = newErrorStatus; } get source() { return this._data.source; } set source(newSource) { this._data.source = newSource; } get errorMessage() { return this._data.errorMessage; } set errorMessage(newErrorMessage) { this._data.errorMessage = newErrorMessage; } get projectPath() { if (this._data.projectPath === undefined && this._data.storagePath) { this._data.projectPath = this._data.storagePath; return this._data.storagePath; } return this._data.projectPath; } set projectPath(newBasePath) { this._data.projectPath = newBasePath; } get effectiveEditPreference() { const ep = this.editPreference; if (ep === IProjectItemData_1.ProjectItemEditPreference.projectDefault) { return this._project.effectiveEditPreference; } else if (ep === IProjectItemData_1.ProjectItemEditPreference.forceRaw) { return IProjectData_1.ProjectEditPreference.raw; } else { return IProjectData_1.ProjectEditPreference.summarized; } } get editPreference() { if (this._data.editPreference === undefined) { return IProjectItemData_1.ProjectItemEditPreference.projectDefault; } return this._data.editPreference; } set editPreference(newEditPreference) { this._data.editPreference = newEditPreference; } get storageType() { if (this._data.storageType === undefined) { return IProjectItemData_1.ProjectItemStorageType.singleFile; } return this._data.storageType; } set storageType(newStorageType) { this._data.storageType = newStorageType; } get creationType() { return this._data.creationType; } set creationType(newCreationType) { this._data.creationType = newCreationType; } get itemType() { return this._data.itemType; } get defaultFile() { return this._defaultFile; } /** * Get the cached thumbnail data URL for this item. * Returns undefined if no thumbnail has been generated. */ get cachedThumbnail() { return this._data.cachedThumbnail; } /** * Set the cached thumbnail for this item. * Also updates the thumbnailGeneratedAt timestamp and clears the imageUrl cache. */ set cachedThumbnail(value) { this._data.cachedThumbnail = value; this._data.thumbnailGeneratedAt = value ? Date.now() : undefined; // Clear the cached imageUrl so it picks up the new thumbnail this._imageUrlBase64Cache = undefined; } /** * Get the timestamp when the thumbnail was last generated. */ get thumbnailGeneratedAt() { return this._data.thumbnailGeneratedAt; } /** * Get the thumbnail link - path to another item whose thumbnail should be used. */ get thumbnailLink() { return this._data.thumbnailLink; } /** * Set the thumbnail link - path to another item whose thumbnail should be used. */ set thumbnailLink(value) { this._data.thumbnailLink = value; } /** * Check if this item type supports thumbnail generation. */ get supportsThumbnail() { return this.itemType === IProjectItemData_2.ProjectItemType.modelGeometryJson; } /** * Clear the cached thumbnail and imageUrl cache. * Call this when the file content changes. */ clearThumbnailCache() { this._data.cachedThumbnail = undefined; this._data.thumbnailGeneratedAt = undefined; this._imageUrlBase64Cache = undefined; } sortVariantsMostImportantLast(a, b) { if (!this._variants) { this._getVariantList(); } if (!this._variants) { return Utilities_1.default.staticCompare(a, b); } const va = this._variants[a]; const vb = this._variants[b]; if (!va || !vb || !va.label || !vb.label) { return 0; } if ((va.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSlice && vb.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSlice) || (va.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAlt && vb.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAlt) || (va.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAltPacks && vb.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAltPacks)) { const versionIndexA = Database_1.default.getVersionIndexFromVersionStr(va.label); const versionIndexB = Database_1.default.getVersionIndexFromVersionStr(vb.label); return versionIndexA - versionIndexB; } return Utilities_1.default.staticCompare(va.label.toString(), vb.label.toString()); } sortVariantsMostImportantFirst(a, b) { if (!this._variants) { this._getVariantList(); } if (!this._variants) { return Utilities_1.default.staticCompare(a, b); } const va = this._variants[a]; const vb = this._variants[b]; if (!va || !vb || !va.label || !vb.label) { return 0; } if ((va.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSlice && vb.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSlice) || (va.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAlt && vb.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAlt) || (va.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAltPacks && vb.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAltPacks)) { const versionIndexA = Database_1.default.getVersionIndexFromVersionStr(va.label); const versionIndexB = Database_1.default.getVersionIndexFromVersionStr(vb.label); return versionIndexB - versionIndexA; } return vb.label.localeCompare(va.label); } get primaryVariantLabel() { const variantKeys = Object.keys(this._variants); // if we have version slices, return the latest one that has a file if (this.hasVersionSliceCustomVariants()) { variantKeys.sort(this.sortVariantsMostImportantFirst); for (const variantName of variantKeys) { const variant = this._variants[variantName]; if (variant.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSlice || variant.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAlt || variant.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAltPacks) { if (variant.file) { return variant.label; } } } } if (this._defaultFile) { return undefined; } for (const variantName of variantKeys) { if (variantName) { const variant = this._variants[variantName]; if (variant.file) { return variant.label; } } } return undefined; } get primaryFile() { if (this._primaryFile) { return this._primaryFile; } // if we have version slices, return the latest one that has a file if (this.hasVersionSliceCustomVariants()) { const variantKeys = Object.keys(this._variants); variantKeys.sort(this.sortVariantsMostImportantFirst); for (const variantName of variantKeys) { const variant = this._variants[variantName]; if (variant.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSlice || variant.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAlt || variant.variantType === IProjectItemVariant_1.ProjectItemVariantType.versionSliceAltPacks) { if (variant.file) { this._primaryFile = variant.file; return this._primaryFile; } } } } if (this._defaultFile && (!this._defaultFile.isContentLoaded || this._defaultFile.content)) { return this._defaultFile; } const variantKeys = Object.keys(this._variants); variantKeys.sort(this.sortVariantsMostImportantFirst); for (const variantName of variantKeys) { if (variantName) { const variant = this._variants[variantName]; if (variant.file) { this._primaryFile = variant.file; return this._primaryFile; } } } // as a last fallback, return an assumedly content-less default file if (this._defaultFile) { this._primaryFile = this._defaultFile; return this._primaryFile; } return null; } get defaultFolder() { return this._defaultFolder; } set itemType(newItemType) { this._data.itemType = newItemType; this._onPropertyChanged.dispatch(this, "itemType"); this._project.notifyProjectItemChanged(this); } get isContentLoaded() { if (this.storageType === IProjectItemData_1.ProjectItemStorageType.folder) { if (!this.defaultFolder) { return false; } if (!this.defaultFolder.isLoaded) { return false; } if (this.itemType === IProjectItemData_2.ProjectItemType.worldFolder) { if (!this.defaultFolder.manager || !(this.defaultFolder.manager instanceof MCWorld_1.default)) { return false; } const world = this.defaultFolder.manager; return world.isLoaded; } return true; } else if (this.storageType === IProjectItemData_1.ProjectItemStorageType.singleFile) { const itemFile = this.primaryFile; if (!itemFile) { return false; } if (!itemFile.isContentLoaded) { return false; } if (this.isWorld) { if (!itemFile.manager || !(itemFile.manager instanceof MCWorld_1.default)) { return false; } const world = itemFile.manager; return world.isLoaded; } return this._isFileContentProcessed; } Log_1.default.unexpectedContentState(); return false; } get tags() { return this._data.tags; } async setFile(file) { if (file !== this._defaultFile) { this._defaultFile = file; this._isFileContentProcessed = false; } } get needsSave() { if (this._defaultFile === null && this.creationType === IProjectItemData_1.ProjectItemCreationType.generated && this.storageType === IProjectItemData_1.ProjectItemStorageType.singleFile) { return true; } if (this._defaultFile === null) { return false; } let val = this._defaultFile.needsSave; if (!val) { if (this.isFileContainerStorageItem && this._defaultFile.fileContainerStorage && this._defaultFile.fileContainerStorage instanceof ZipStorage_1.default) { val = this._defaultFile.fileContainerStorage.isContentUpdated; } if (!val) { const jsFile = this.getJavaScriptLibTwin(); if (jsFile !== undefined) { val = jsFile.needsSave; } } } return val; } updateProjectPath() { if (this._project && this._project.projectFolder) { if (this._defaultFile) { this.projectPath = this._defaultFile.getFolderRelativePath(this._project.projectFolder); } else if (this._defaultFolder) { this.projectPath = this._defaultFolder.getFolderRelativePath(this._project.projectFolder); } } } async rename(newFileBaseName) { if (!this.isContentLoaded) { await this.loadContent(); } await this._project.ensureProjectFolder(); if (this._project.projectFolder === undefined || this._project.projectFolder === null) { return; } if (this._defaultFile !== null) { await this._defaultFile.moveTo(this._defaultFile.parentFolder.storageRelativePath + newFileBaseName + "." + this._defaultFile.type); this._data.name = newFileBaseName + "." + this._defaultFile.type; this.projectPath = this._defaultFile.getFolderRelativePath(this._project.projectFolder); this.storageType = IProjectItemData_1.ProjectItemStorageType.singleFile; } else { this._data.name = newFileBaseName; } this._onPropertyChanged.dispatch(this, "name"); this._project.notifyProjectItemChanged(this); } async deleteItem() { if (!this.isContentLoaded) { await this.loadContent(); } await ProjectItemRelations_1.default.deleteLinksFromParents(this); if (this._defaultFile !== null) { await this._defaultFile.deleteThisFile(); } this._project.removeItem(this); } get imageUrl() { if (this._imageUrlBase64Cache === null) { return undefined; } if (this._imageUrlBase64Cache) { return this._imageUrlBase64Cache; } // Check for cached thumbnail (generated by worker for geometry models, etc.) if (this._data.cachedThumbnail) { this._imageUrlBase64Cache = this._data.cachedThumbnail; return this._imageUrlBase64Cache; } if (this.itemType === IProjectItemData_2.ProjectItemType.worldFolder) { if (this.defaultFolder) { if (this.defaultFolder.manager instanceof MCWorld_1.default) { const world = this.defaultFolder.manager; if (world.isLoaded) { this._imageUrlBase64Cache = "data:image/jpg;base64, " + world.imageBase64; return this._imageUrlBase64Cache; } } } } else if (this.isWorld) { const itemFile = this.primaryFile; if (itemFile) { if (itemFile.manager instanceof MCWorld_1.default) { const world = itemFile.manager; if (world.isLoaded) { return "data:image/jpg;base64, " + world.imageBase64; } } } } else if (this.projectPath && ProjectItemUtilities_1.default.isImageType(this.itemType)) { if (this.projectPath && this.projectPath.endsWith(".tga")) { // TGA files need async conversion, handled elsewhere this._imageUrlBase64Cache = null; } else if (this._defaultFile && this._defaultFile.content && this._defaultFile.content instanceof Uint8Array) { if (this._defaultFile.content.length > 10000) { this._imageUrlBase64Cache = null; return undefined; } // Determine the correct MIME type based on file extension const ext = StorageUtilities_1.default.getTypeFromName(this.projectPath || ""); let mimeType = "image/png"; // Default to PNG if (ext === "jpg" || ext === "jpeg") { mimeType = "image/jpeg"; } else if (ext === "gif") { mimeType = "image/gif"; } else if (ext === "webp") { mimeType = "image/webp"; } this._imageUrlBase64Cache = "data:" + mimeType + ";base64," + Utilities_1.default.uint8ArrayToBase64(this._defaultFile.content); return this._imageUrlBase64Cache; } } return undefined; } get name() { if (this.itemType === IProjectItemData_2.ProjectItemType.worldFolder) { if (this.defaultFolder) { if (this.defaultFolder.manager instanceof MCWorld_1.default) { const world = this.defaultFolder.manager; if (world.isLoaded) { return world.name; } } } } else if (this.itemType === IProjectItemData_2.ProjectItemType.MCWorld || this.itemType === IProjectItemData_2.ProjectItemType.MCTemplate) { const itemFile = this.primaryFile; if (itemFile) { if (itemFile.manager instanceof MCWorld_1.default) { const world = itemFile.manager; if (world.isLoaded) { return world.name; } } } } // for certain types of project items, the name of the file is critical if (this.projectPath && (this.itemType === IProjectItemData_2.ProjectItemType.js || this.itemType === IProjectItemData_2.ProjectItemType.ts || this.itemType === IProjectItemData_2.ProjectItemType.testJs || this.itemType === IProjectItemData_2.ProjectItemType.structure)) { return StorageUtilities_1.default.getLeafName(this.projectPath); } if (this._data.name !== undefined) { return this._data.name; } if (this.projectPath) { return StorageUtilities_1.default.getLeafName(this.projectPath); } return "untitled"; } async loadFolder() { if (this.storageType === IProjectItemData_1.ProjectItemStorageType.folder) { if (this._defaultFolder === null && this.projectPath !== null && this.projectPath !== undefined && this.projectPath.startsWith("/") && this._project.projectFolder !== null && this._project.projectFolder !== undefined) { const prefixPaths = this.projectPath.split("#"); if (prefixPaths.length > 1) { let folderToLoadFrom = this._project.projectFolder; for (let i = 0; i < prefixPaths.length - 1; i++) { if (folderToLoadFrom) { const zipFile = await folderToLoadFrom.ensureFileFromRelativePath(prefixPaths[i]); if (!zipFile.isContentLoaded) { await zipFile.loadContent(); } if (zipFile.content && zipFile.content instanceof Uint8Array) { if (!zipFile.fileContainerStorage) { const zipStorage = new ZipStorage_1.default(); zipStorage.storagePath = zipFile.storageRelativePath + "#"; await zipStorage.loadFromUint8Array(zipFile.content, zipFile.name); zipStorage.containerFile = zipFile; zipFile.fileContainerStorage = zipStorage; } folderToLoadFrom = zipFile.fileContainerStorage.rootFolder; } else { folderToLoadFrom = undefined; } } } if (folderToLoadFrom) { this._defaultFolder = await folderToLoadFrom.ensureFolderFromRelativePath(prefixPaths[prefixPaths.length - 1]); } else { // Log.debugAlert("Unable to parse a containerized file path of '" + this.storagePath + "'"); return null; } } else { this._defaultFolder = await this._project.projectFolder.ensureFolderFromRelativePath(this.projectPath); } await this._defaultFolder.load(); this._onFolderRetrieved.dispatch(this, this._defaultFolder); if (this.itemType === IProjectItemData_2.ProjectItemType.worldFolder) { const mcworld = await MCWorld_1.default.ensureMCWorldOnFolder(this._defaultFolder, this._project, this._handleMCWorldLoaded); if (mcworld) { this.errorMessage = mcworld.storageErrorMessage; if (mcworld.storageErrorStatus === IStorage_1.StorageErrorStatus.unprocessable) { this.errorStatus = IProjectItemData_1.ProjectItemErrorStatus.unprocessable; } else { this.errorStatus = IProjectItemData_1.ProjectItemErrorStatus.none; } } } else { this._fireLoadedEvent(); } } return this._defaultFolder; } return undefined; } _handleMCWorldLoaded(world, worldA) { this._fireLoadedEvent(); } _fireLoadedEvent() { if (this._onLoaded && this.isContentLoaded) { this._onLoaded.dispatch(this, this); } } invalidateContentProcessedState() { if (this._defaultFile) { this._defaultFile.manager = undefined; } for (const varLabel in this._variants) { const variant = this._variants[varLabel]; if (variant.file) { variant.file.manager = undefined; } } this._isFileContentProcessed = false; } async getManager() { if (!this.isContentLoaded) { await this.loadContent(); } if (this.storageType === IProjectItemData_1.ProjectItemStorageType.singleFile && this._defaultFile) { return this._defaultFile.manager; } if (this.storageType === IProjectItemData_1.ProjectItemStorageType.folder && this._defaultFolder) { return this._defaultFolder.manager; } return undefined; } async ensureStorage() { if (this.storageType === IProjectItemData_1.ProjectItemStorageType.folder) { await this.loadFolder(); } else if (this.storageType === IProjectItemData_1.ProjectItemStorageType.singleFile) { await this.ensureFileStorage(); } } get isStorageEnsured() { if (this.storageType === IProjectItemData_1.ProjectItemStorageType.folder) { return !!this._defaultFolder; } return !!this.primaryFile; } async loadContentDirect() { if (this.storageType === IProjectItemData_1.ProjectItemStorageType.folder) { await this.loadFolder(); } else if (this.storageType === IProjectItemData_1.ProjectItemStorageType.singleFile) { await this.loadFileContent(); } } async ensureFileStorage() { if (this.storageType === IProjectItemData_1.ProjectItemStorageType.singleFile && this._defaultFile === null && this.projectPath !== null && this.projectPath !== undefined && this.projectPath.startsWith("/") && this._project.projectFolder !== null && this._project.projectFolder !== undefined) { const prefixPaths = this.projectPath.split("#"); if (prefixPaths.length > 1) { let folderToLoadFrom = this._project.projectFolder; for (let i = 0; i < prefixPaths.length - 1; i++) { if (folderToLoadFrom) { const zipFile = await folderToLoadFrom.ensureFileFromRelativePath(prefixPaths[i]); if (!zipFile.isContentLoaded) { await zipFile.loadContent(); } if (zipFile.content && zipFile.content instanceof Uint8Array) { if (!zipFile.fileContainerStorage) { const zipStorage = new ZipStorage_1.default(); zipStorage.storagePath = zipFile.storageRelativePath + "#"; await zipStorage.loadFromUint8Array(zipFile.content, zipFile.name); zipStorage.containerFile = zipFile; zipFile.fileContainerStorage = zipStorage; } folderToLoadFrom = zipFile.fileContainerStorage.rootFolder; } else { folderToLoadFrom = undefined; } } } if (folderToLoadFrom) { this._defaultFile = await folderToLoadFrom.ensureFileFromRelativePath(prefixPaths[prefixPaths.length - 1]); this._isFileContentProcessed = false; } else { Log_1.default.debugAlert("Unable to parse a containerized file path of '" + this.projectPath + "'"); return null; } } else { this._defaultFile = await this._project.projectFolder.ensureFileFromRelativePath(this.projectPath); this._isFileContentProcessed = false; } } return this._defaultFile; } async loadFileContent() { if (this._isFileContentProcessed) { return this._defaultFile; } if (!this._defaultFile) { await this.ensureFileStorage(); } const variants = this._getVariantList(); for (const variant of variants) { await variant.ensureAndLoadFileStorage(); } if (this._defaultFile) { await this.loadFileStorage(); } else if (variants.length > 0 && !this._isFileContentProcessed) { // For variant-only items (no default file), mark as processed after // all variant files have been loaded. Without this, isContentLoaded // stays false forever because loadFileStorage() requires _defaultFile. this._isFileContentProcessed = true; this._fireLoadedEvent(); } return this._defaultFile; } async loadFileStorage() { if (!this._isFileContentProcessed && this._defaultFile) { if (this._data.creationType === IProjectItemData_1.ProjectItemCreationType.generated) { await ProjectAutogeneration_1.default.updateItemAutogeneration(this, true); } else { if (!this._defaultFile.isContentLoaded) { await this._defaultFile.loadContent(); } } await ProjectAutogeneration_1.default.updateItemAutogeneratedSideFiles(this); this._isFileContentProcessed = true; this._onFileRetrieved.dispatch(this, this._defaultFile); if (this.itemType === IProjectItemData_2.ProjectItemType.MCWorld || this.itemType === IProjectItemData_2.ProjectItemType.MCTemplate) { const mcworld = await MCWorld_1.default.ensureOnFile(this._defaultFile, this._project, this._handleMCWorldLoaded); if (mcworld) {