From b6ec71620c9c8eaad36b5f7515cd483c783211bf Mon Sep 17 00:00:00 2001 From: Mike Ammerlaan Date: Tue, 12 Dec 2023 08:34:51 -0800 Subject: [PATCH] Improve validation UX with better links; different rules for structure folders --- app/src/UX/ProjectEditor.tsx | 46 ++++++++-- app/src/UX/ProjectInfoDisplay.tsx | 6 +- app/src/UX/ProjectItemEditor.css | 4 + app/src/UX/ProjectItemList.tsx | 25 ++++- app/src/app/Pack.ts | 2 +- app/src/app/Project.ts | 21 +++++ app/src/app/ProjectItem.ts | 4 +- app/src/app/ProjectItemManager.ts | 2 +- app/src/core/Utilities.ts | 26 +----- .../info/AddOnItemRequirementsGenerator.ts | 20 ++-- app/src/info/AddOnRequirementsGenerator.ts | 91 +++++++++++++++++-- app/src/info/JsonFileTagsInfoGenerator.ts | 2 +- app/src/local/JsEslintInfoGenerator.ts | 2 +- app/src/minecraft/Lang.ts | 7 +- app/src/minecraft/LocManager.ts | 2 +- app/src/minecraft/MCWorld.ts | 2 +- app/src/storage/StorageUtilities.ts | 2 +- app/src/storage/ZipStorage.ts | 5 +- 18 files changed, 196 insertions(+), 73 deletions(-) diff --git a/app/src/UX/ProjectEditor.tsx b/app/src/UX/ProjectEditor.tsx index 460ca72a..e502a091 100644 --- a/app/src/UX/ProjectEditor.tsx +++ b/app/src/UX/ProjectEditor.tsx @@ -1847,6 +1847,31 @@ export default class ProjectEditor extends Component, - content: "Shareable Link", - onClick: this._handleGetShareableLinkClick, - title: "Get a shareable link of this project.", - }; - exportMenu.push(exportKeys[nextExportKey]); - + if (this.getIsLinkShareable()) { + exportKeys[nextExportKey] = { + key: nextExportKey, + icon: , + content: "Shareable Link", + onClick: this._handleGetShareableLinkClick, + title: "Get a shareable link of this project.", + }; + exportMenu.push(exportKeys[nextExportKey]); + } nextExportKey = "mcpackAddon"; exportKeys[nextExportKey] = { key: nextExportKey, @@ -2565,7 +2591,7 @@ export default class ProjectEditor extends Component) { + if (event.currentTarget && this.props.project) { + if ( + event.currentTarget.scrollTop > + event.currentTarget.scrollHeight - + (event.currentTarget.offsetHeight + event.currentTarget.scrollHeight / 20) && + this.state.maxItemsToShow < this.props.project.items.length + ) { + this.setState({ + activeItem: this.state.activeItem, + dialogMode: this.state.dialogMode, + maxItemsToShow: this.state.maxItemsToShow + Math.min(this.state.maxItemsToShow, 1100), + }); + } + } + } + _githubProjectUpdated(property: GitHubPropertyType, value?: string) { switch (property) { case GitHubPropertyType.repoName: @@ -829,7 +847,7 @@ export default class ProjectItemList extends Component 1) { + return this.#itemsByStoragePath[path.substring(nextSlash)]; + } + + return undefined; + } + public ensureItemByStoragePath( storagePath: string, storageType: ProjectItemStorageType, diff --git a/app/src/app/ProjectItem.ts b/app/src/app/ProjectItem.ts index fd784d4d..a6305f7a 100644 --- a/app/src/app/ProjectItem.ts +++ b/app/src/app/ProjectItem.ts @@ -725,7 +725,7 @@ export default class ProjectItem { zipStorage.storagePath = zipFile.storageRelativePath + "#"; - await zipStorage.loadFromUint8Array(zipFile.content); + await zipStorage.loadFromUint8Array(zipFile.content, zipFile.name); zipFile.fileContainerStorage = zipStorage; } @@ -825,7 +825,7 @@ export default class ProjectItem { zipStorage.storagePath = zipFile.storageRelativePath + "#"; - await zipStorage.loadFromUint8Array(zipFile.content); + await zipStorage.loadFromUint8Array(zipFile.content, zipFile.name); zipFile.fileContainerStorage = zipStorage; } diff --git a/app/src/app/ProjectItemManager.ts b/app/src/app/ProjectItemManager.ts index 99b9af15..e0562706 100644 --- a/app/src/app/ProjectItemManager.ts +++ b/app/src/app/ProjectItemManager.ts @@ -116,7 +116,7 @@ export default class ProjectItemManager { return undefined; } - await zipStorage.loadFromUint8Array(containerFile.content); + await zipStorage.loadFromUint8Array(containerFile.content, containerFile.name); containerFile.fileContainerStorage = zipStorage; containerFile.fileContainerStorage.storagePath = containerFilePath + "#"; } diff --git a/app/src/core/Utilities.ts b/app/src/core/Utilities.ts index c36ad3ac..b86abe48 100644 --- a/app/src/core/Utilities.ts +++ b/app/src/core/Utilities.ts @@ -19,31 +19,7 @@ export default class Utilities { static get isDebug(): boolean { if (Utilities._isDebug === undefined) { - if (AppServiceProxy.hasAppService) { - // @ts-ignore - if (typeof window !== "undefined") { - // @ts-ignore - if (window.location.href.indexOf("localhost") >= 0) { - Utilities._isDebug = true; - } - } - - if (!Utilities._isDebug) { - Utilities._isDebug = false; - } - // @ts-ignore - } else if (typeof window !== "undefined") { - // @ts-ignore - const query = window.location.search.toLowerCase(); - - if (query.indexOf("debug=true") >= 0) { - Utilities._isDebug = true; - } else { - Utilities._isDebug = false; - } - } else { - Utilities._isDebug = false; - } + Utilities._isDebug = false; } return Utilities._isDebug; diff --git a/app/src/info/AddOnItemRequirementsGenerator.ts b/app/src/info/AddOnItemRequirementsGenerator.ts index 672e7bfb..9ce01f00 100644 --- a/app/src/info/AddOnItemRequirementsGenerator.ts +++ b/app/src/info/AddOnItemRequirementsGenerator.ts @@ -44,7 +44,7 @@ export default class AddOnItemRequirementsGenerator implements IProjectInfoItemG this.id, 100, `Behavior pack animation controller identifier is not in expected form of controller.animation.xyz`, - undefined, + projectItem, bacName ) ); @@ -55,7 +55,7 @@ export default class AddOnItemRequirementsGenerator implements IProjectInfoItemG this.id, 101, `Behavior pack animation controller name section is not in expected form of controller.animation.creatorshortname_projectshortname`, - undefined, + projectItem, bacName ) ); @@ -80,7 +80,7 @@ export default class AddOnItemRequirementsGenerator implements IProjectInfoItemG this.id, 110, `Behavior animation identifier is not in expected form of animation.xyz.animation_name`, - undefined, + projectItem, aName ) ); @@ -91,7 +91,7 @@ export default class AddOnItemRequirementsGenerator implements IProjectInfoItemG this.id, 111, `Behavior pack animation name section is not in expected form of animation.creatorshortname_projectshortname.animation_name`, - undefined, + projectItem, aName ) ); @@ -116,7 +116,7 @@ export default class AddOnItemRequirementsGenerator implements IProjectInfoItemG this.id, 120, `Resource pack animation controller identifier is not in expected form of controller.animation.xyz`, - undefined, + projectItem, racName ) ); @@ -127,7 +127,7 @@ export default class AddOnItemRequirementsGenerator implements IProjectInfoItemG this.id, 121, `Resource pack animation controller name section is not in expected form of controller.animation.creatorshortname_projectshortname`, - undefined, + projectItem, racName ) ); @@ -152,7 +152,7 @@ export default class AddOnItemRequirementsGenerator implements IProjectInfoItemG this.id, 130, `Resource animation identifier is not in expected form of animation.xyz.animation_name`, - undefined, + projectItem, aName ) ); @@ -163,7 +163,7 @@ export default class AddOnItemRequirementsGenerator implements IProjectInfoItemG this.id, 131, `Resource animation name section is not in expected form of animation.creatorshortname_projectshortname.animation_name`, - undefined, + projectItem, aName ) ); @@ -188,7 +188,7 @@ export default class AddOnItemRequirementsGenerator implements IProjectInfoItemG this.id, 140, `Resource pack animation controller identifier is not in expected form of controller.render.xyz`, - undefined, + projectItem, rrcName ) ); @@ -199,7 +199,7 @@ export default class AddOnItemRequirementsGenerator implements IProjectInfoItemG this.id, 141, `Resource pack animation controller name section is not in expected form of controller.render.creatorshortname_projectshortname`, - undefined, + projectItem, rrcName ) ); diff --git a/app/src/info/AddOnRequirementsGenerator.ts b/app/src/info/AddOnRequirementsGenerator.ts index d8cef1f2..49402b50 100644 --- a/app/src/info/AddOnRequirementsGenerator.ts +++ b/app/src/info/AddOnRequirementsGenerator.ts @@ -192,7 +192,9 @@ export default class AddOnRequirementsGenerator implements IProjectInfoGenerator } if (childFolder && !folder.errorStatus) { - if ( + if (folderNameCanon === "structures") { + await this.generateFromFirstLevelFolderCreator_Game(project, childFolder, items); + } else if ( folderNameCanon !== "texts" && folderNameCanon !== "entities" && folderNameCanon !== "particles" && @@ -207,7 +209,7 @@ export default class AddOnRequirementsGenerator implements IProjectInfoGenerator folderNameCanon !== "render_controllers" && folderNameCanon !== "blocks" ) { - await this.generateFromFirstLevelFolder(project, childFolder, items); + await this.generateFromFirstLevelFolderCreatorNameGameName(project, childFolder, items); } } } @@ -245,7 +247,7 @@ export default class AddOnRequirementsGenerator implements IProjectInfoGenerator folderNameCanon !== "animation_controllers" && folderNameCanon !== "animations" ) { - await this.generateFromFirstLevelFolder(project, childFolder, items); + await this.generateFromFirstLevelFolderCreatorNameGameName(project, childFolder, items); } } } @@ -276,8 +278,70 @@ export default class AddOnRequirementsGenerator implements IProjectInfoGenerator return UniqueRegEx.test(name); } + async generateFromFirstLevelFolderCreator_Game(project: Project, folder: IFolder, items: ProjectInfoItem[]) { + await folder.load(false); + + for (const fileName in folder.files) { + const fileNameCanon = StorageUtilities.canonicalizeName(fileName); + + if ( + (folder.name !== "functions" || fileNameCanon !== "tick.json") && + (folder.name !== "textures" || + (fileNameCanon !== "flipbook_textures.json" && + fileNameCanon !== "item_texture.json" && + fileNameCanon !== "terrain_texture.json")) && + (folder.name !== "sounds" || fileNameCanon !== "sound_definitions.json") + ) { + const file = folder.files[fileName]; + + const projectItem = file?.extendedPath ? project.getItemByExtendedOrStoragePath(file?.extendedPath) : undefined; + + items.push( + new ProjectInfoItem( + InfoItemType.testCompleteFail, + this.id, + 111, + `Found a loose file in the ${folder.name} folder. Should only see files in the folder ${folder.name}\\creatorshortname_gamename\\`, + projectItem, + fileName + ) + ); + } + } - async generateFromFirstLevelFolder(project: Project, folder: IFolder, items: ProjectInfoItem[]) { + let folderCount = 0; + for (const folderName in folder.folders) { + const folderNameCanon = StorageUtilities.canonicalizeName(folderName); + folderCount++; + if (AddOnRequirementsGenerator.isUniqueNamespaceOrShortName(folderNameCanon)) { + items.push( + new ProjectInfoItem( + InfoItemType.testCompleteFail, + this.id, + 109, + `Found an add-on-blocked folder '${folderName}' in a parent folder pack\\${folder.name}. Should be named 'creatorshortname' and not a common term`, + undefined, + folderName + ) + ); + } + } + + if (folderCount > 1) { + items.push( + new ProjectInfoItem( + InfoItemType.testCompleteFail, + this.id, + 110, + `Folder '${folder.name}' has more than one subfolder, which is not supported. There should only be one folder in pack\\${folder.name}\\_`, + undefined, + folder.name + ) + ); + } + } + + async generateFromFirstLevelFolderCreatorNameGameName(project: Project, folder: IFolder, items: ProjectInfoItem[]) { await folder.load(false); for (const fileName in folder.files) { @@ -290,17 +354,22 @@ export default class AddOnRequirementsGenerator implements IProjectInfoGenerator fileNameCanon !== "item_texture.json" && fileNameCanon !== "terrain_texture.json")) && (folder.name !== "sounds" || fileNameCanon !== "sound_definitions.json") - ) + ) { + const file = folder.files[fileName]; + + const projectItem = file?.extendedPath ? project.getItemByExtendedOrStoragePath(file?.extendedPath) : undefined; + items.push( new ProjectInfoItem( InfoItemType.testCompleteFail, this.id, 101, `Found a loose file in the ${folder.name} folder. Should only see files in the folder ${folder.name}\\creatorshortname\\gamename\\`, - undefined, + projectItem, fileName ) ); + } } for (const folderName in folder.folders) { @@ -321,12 +390,12 @@ export default class AddOnRequirementsGenerator implements IProjectInfoGenerator const childFolder = folder.folders[folderName]; if (childFolder) { - await this.generateFromSecondLevelFolder(project, folder.name, childFolder, items); + await this.generateFromSecondLevelFolderGameName(project, folder.name, childFolder, items); } } } - async generateFromSecondLevelFolder( + async generateFromSecondLevelFolderGameName( project: Project, parentFolderName: string, folder: IFolder, @@ -335,13 +404,17 @@ export default class AddOnRequirementsGenerator implements IProjectInfoGenerator await folder.load(false); for (const fileName in folder.files) { + const file = folder.files[fileName]; + + const projectItem = file?.extendedPath ? project.getItemByExtendedOrStoragePath(file.extendedPath) : undefined; + items.push( new ProjectInfoItem( InfoItemType.testCompleteFail, this.id, 104, `Found a loose file '${fileName}' in ${parentFolderName}\\${folder.name}. Files should only be in the folder ${parentFolderName}\\${folder.name}\\`, - undefined, + projectItem, fileName ) ); diff --git a/app/src/info/JsonFileTagsInfoGenerator.ts b/app/src/info/JsonFileTagsInfoGenerator.ts index d787fa61..48b535d5 100644 --- a/app/src/info/JsonFileTagsInfoGenerator.ts +++ b/app/src/info/JsonFileTagsInfoGenerator.ts @@ -52,7 +52,7 @@ export default class JsonFileTagsInfoGenerator implements IProjectInfoGenerator zipStorage.storagePath = file.storageRelativePath + "#"; - await zipStorage.loadFromUint8Array(file.content); + await zipStorage.loadFromUint8Array(file.content, file.name); file.fileContainerStorage = zipStorage; } diff --git a/app/src/local/JsEslintInfoGenerator.ts b/app/src/local/JsEslintInfoGenerator.ts index 502863ff..78e10165 100644 --- a/app/src/local/JsEslintInfoGenerator.ts +++ b/app/src/local/JsEslintInfoGenerator.ts @@ -65,7 +65,7 @@ export default class JsEslintInfoGenerator implements IProjectInfoGenerator { zipStorage.storagePath = file.storageRelativePath + "#"; - await zipStorage.loadFromUint8Array(file.content); + await zipStorage.loadFromUint8Array(file.content, file.name); file.fileContainerStorage = zipStorage; } diff --git a/app/src/minecraft/Lang.ts b/app/src/minecraft/Lang.ts index 34b88fc8..8eedd26b 100644 --- a/app/src/minecraft/Lang.ts +++ b/app/src/minecraft/Lang.ts @@ -3,6 +3,7 @@ import { EventDispatcher, IEventHandler } from "ste-events"; import LocToken from "./LocToken"; import StorageUtilities from "../storage/StorageUtilities"; +import ZipStorage from "../storage/ZipStorage"; export default class Lang { private _file?: IFile; @@ -155,7 +156,11 @@ export default class Lang { dir = dir.parentFolder; } - this._containerName = dir.name; + if (dir.name === "" && !dir.parentFolder && (dir.storage as ZipStorage).name) { + this._containerName = (dir.storage as ZipStorage).name; + } else { + this._containerName = dir.name; + } } const lines = content.split("\n"); diff --git a/app/src/minecraft/LocManager.ts b/app/src/minecraft/LocManager.ts index e911a3bc..58d5c692 100644 --- a/app/src/minecraft/LocManager.ts +++ b/app/src/minecraft/LocManager.ts @@ -94,7 +94,7 @@ export default class LocManager { if (lang) { await lang.load(); - if (lang.language && lang.containerName) { + if (lang.language && lang.containerName !== undefined) { for (const tokenName in lang.tokens) { if (this.tokens[tokenName] === undefined) { this.tokens[tokenName] = {}; diff --git a/app/src/minecraft/MCWorld.ts b/app/src/minecraft/MCWorld.ts index 6a871a67..ebdf23b1 100644 --- a/app/src/minecraft/MCWorld.ts +++ b/app/src/minecraft/MCWorld.ts @@ -1005,7 +1005,7 @@ export default class MCWorld implements IGetSetPropertyObject, IDimension { storage = this._zipStorage; } - await storage.loadFromUint8Array(content); + await storage.loadFromUint8Array(content, this._file?.name); const rootFolder = storage.rootFolder; diff --git a/app/src/storage/StorageUtilities.ts b/app/src/storage/StorageUtilities.ts index 0710dd3e..2b66fbff 100644 --- a/app/src/storage/StorageUtilities.ts +++ b/app/src/storage/StorageUtilities.ts @@ -208,7 +208,7 @@ export default class StorageUtilities { zipStorage = new ZipStorage(); - await zipStorage.loadFromUint8Array(file.content); + await zipStorage.loadFromUint8Array(file.content, file.name); file.fileContainerStorage = zipStorage; file.fileContainerStorage.storagePath = file.storageRelativePath + "#"; diff --git a/app/src/storage/ZipStorage.ts b/app/src/storage/ZipStorage.ts index cc1ca94d..d22bfe4c 100644 --- a/app/src/storage/ZipStorage.ts +++ b/app/src/storage/ZipStorage.ts @@ -8,6 +8,7 @@ import StorageUtilities from "./StorageUtilities"; export default class ZipStorage extends StorageBase implements IStorage { private _jsz: JSZip; + name?: string; rootFolder: ZipFolder; modified: Date | null = null; lastLoadedOrSaved: Date | null = null; @@ -36,7 +37,6 @@ export default class ZipStorage extends StorageBase implements IStorage { static zipFixup() { if (CartoApp.hostType === HostType.electronNodeJs || CartoApp.hostType === HostType.toolsNodejs) { - // console.log("ZIP FIXUP DONE"); // eslint-disable-next-line eval("jszip_1.default = jszip_1"); } @@ -70,7 +70,7 @@ export default class ZipStorage extends StorageBase implements IStorage { this.lastLoadedOrSaved = new Date(); } - async loadFromUint8Array(data: Uint8Array) { + async loadFromUint8Array(data: Uint8Array, name?: string) { try { await this._jsz.loadAsync(data, { base64: false, @@ -82,6 +82,7 @@ export default class ZipStorage extends StorageBase implements IStorage { // Log.fail("Loading zip file from data " + data.length); + this.name = name; this.updateLastLoadedOrSaved(); await this.rootFolder.load(true);