diff --git a/app/package-lock.json b/app/package-lock.json index fb2182a4..40994001 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -24,7 +24,7 @@ "ajv": "^8.11.0", "axios": "^1.6.4", "commander": "^8.2.0", - "esbuild-wasm": "^0.21.3", + "esbuild-wasm": "^0.21.5", "esprima-next": "^6.0.2", "exifr": "^7.1.3", "file-saver": "^2.0.5", diff --git a/app/package.json b/app/package.json index 1ec1e958..9e0ffba7 100644 --- a/app/package.json +++ b/app/package.json @@ -27,7 +27,7 @@ "ajv": "^8.11.0", "axios": "^1.6.4", "commander": "^8.2.0", - "esbuild-wasm": "^0.21.3", + "esbuild-wasm": "^0.21.5", "esprima-next": "^6.0.2", "exifr": "^7.1.3", "file-saver": "^2.0.5", diff --git a/app/public/data/forms/block_minecraft_geometry.form.json b/app/public/data/forms/block_minecraft_geometry.form.json new file mode 100644 index 00000000..cebfaa94 --- /dev/null +++ b/app/public/data/forms/block_minecraft_geometry.form.json @@ -0,0 +1,17 @@ +{ + "title": "Geometry", + "description": "Specifies the geometry description identifier to use to render this block.", + "fields": [ + { + "id": "identifier", + "dataType": 2, + "description": "The geometry description identifier to use to render this block. This identifier must match an existing geometry identifier in any of the currently loaded resource packs." + }, + { + "id": "bone_visibility", + "dataType": 27, + "description": "Defines the visibility of individual bones in the geometry file. \n\nNote that all bones default to 'true,' so bones should only be defined if they are being set to 'false.' Including bones set to 'true' will work the same as the default." + } + ], + "id": "minecraft:geometry" +} \ No newline at end of file diff --git a/app/public/data/forms/dataform.form.json b/app/public/data/forms/dataform.form.json index c12d9543..65df9628 100644 --- a/app/public/data/forms/dataform.form.json +++ b/app/public/data/forms/dataform.form.json @@ -97,11 +97,11 @@ }, { "id": 14, - "title": "Keyed object collection" + "title": "Keyed object collection ({\"foo\": {\"color\":\"green\"}})" }, { "id": 15, - "title": "Object array" + "title": "Object array ([\"foo\", \"green\"])" }, { "id": 16, @@ -137,7 +137,7 @@ }, { "id": 24, - "title": "Keyed string collection" + "title": "Keyed string ({\"foo\": \"alpha\"})" }, { "id": 25, @@ -146,6 +146,14 @@ { "id": 26, "title": "Uuid" + }, + { + "id": 27, + "title": "Keyed boolean ({\"foo\": true})" + }, + { + "id": 28, + "title": "Keyed string array ({\"foo\": [\"alpha\"]})" } ] }, diff --git a/app/public/data/forms/entity_minecraft_can_climb.form.json b/app/public/data/forms/entity_minecraft_can_climb.form.json new file mode 100644 index 00000000..09e7d4b5 --- /dev/null +++ b/app/public/data/forms/entity_minecraft_can_climb.form.json @@ -0,0 +1,5 @@ +{ + "title": "Can climb", + "description": "Allows an entity to climb ladders.", + "fields": [] +} diff --git a/app/public/data/forms/entity_minecraft_is_hidden_when_invisible.form.json b/app/public/data/forms/entity_minecraft_is_hidden_when_invisible.form.json new file mode 100644 index 00000000..3d45ef26 --- /dev/null +++ b/app/public/data/forms/entity_minecraft_is_hidden_when_invisible.form.json @@ -0,0 +1,5 @@ +{ + "title": "Is Hidden When Invisible", + "description": "The entity can hide from hostile mobs while invisible.", + "fields": [] +} diff --git a/app/public/data/gallery.json b/app/public/data/gallery.json index 0cf9821a..05b1a4d1 100644 --- a/app/public/data/gallery.json +++ b/app/public/data/gallery.json @@ -147,21 +147,147 @@ "id": "fishBowlBlock", "type": 2 }, + { + "title": "Spawn Feather Item", + "topics": ["Dimension.spawnItem", "ItemStack", "MinecraftItemTypes"], + "sampleSet": "server", + "localLogo": "items/feather.png", + "id": "spawnFeatherItem", + "type": 3 + }, + { + "title": "Test that Entity is Feather Item", + "topics": ["EntityItemComponent", "ItemStack.getComponent", "Dimension.getEntities"], + "sampleSet": "server", + "localLogo": "items/feather.png", + "id": "testThatEntityIsFeatherItem", + "type": 3 + }, + { + "title": "Give player equipment", + "topics": [ + "EntityEquippableComponent.setEquipment", + "World.getAllPlayers", + "Dimension.spawnEntity", + "MinecraftItemTypes", + "EntityComponentTypes", + "ItemStack" + ], + "sampleSet": "server", + "localLogo": "items/feather.png", + "id": "givePlayerEquipment", + "type": 3 + }, + { + "title": "Give player a damaged sword", + "topics": [ + "ItemDurabilityComponent", + "ItemStack.getComponent", + "ItemComponentTypes", + "EntityInventoryComponent", + "World.getAllPlayers", + "Entity.getComponent", + "Container.addItem" + ], + "sampleSet": "server", + "localLogo": "items/wood_sword.png", + "id": "giveHurtDiamondSword", + "type": 3 + }, + { + "title": "Give player a destroy-restricted pickaxe", + "topics": ["ItemStack.setCanDestroy", "World.getAllPlayers", "EntityInventoryComponent", "Container.addItem"], + "sampleSet": "server", + "localLogo": "items/wood_pickaxe.png", + "id": "giveDestroyRestrictedPickaxe", + "type": 3 + }, + { + "title": "Give player a place-restricted gold block", + "topics": ["ItemStack.setCanPlaceOn", "World.getAllPlayers", "EntityInventoryComponent", "Container.addItem"], + "sampleSet": "server", + "localLogo": "items/gold_ingot.png", + "id": "givePlaceRestrictedGoldBlock", + "type": 3 + }, + { + "title": "Give player a sword with custom lore", + "topics": ["ItemStack.setLore", "World.getAllPlayers", "EntityInventoryComponent", "Container.setItem"], + "sampleSet": "server", + "localLogo": "items/diamond_sword.png", + "id": "diamondAwesomeSword", + "type": 3 + }, + { + "title": "Get first hotbar item", + "topics": ["Container.getItem", "World.getAllPlayers", "EntityInventoryComponent"], + "sampleSet": "server", + "localLogo": "items/item_frame.png", + "id": "getFirstHotbarItem", + "type": 3 + }, + { + "title": "Move an item between containers", + "topics": ["Container.moveItem", "Dimension.spawnEntity", "World.getAllPlayers", "EntityInventoryComponent"], + "sampleSet": "server", + "localLogo": "items/minecart_chest.png", + "id": "moveBetweenContainers", + "type": 3 + }, + { + "title": "Swap an item between containers", + "topics": ["Container.swapItems", "Dimension.spawnEntity", "World.getAllPlayers", "EntityInventoryComponent"], + "sampleSet": "server", + "localLogo": "items/minecart_chest.png", + "id": "swapBetweenContainers", + "type": 3 + }, + { + "title": "Transfer an item between containers", + "topics": ["Container.transferItem", "Dimension.spawnEntity", "World.getAllPlayers", "EntityInventoryComponent"], + "sampleSet": "server", + "localLogo": "items/minecart_chest.png", + "id": "transferBetweenContainers", + "type": 3 + }, { "title": "Create Explosion", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "Dimension.createExplosion", + "ExplosionOptions.breaksBlocks", + "Vector3Utils.floor", + "Vector3Utils.add" + ], + "sampleSet": "server", "localLogo": "items/fireball.png", "id": "createExplosion", - "codeLineStart": 1, + "type": 3 + }, + { + "title": "Explosions of Different Types", + "topics": [ + "Dimension.createExplosion", + "ExplosionOptions.causesFire", + "ExplosionOptions.allowUnderwater", + "Vector3Utils.add" + ], + "sampleSet": "server", + "localLogo": "items/fireball.png", + "id": "createExplosions", "type": 3 }, { "title": "Button Push Event", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "ButtonPushAfterEvent", + "ButtonPushAfterEventSignal", + "World.afterEvents", + "Dimension.getBlock", + "Block.setPermutation", + "BlockPermutation.resolve", + "BlockPermutation.withState" + ], + "sampleSet": "server", "localLogo": "blocks/barrel_bottom.png", "id": "buttonPushEvent", "codeLineStart": 15, @@ -169,19 +295,33 @@ }, { "title": "Lever Action Event", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "LeverActionAfterEvent", + "LeverActionAfterEventSignal", + "World.afterEvents", + "Dimension.getBlock", + "Block.setPermutation", + "BlockPermutation.resolve", + "BlockPermutation.withState" + ], + "sampleSet": "server", "localLogo": "blocks/lever.png", "id": "leverActionEvent", "codeLineStart": 15, "type": 3 }, { - "title": "Tripwire Trip Event", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "title": "TripWire Trip Event", + "topics": [ + "TripWireTripAfterEvent", + "TripWireTripAfterEventSignal", + "World.afterEvents", + "Dimension.getBlock", + "Block.setPermutation", + "BlockPermutation.resolve", + "BlockPermutation.withState" + ], + "sampleSet": "server", "localLogo": "blocks/cut_copper.png", "id": "tripWireTripEvent", "codeLineStart": 13, @@ -189,37 +329,37 @@ }, { "title": "Block Color Cube", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["MinecraftBlockTypes", "BlockPermutation.resolve", "Vector3Utils.add", "Dimension.getBlock"], + "sampleSet": "server", "localLogo": "blocks/wool_colored_red.png", "id": "addBlockColorCube", "type": 3 }, { "title": "No-Block Explosion", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "ExplosionOptions.breaksBlocks", + "Dimension.createExplosion", + "Vector3Utils.floor", + "Vector3Utils.add" + ], + "sampleSet": "server", "localLogo": "items/fireball.png", "id": "createNoBlockExplosion", - "codeLineStart": 1, "type": 3 }, { "title": "Item Stacks", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["ItemStack", "MinecraftItemTypes", "Dimension.spawnItem"], + "sampleSet": "server", "localLogo": "items/diamond_pickaxe.png", "id": "itemStacks", "type": 3 }, { "title": "Spawn Mobs with Effects", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["Entity.addEffect", "Dimension.spawnEntity", "MinecraftEntityTypes", "Entity.isSneaking"], + "sampleSet": "server", "localLogo": "entity/wolf/wolf.png", "logoLocation": { "x": 4, "y": 4, "width": 6, "height": 6 }, "id": "quickFoxLazyDog", @@ -227,9 +367,8 @@ }, { "title": "Set Dynamic Property", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["World.setDynamicProperty", "World.getDynamicProperty"], + "sampleSet": "server", "localLogo": "blocks/structure_block_data.png", "id": "incrementDynamicProperty", "codeLineStart": 9, @@ -237,9 +376,8 @@ }, { "title": "Set Json Dynamic Property", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["World.setDynamicProperty", "World.getDynamicProperty", "JSON.stringify", "JSON.parse"], + "sampleSet": "server", "localLogo": "blocks/structure_block_data.png", "id": "incrementDynamicPropertyInJsonBlob", "codeLineStart": 25, @@ -247,54 +385,60 @@ }, { "title": "Trigger Event", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["Entity.triggerEvent"], + "sampleSet": "server", "localLogo": "blocks/redstone_lamp_on.png", "id": "triggerEvent", "type": 3 }, { "title": "Apply Impulse", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["Entity.applyImpulse", "Entity.clearVelocity", "Dimension.spawnEntity"], + "sampleSet": "server", "localLogo": "items/potion_bottle_splash_jump.png", "id": "applyImpulse", "type": 3 }, { "title": "Get Entity Velocity", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["Entity.getVelocity", "Dimension.spawnEntity"], + "sampleSet": "server", "localLogo": "ui/speed_effect.png", "id": "getFireworkVelocity", "type": 3 }, { "title": "Apply Damage and Heal", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["Entity.getVelocity", "Dimension.spawnEntity", "MinecraftEntityTypes"], + "sampleSet": "server", "localLogo": "items/potion_bottle_splash_damageBoost.png", "id": "applyDamageThenHeal", "type": 3 }, { "title": "Set On Fire", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "Entity.setOnFire", + "Entity.extinguishFire", + "EntityOnFireComponent", + "Dimension.spawnEntity", + "MinecraftEntityTypes" + ], + "sampleSet": "server", "localLogo": "blocks/fire_1_placeholder.png", "id": "setOnFire", "type": 3 }, { "title": "Teleport", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "Entity.teleport", + "TeleportOptions", + "System.runTimeout", + "Dimension.spawnEntity", + "MinecraftEntityTypes" + ], + "sampleSet": "server", "localLogo": "ui/portalBg.png", "id": "teleport", "codeLineStart": 2, @@ -302,9 +446,15 @@ }, { "title": "Teleport for Movement", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "Entity.teleport", + "TeleportOptions", + "System.runInterval", + "System.clearRun", + "Dimension.spawnEntity", + "MinecraftEntityTypes" + ], + "sampleSet": "server", "localLogo": "ui/portalBg.png", "id": "teleportMovement", "codeLineStart": 3, @@ -312,9 +462,8 @@ }, { "title": "Apply Knockback", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["Entity.applyKnockback", "Dimension.spawnEntity", "EntityQueryOptions", "Dimension.getEntities"], + "sampleSet": "server", "localLogo": "blocks/cut_copper.png", "id": "bounceSkeletons", "codeLineStart": 8, @@ -322,58 +471,159 @@ }, { "title": "Tag Query", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "EntityQueryOptions.tags", + "Entity.addTag", + "Dimension.getEntities", + "Dimension.spawnEntity", + "EntityQueryOptions" + ], + "sampleSet": "server", "localLogo": "items/name_tag.png", "id": "tagsQuery", "codeLineStart": 4, "type": 3 }, { - "title": "Portal Generator", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "title": "Entity spawn event with test", + "topics": [ + "EntitySpawnAfterEventSignal", + "EntitySpawnAfterEvent", + "Dimension.spawnEntity", + "System.runTimeout", + "Vector3Utils.add" + ], + "sampleSet": "server", "localLogo": "blocks/cut_copper.png", "id": "logEntitySpawnEvent", - "codeLineStart": 1, "type": 3 }, { "title": "Create mob", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", - "localLogo": "ui/promo_bee.png", + "topics": ["Dimension.spawnEntity", "Vector3Utils.add"], + "sampleSet": "server", + "localLogo": "items/spawn_egg.png", "id": "spawnAdultHorse", - "codeLineStart": 1, "type": 3 }, { - "title": "Give player equipment", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "title": "Spawn poisoned villager", + "topics": ["Entity.addEffect", "Dimension.spawnEntity", "MinecraftEffectTypes", "Vector3Utils.add"], + "sampleSet": "server", + "localLogo": "items/spawn_egg.png", + "id": "spawnPoisonedVillager", + "type": 3 + }, + { + "title": "Conditionally spawn entities on block", + "topics": ["Block.matches", "Dimension.getEntities", "Dimension.getBlock", "Dimension.spawnEntity"], + "sampleSet": "server", + "localLogo": "items/fish_raw.png", + "id": "blockConditional", + "type": 3 + }, + { + "title": "Shoot arrow", + "topics": [ + "EntityProjectileComponent.shoot", + "Dimension.spawnEntity", + "Entity.getComponent", + "Dimension.spawnEntity", + "EntityQueryOptions" + ], + "sampleSet": "server", + "localLogo": "items/arrow.png", + "id": "shootArrow", + "type": 3 + }, + { + "title": "Find entities based on properties", + "topics": ["EntityQueryOptions.propertyOptions", "Dimension.getEntities", "EntityQueryOptions"], + "sampleSet": "server", + "localLogo": "items/seeds_beetroot.png", + "id": "findEntitiesHavingPropertyEqualsTo", + "type": 3 + }, + { + "title": "Give player elytra", + "topics": [ + "EntityEquippableComponent.setEquipment", + "EquipmentSlot", + "Entity.getComponent", + "ItemStack", + "MinecraftItemTypes", + "World.getAllPlayers" + ], + "sampleSet": "server", "localLogo": "items/elytra.png", "id": "givePlayerElytra", "type": 3 }, + { + "title": "Play sound chained", + "topics": ["Entity.playSound", "Dimension.getEntities", "Dimension.getPlayers"], + "sampleSet": "server", + "localLogo": "items/record_cat.png", + "id": "playSoundChained", + "type": 3 + }, + { + "title": "Set scoreboard chained", + "topics": [ + "Scoreboard.setScore", + "World.scoreboard", + "Scoreboard.addObjective", + "Entity.scoreboardIdentity", + "Dimension.getEntities" + ], + "sampleSet": "server", + "localLogo": "items/painting.png", + "id": "setScoreboardChained", + "type": 3 + }, + { + "title": "A message that displays a scoreboard score using a wildcard", + "topics": ["RawMessage", "World.sendMessage"], + "sampleSet": "server", + "localLogo": "items/painting.png", + "id": "scoreWildcard", + "type": 3 + }, + { + "title": "Update a signs existing sign text", + "topics": ["RawMessage", "RawText", "BlockSignComponent.setText", "BlockComponentTypes", "Block.getComponent"], + "sampleSet": "server", + "localLogo": "items/painting.png", + "id": "updateSignText", + "type": 3 + }, + { + "title": "Summon mob chained", + "topics": ["Dimension.spawnEntity", "Dimension.getEntities", "Dimension.getPlayers"], + "sampleSet": "server", + "localLogo": "items/record_cat.png", + "id": "summonMobChained", + "type": 3 + }, { "title": "Play Music and Sound", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "World.playMusic", + "World.playSound", + "MusicOptions", + "WorldSoundOptions", + "PlayerSoundOptions", + "World.getPlayers" + ], + "sampleSet": "server", "localLogo": "items/record_cat.png", "id": "playMusicAndSound", - "codeLineStart": 1, "type": 3 }, { "title": "Spawn Particles", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["Dimensions.spawnParticle", "MolangVariableMap.setColorRGB"], + "sampleSet": "server", "localLogo": "particle/particles.png", "logoLocation": { "x": 56, "y": 0, "width": 8, "height": 8, "imageWidth": 128 }, "id": "spawnParticle", @@ -382,18 +632,29 @@ }, { "title": "Piston After Event", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "PistonActivateAfterEvent", + "PistonActivateAfterEventSignal", + "Block.setPermutation", + "Dimension.getBlock", + "MinecraftBlockTypes" + ], + "sampleSet": "server", "localLogo": "blocks/piston_side.png", "id": "pistonAfterEvent", "type": 3 }, { "title": "Update Scoreboard", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "Scoreboard.getObjective", + "Scoreboard.addObjective", + "Entity.scoreboardIdentity", + "Scoreboard.setScore", + "ScoreboardObjective.setScore", + "DisplaySlotId" + ], + "sampleSet": "server", "localLogo": "blocks/cut_copper.png", "codeLineStart": 5, "id": "updateScoreboard", @@ -401,56 +662,132 @@ }, { "title": "Set Onscreen Display Title", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["ScreenDisplay.setTitle", "World.getPlayers"], + "sampleSet": "server", "localLogo": "ui/screenshot_frame.png", "id": "setTitle", "type": 3 }, { "title": "Set Display Title/Subtitle", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", - "localLogo": "ui/screenshot_frame.png", - "codeLineStart": 1, + "topics": ["ScreenDisplay.setTitle", "World.getPlayers"], + "sampleSet": "server", + "localLogo": "items/sign_darkoak.png", "id": "setTitleAndSubtitle", "type": 3 }, { "title": "Countdown", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", - "localLogo": "ui/timer.png", + "topics": [ + "ScreenDisplay.setTitle", + "ScreenDisplay.updateSubtitle", + "World.getPlayers", + "System.runInterval", + "System.clearRun" + ], + "sampleSet": "server", + "localLogo": "items/clock_item.png", "id": "countdown", "type": 3 }, { "title": "Send Basic Message", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", - "localLogo": "ui/dialog_background_opaque_overlap_bottom.png", + "topics": ["Player.sendMessage", "World.getPlayers"], + "sampleSet": "server", + "localLogo": "items/paper.png", "id": "sendBasicMessage", "type": 3 }, + { + "title": "Send Translated Message", + "topics": ["RawMessage", "Player.sendMessage", "World.getPlayers"], + "sampleSet": "server", + "localLogo": "items/paper.png", + "id": "sendTranslatedMessage", + "type": 3 + }, + { + "title": "Send Players Different Message Types", + "topics": ["RawMessage", "Player.sendMessage", "World.getAllPlayers"], + "sampleSet": "server", + "localLogo": "items/paper.png", + "id": "sendPlayerMessages", + "type": 3 + }, + { + "title": "Messages with nested translatations", + "topics": ["RawMessage", "Player.sendMessage", "World.getPlayers"], + "sampleSet": "server", + "localLogo": "items/paper.png", + "id": "nestedTranslation", + "type": 3 + }, + { + "title": "Cube generator", + "topics": ["System.runJob", "Block.setPermutation"], + "sampleSet": "server", + "localLogo": "blocks/cobblestone.png", + "id": "cubeGenerator", + "type": 3 + }, + { + "title": "Phantoms should fly from cats", + "topics": ["Test.spawn", "Test.assertEntityPresentInArea", "register"], + "sampleSet": "server-gametest-beta", + "localLogo": "blocks/cobblestone.png", + "id": "phantomsShouldFlyFromCats", + "type": 3 + }, + { + "title": "Tests a roller coaster obstacle course.", + "topics": ["Test.spawn", "Test.assertEntityPresentInArea", "register"], + "sampleSet": "server-gametest-beta", + "localLogo": "blocks/cobblestone.png", + "id": "minibiomes", + "type": 3 + }, + { + "title": "A simple mob test - the fox should attack the chicken.", + "topics": ["Test.spawn", "Test.assertEntityPresentInArea", "register"], + "sampleSet": "server-gametest-beta", + "localLogo": "blocks/cobblestone.png", + "id": "simpleMobGameTest", + "type": 3 + }, + { + "title": "Get Player Profile", + "topics": ["variables", "secrets"], + "sampleSet": "server-admin-beta", + "localLogo": "blocks/cobblestone.png", + "id": "getPlayerProfile", + "type": 3 + }, + { + "title": "Update score via Http Request", + "topics": ["HttpRequestMethod", "HttpHeader", "HttpResponse"], + "sampleSet": "server-net-beta", + "localLogo": "blocks/cobblestone.png", + "id": "updateScore", + "type": 3 + }, { "title": "Translated Message Form", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["MessageFormData", "MessageFormResponse", "World.getPlayers"], + "sampleSet": "server-ui", "localLogo": "ui/dialog_background_opaque_overlap_bottom.png", "id": "showTranslatedMessageForm", - "codeLineStart": 1, "type": 3 }, { "title": "Add Sign", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "BlockSignComponent.setText", + "Block.getComponent", + "BlockPermutation.resolve", + "Block.setPermutation", + "Dimension.getBlock" + ], + "sampleSet": "server", "localLogo": "ui/icon_sign.png", "id": "addSign", "codeLineStart": 9, @@ -458,9 +795,15 @@ }, { "title": "Add Translated Sign", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "BlockSignComponent.setText", + "RawMessage", + "Block.getComponent", + "BlockPermutation.resolve", + "Block.setPermutation", + "Dimension.getBlock" + ], + "sampleSet": "server", "localLogo": "ui/icon_sign.png", "id": "addTranslatedSign", "codeLineStart": 9, @@ -468,59 +811,108 @@ }, { "title": "Add Two Sided Sign", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "BlockSignComponent.setText", + "BlockSignComponent.setTextDyeColor", + "BlockSignComponent.setWaxed", + "RawMessage", + "Block.getComponent", + "BlockPermutation.resolve", + "Block.setPermutation", + "Dimension.getBlock" + ], + "sampleSet": "server", "localLogo": "ui/icon_sign.png", "id": "addTwoSidedSign", "type": 3 }, { "title": "Trap Tick", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["System.run", "World.sendMessage"], + "sampleSet": "server", "localLogo": "ui/timer.png", "id": "trapTick", - "codeLineStart": 1, "type": 3 }, { "title": "Every 30 Seconds", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["System.runInterval", "World.sendMessage"], + "sampleSet": "server", "localLogo": "ui/timer.png", "id": "every30Seconds", "type": 3 }, { "title": "Check Block Tags", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": ["Block.hasTag", "Dimension.getBlock"], + "sampleSet": "server", "localLogo": "ui/timer.png", "id": "checkBlockTags", "type": 3 }, { "title": "Containers", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "BlockInventoryComponent", + "EntityInventoryComponent", + "Container", + "Dimension.getBlock", + "ItemStack", + "MinecraftItemTypes" + ], + "sampleSet": "server", "localLogo": "ui/timer.png", "id": "containers", "type": 3 }, { "title": "Place Items in Chest", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/script-box", + "topics": [ + "BlockInventoryComponent", + "ItemStack", + "MinecraftBlockTypes", + "Dimension.getBlock", + "Container.setItem", + "Block.setType", + "MinecraftItemTypes" + ], + "sampleSet": "server", "localLogo": "ui/timer.png", "id": "placeItemsInChest", "type": 3 }, + { + "title": "Show action form", + "topics": ["ActionFormData", "ActionFormResponse", "World.getPlayers"], + "sampleSet": "server-ui", + "localLogo": "ui/timer.png", + "id": "showActionForm", + "type": 3 + }, + { + "title": "Show favorite month dialog", + "topics": ["ActionFormData", "ActionFormResponse", "World.getPlayers"], + "sampleSet": "server-ui", + "localLogo": "ui/timer.png", + "id": "showFavoriteMonth", + "type": 3 + }, + { + "title": "Show message form", + "topics": ["MessageFormData", "MessageFormResponse", "World.getPlayers"], + "sampleSet": "server-ui", + "localLogo": "ui/timer.png", + "id": "showBasicMessageForm", + "type": 3 + }, + { + "title": "Show basic modal form", + "topics": ["ModalFormData", "ModalFormResponse", "World.getPlayers"], + "sampleSet": "server-ui", + "localLogo": "ui/timer.png", + "id": "showBasicModalForm", + "type": 3 + }, { "title": "Allay", "description": "The allay is a new befriend-able flying mob that loves to collect things.", @@ -849,90 +1241,74 @@ }, { "title": "Camera Grapple", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/editor-script-box", + "topics": ["IModalToolContainer", "IPlayerUISession", "ActionManager", "registerEditorExtension"], + "sampleSet": "server-editor", "localLogo": "blocks/camera_front.png", "id": "camera-grapple.ts", "codeLineStart": 19, - "tags": ["editor"], "type": 5 }, { "title": "Star Shape Brush", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/editor-script-box", + "topics": ["CompoundBlockVolume", "IPlayerUISession", "ActionManager", "registerEditorExtension"], + "sampleSet": "server-editor", "localLogo": "items/nether_star.png", "id": "star-brush-shape.ts", "codeLineStart": 17, - "tags": ["editor"], "type": 5 }, { "title": "Dye Brush", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/editor-script-box", + "topics": ["ExtensionContext", "IPlayerUISession", "ActionManager", "registerEditorExtension"], + "sampleSet": "server-editor", "localLogo": "items/dye_powder_red.png", "id": "dye-brush.ts", "codeLineStart": 117, - "tags": ["editor"], "type": 5 }, { "title": "Farm Generator", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/editor-script-box", + "topics": ["IPlayerUISession", "ActionManager", "registerEditorExtension"], + "sampleSet": "server-editor", "localLogo": "blocks/farmland_wet.png", "id": "farm-generator.ts", "codeLineStart": 41, - "tags": ["editor"], "type": 5 }, { "title": "Go to Mark", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/editor-script-box", + "topics": ["IPlayerUISession", "ActionManager", "registerEditorExtension"], + "sampleSet": "server-editor", "localLogo": "blocks/cut_copper.png", "id": "goto-mark.ts", "codeLineStart": 96, - "tags": ["editor"], "type": 5 }, { "title": "Portal Generator", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/editor-script-box", + "topics": ["IPlayerUISession", "ActionManager", "registerEditorExtension"], + "sampleSet": "server-editor", "localLogo": "blocks/cut_copper.png", "id": "portal-generator.ts", "codeLineStart": 316, - "tags": ["editor"], "type": 5 }, { "title": "Locate Biome", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/editor-script-box", + "topics": ["IPlayerUISession", "ActionManager", "registerEditorExtension"], + "sampleSet": "server-editor", "localLogo": "blocks/cut_copper.png", "id": "simple-locate-biome.ts", "codeLineStart": 93, - "tags": ["editor"], "type": 5 }, { "title": "Tree Generator", - "gitHubOwner": "microsoft", - "gitHubRepoName": "minecraft-scripting-samples", - "gitHubFolder": "/editor-script-box", + "topics": ["IPlayerUISession", "ActionManager", "registerEditorExtension"], + "sampleSet": "server-editor", "localLogo": "blocks/cut_copper.png", "id": "tree-generator.ts", "codeLineStart": 47, - "tags": ["editor"], "type": 5 }, { diff --git a/app/public/data/snippets/editor-samples.json b/app/public/data/snippets/editor.json similarity index 100% rename from app/public/data/snippets/editor-samples.json rename to app/public/data/snippets/editor.json diff --git a/app/public/data/snippets/index.json b/app/public/data/snippets/index.json index 4acf9d85..d3dbbf59 100644 --- a/app/public/data/snippets/index.json +++ b/app/public/data/snippets/index.json @@ -1,7 +1,11 @@ { "files": [ - "server-samples.json", - "server-ui-samples.json", - "editor-samples.json" + "server.json", + "server-ui.json", + "editor.json", + "server-admin-beta.json", + "server-gametest-beta.json", + "server-net-beta.json", + "server-beta.json" ] } diff --git a/app/public/data/snippets/server-admin-beta.json b/app/public/data/snippets/server-admin-beta.json new file mode 100644 index 00000000..3259c18b --- /dev/null +++ b/app/public/data/snippets/server-admin-beta.json @@ -0,0 +1,22 @@ +{ + "getPlayerProfile": { + "description": "Uses secrets and variables from dedicated server configuration files to further parameterize web requests. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-admin/serversecrets", + "prefix": ["mc"], + "body": [ + "// Note this code will only run on Bedrock Dedicated Server.", + " const serverUrl = mcsa.variables.get('serverEndpoint');", + " const req = new HttpRequest(serverUrl + 'getPlayerProfile');", + " req.body = JSON.stringify({", + " playerId: 'johndoe',", + " });", + " const authTokenSec = mcsa.secrets.get('authtoken');", + " if (!authTokenSec) {", + " log('authtoken secret not defined.', -1);", + " return;", + " }", + " req.method = HttpRequestMethod.Post;", + " req.headers = [new HttpHeader('Content-Type', 'application/json'), new HttpHeader('auth', authTokenSec)];", + " await http.request(req);" + ] + } +} diff --git a/app/public/data/snippets/server-beta.json b/app/public/data/snippets/server-beta.json new file mode 100644 index 00000000..7b112f68 --- /dev/null +++ b/app/public/data/snippets/server-beta.json @@ -0,0 +1,42 @@ +{ +"customCommand": { + "description": "Implements a very basic command system using the experiment chatbefore event. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/chatsendbeforeeventsignal", + "prefix": ["mc"], + "body": [" const chatCallback = world.beforeEvents.chatSend.subscribe((eventData) => {", +" if (eventData.message.includes('cancel')) {", +" // Cancel event if the message contains 'cancel'", +" eventData.cancel = true;", +" } else {", +" const args = eventData.message.split(' ');", +" if (args.length > 0) {", +" switch (args[0].toLowerCase()) {", +" case 'echo':", +" // Send a modified version of chat message", +" world.sendMessage(`Echo '${eventData.message.substring(4).trim()}'`);", +" break;", +" case 'help':", +" world.sendMessage(`Available commands: echo `);", +" break;", +" }", +" }", +" }", +" });" +]}, +"minibiomes": { + "description": "Tests a roller coaster obstacle course. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityrideablecomponent", + "prefix": ["mc"], + "body": ["import { EntityComponentTypes } from '@minecraft/server';", +"import { Test, register } from '@minecraft/server-gametest';", +"import { MinecraftBlockTypes, MinecraftEntityTypes } from '@minecraft/vanilla-data';", +"function minibiomes(test: Test) {", +" const minecart = test.spawn(MinecraftEntityTypes.Minecart, { x: 9, y: 7, z: 7 });", +" const pig = test.spawn(MinecraftEntityTypes.Pig, { x: 9, y: 7, z: 7 });", +" test.setBlockType(MinecraftBlockTypes.Cobblestone, { x: 10, y: 7, z: 7 });", +" const minecartRideableComp = minecart.getComponent(EntityComponentTypes.Rideable);", +" minecartRideableComp?.addRider(pig);", +" test.succeedWhenEntityPresent(MinecraftEntityTypes.Pig, { x: 8, y: 3, z: 1 }, true);", +"}", +"register('ChallengeTests', 'minibiomes', minibiomes).structureName('gametests:minibiomes').maxTicks(160);", +"" +]} +} \ No newline at end of file diff --git a/app/public/data/snippets/server-gametest-beta.json b/app/public/data/snippets/server-gametest-beta.json new file mode 100644 index 00000000..6b4d2862 --- /dev/null +++ b/app/public/data/snippets/server-gametest-beta.json @@ -0,0 +1,53 @@ +{ + "simpleMobGameTest": { + "description": "A simple mob test - the fox should attack the chicken. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-gametest/minecraft-server-gametest/register", + "prefix": ["mc"], + "body": [ + "// Note this code will run in experimental worlds with the Beta APIs experiment, and supporting GameTest structures.", + "function simpleMobGameTest(test: Test) {", + " const attackerId = MinecraftEntityTypes.Fox;", + " const victimId = MinecraftEntityTypes.Chicken;", + " test.spawn(attackerId, { x: 5, y: 2, z: 5 });", + " test.spawn(victimId, { x: 2, y: 2, z: 2 });", + " test.assertEntityPresentInArea(victimId, true);", + " test.succeedWhen(() => {", + " test.assertEntityPresentInArea(victimId, false);", + " });", + "}", + "register('StarterTests', 'simpleMobTest', simpleMobGameTest).maxTicks(400).structureName('gametests:mediumglass');", + "" + ] + }, + "phantomsShouldFlyFromCats": { + "description": "Tests a failure case - phantoms should fly away from cats, but get captured by them. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-gametest/minecraft-server-gametest/register", + "prefix": ["mc"], + "body": [ + "// Note this code will run in experimental worlds with the Beta APIs experiment, and supporting GameTest structures.", + "function phantomsShouldFlyFromCats(test: Test) {", + " test.spawn(MinecraftEntityTypes.Cat, { x: 4, y: 3, z: 3 });", + " test.spawn(MinecraftEntityTypes.Phantom, { x: 4, y: 3, z: 3 });", + " test.succeedWhenEntityPresent(MinecraftEntityTypes.Phantom, { x: 4, y: 6, z: 3 }, true);", + "}", + "register('MobBehaviorTests', 'phantoms_should_fly_from_cats', phantomsShouldFlyFromCats)", + " .structureName('gametests:glass_cells');", + "" + ] + }, + "minibiomes": { + "description": "Tests a roller coaster obstacle course. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-gametest/minecraft-server-gametest/register", + "prefix": ["mc"], + "body": [ + "// Note this code will run in experimental worlds with the Beta APIs experiment, and supporting GameTest structures.", + "function minibiomes(test: Test) {", + " const minecart = test.spawn(MinecraftEntityTypes.Minecart, { x: 9, y: 7, z: 7 });", + " const pig = test.spawn(MinecraftEntityTypes.Pig, { x: 9, y: 7, z: 7 });", + " test.setBlockType(MinecraftBlockTypes.Cobblestone, { x: 10, y: 7, z: 7 });", + " const minecartRideableComp = minecart.getComponent(EntityComponentTypes.Rideable);", + " minecartRideableComp?.addRider(pig);", + " test.succeedWhenEntityPresent(MinecraftEntityTypes.Pig, { x: 8, y: 3, z: 1 }, true);", + "}", + "register('ChallengeTests', 'minibiomes', minibiomes).structureName('gametests:minibiomes').maxTicks(160);", + "" + ] + } +} diff --git a/app/public/data/snippets/server-net-beta.json b/app/public/data/snippets/server-net-beta.json new file mode 100644 index 00000000..c1e1bd56 --- /dev/null +++ b/app/public/data/snippets/server-net-beta.json @@ -0,0 +1,19 @@ +{ + "updateScore": { + "description": "Updates score on a local server. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-net/httprequest", + "prefix": ["mc"], + "body": [ + "// Note this code will only run on Bedrock Dedicated Server.", + " const req = new HttpRequest('http://localhost:3000/updateScore');", + " req.body = JSON.stringify({", + " score: 22,", + " });", + " req.method = HttpRequestMethod.Post;", + " req.headers = [", + " new HttpHeader('Content-Type', 'application/json'),", + " new HttpHeader('auth', 'my-auth-token'),", + " ];", + " await http.request(req);" + ] + } +} diff --git a/app/public/data/snippets/server-ui-samples.json b/app/public/data/snippets/server-ui.json similarity index 87% rename from app/public/data/snippets/server-ui-samples.json rename to app/public/data/snippets/server-ui.json index 6e954f84..aa7f0d97 100644 --- a/app/public/data/snippets/server-ui-samples.json +++ b/app/public/data/snippets/server-ui.json @@ -1,6 +1,6 @@ { "showActionForm": { - "description": "Shows a very basic action form See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/actionformdata", + "description": "Shows a very basic action form. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/actionformdata", "prefix": ["mc"], "body": [" const playerList = world.getPlayers();", " if (playerList.length >= 1) {", @@ -23,7 +23,7 @@ " }" ]}, "showFavoriteMonth": { - "description": "Shows a dialog that lets a player pick their favorite month See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/actionformdata", + "description": "Shows a dialog that lets a player pick their favorite month. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/actionformdata", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " if (players.length >= 1) {", @@ -44,7 +44,7 @@ " }" ]}, "showBasicMessageForm": { - "description": "Shows an example two-button dialog See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/messageformdata", + "description": "Shows an example two-button dialog. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/messageformdata", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " const messageForm = new MessageFormData()", @@ -67,7 +67,7 @@ " });" ]}, "showTranslatedMessageForm": { - "description": "Shows an example translated two-button dialog dialog See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/messageformdata", + "description": "Shows an example translated two-button dialog dialog. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/messageformdata", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " const messageForm = new MessageFormData()", @@ -90,7 +90,7 @@ " });" ]}, "showBasicModalForm": { - "description": "Shows an example multiple-control modal dialog See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/modalformdata", + "description": "Shows an example multiple-control modal dialog. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/modalformdata", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " const modalForm = new ModalFormData().title('Example Modal Controls for §o§7ModalFormData§r');", diff --git a/app/public/data/snippets/server-samples.json b/app/public/data/snippets/server.json similarity index 59% rename from app/public/data/snippets/server-samples.json rename to app/public/data/snippets/server.json index 4b26f31e..2651870d 100644 --- a/app/public/data/snippets/server-samples.json +++ b/app/public/data/snippets/server.json @@ -1,6 +1,6 @@ { "buttonPushEvent": { - "description": "A simple button push before even See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/buttonpushaftereventsignal", + "description": "A simple button push before event. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/buttonpushaftereventsignal", "prefix": ["mc"], "body": [" // set up a button on cobblestone", " const cobblestone = targetLocation.dimension.getBlock(targetLocation);", @@ -23,7 +23,7 @@ " });" ]}, "leverActionEvent": { - "description": "A simple lever activate even See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/leveractionaftereventsignal", + "description": "A simple lever activate event. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/leveractionaftereventsignal", "prefix": ["mc"], "body": [" // set up a lever", " const cobblestone = targetLocation.dimension.getBlock(targetLocation);", @@ -48,7 +48,7 @@ " });" ]}, "tripWireTripEvent": { - "description": "A basic tripwire even See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/tripwiretripaftereventsignal", + "description": "A basic tripwire event. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/tripwiretripaftereventsignal", "prefix": ["mc"], "body": [" // set up a tripwire", " const redstone = targetLocation.dimension.getBlock({", @@ -75,7 +75,7 @@ " });" ]}, "addBlockColorCube": { - "description": "Creates a multicolored block out of different colors of wool See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blockpermutation/resolve", + "description": "Creates a multicolored block out of different colors of wool. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blockpermutation/resolve", "prefix": ["mc"], "body": [" const allWoolBlocks: string[] = [", " MinecraftBlockTypes.WhiteWool,", @@ -109,7 +109,7 @@ " }" ]}, "checkBlockTags": { - "description": "Checks whether a specified block is dirt, wood, or stone See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/block/hastag", + "description": "Checks whether a specified block is dirt, wood, or stone. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/block/hastag", "prefix": ["mc"], "body": [" // Fetch the block", " const block = targetLocation.dimension.getBlock(targetLocation);", @@ -121,17 +121,17 @@ " }" ]}, "containers": { - "description": "Creates a multicolored block out of different colors of wool See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/container", + "description": "Creates some chests and containers and uses container transfer and swapping APIs. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/container", "prefix": ["mc"], - "body": [" let xLocation = targetLocation; // left chest location", -" let xPlusTwoLocation = { x: targetLocation.x + 2, y: targetLocation.y, z: targetLocation.z }; // right chest", + "body": [" const xLocation = targetLocation; // left chest location", +" const xPlusTwoLocation = { x: targetLocation.x + 2, y: targetLocation.y, z: targetLocation.z }; // right chest", " const chestCart = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.ChestMinecart, {", " x: targetLocation.x + 4,", " y: targetLocation.y,", " z: targetLocation.z,", " });", -" let xChestBlock = targetLocation.dimension.getBlock(xLocation);", -" let xPlusTwoChestBlock = targetLocation.dimension.getBlock(xPlusTwoLocation);", +" const xChestBlock = targetLocation.dimension.getBlock(xLocation);", +" const xPlusTwoChestBlock = targetLocation.dimension.getBlock(xPlusTwoLocation);", " if (!xChestBlock || !xPlusTwoChestBlock) {", " log('Could not retrieve chest blocks.');", " return;", @@ -176,7 +176,7 @@ " }" ]}, "placeItemsInChest": { - "description": "Creates a multicolored block out of different colors of wool See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blockinventorycomponent", + "description": "Creates a chest and places some items within it. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blockinventorycomponent", "prefix": ["mc"], "body": [" // Fetch block", " const block = targetLocation.dimension.getBlock(targetLocation);", @@ -197,20 +197,20 @@ " inventoryContainer.setItem(0, new ItemStack(MinecraftItemTypes.Apple, 10));" ]}, "createExplosion": { - "description": "Creates an explosion in the world See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/createexplosion", + "description": "Creates an explosion in the world. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/createexplosion", "prefix": ["mc"], "body": [" log('Creating an explosion of radius 10.');", " targetLocation.dimension.createExplosion(targetLocation, 10);" ]}, "createNoBlockExplosion": { - "description": "Creates an explosion in the world that does not impact blocks See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/createexplosion", + "description": "Creates an explosion in the world that does not impact blocks. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/createexplosion", "prefix": ["mc"], "body": [" const explodeNoBlocksLoc = Vector3Utils.floor(Vector3Utils.add(targetLocation, { x: 1, y: 2, z: 1 }));", " log('Creating an explosion of radius 15 that does not break blocks.');", " targetLocation.dimension.createExplosion(explodeNoBlocksLoc, 15, { breaksBlocks: false });" ]}, "createExplosions": { - "description": "Creates a fire explosion and an underwater explosion in the world See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/createexplosion", + "description": "Creates a fire explosion and an underwater explosion in the world. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/createexplosion", "prefix": ["mc"], "body": [" const explosionLoc = Vector3Utils.add(targetLocation, { x: 0.5, y: 0.5, z: 0.5 });", " log('Creating an explosion of radius 15 that causes fire.');", @@ -220,7 +220,7 @@ " targetLocation.dimension.createExplosion(belowWaterLoc, 10, { allowUnderwater: true });" ]}, "itemStacks": { - "description": "Creates free-floating item stacks in the world See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/itemstack", + "description": "Creates free-floating item stacks in the world. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/itemstack", "prefix": ["mc"], "body": [" const oneItemLoc = { x: targetLocation.x + targetLocation.y + 3, y: 2, z: targetLocation.z + 1 };", " const fiveItemsLoc = { x: targetLocation.x + 1, y: targetLocation.y + 2, z: targetLocation.z + 1 };", @@ -236,7 +236,7 @@ " targetLocation.dimension.spawnItem(onePickaxe, diamondPickaxeLoc);" ]}, "quickFoxLazyDog": { - "description": "Creates a fox and, well, a wolf with effects applied See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/spawnentity", + "description": "Creates a fox and, well, a wolf with effects applied. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/spawnentity", "prefix": ["mc"], "body": [" const fox = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.Fox, {", " x: targetLocation.x + 1,", @@ -259,7 +259,7 @@ " log('Created a sneaking wolf.', 1);" ]}, "incrementDynamicProperty": { - "description": "Increments a dynamic numeric persisted property See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/world/getdynamicproperty", + "description": "Increments a dynamic numeric persisted property. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/world/getdynamicproperty", "prefix": ["mc"], "body": [" let number = world.getDynamicProperty('samplelibrary:number');", " log('Current value is: ' + number);", @@ -273,7 +273,7 @@ " world.setDynamicProperty('samplelibrary:number', number + 1);" ]}, "incrementDynamicPropertyInJsonBlob": { - "description": "Increments a dynamic numeric persisted property See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/world/getdynamicproperty", + "description": "Increments a dynamic numeric persisted property. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/world/getdynamicproperty", "prefix": ["mc"], "body": [" let paintStr = world.getDynamicProperty('samplelibrary:longerjson');", " let paint: { color: string; intensity: number } | undefined = undefined;", @@ -303,14 +303,22 @@ " paintStr = JSON.stringify(paint); // be very careful to ensure your serialized JSON str cannot exceed limits", " world.setDynamicProperty('samplelibrary:longerjson', paintStr);" ]}, +"spawnPoisonedVillager": { + "description": "Spawns a villager and gives it a poison effect. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/addeffect", + "prefix": ["mc"], + "body": [" const villagerType = 'minecraft:villager_v2';", +" const villager = targetLocation.dimension.spawnEntity(villagerType, targetLocation);", +" const duration = 20;", +" villager.addEffect(MinecraftEffectTypes.Poison, duration, { amplifier: 1 });" +]}, "triggerEvent": { - "description": "Creates a creeper and then triggers an explosion See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/spawnentity", + "description": "Creates a creeper and then triggers an explosion. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/spawnentity", "prefix": ["mc"], "body": [" const creeper = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.Creeper, targetLocation);", " creeper.triggerEvent('minecraft:start_exploding_forced');" ]}, "applyImpulse": { - "description": "Creates a zombie and then applies an impulse See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/applyimpulse", + "description": "Creates a zombie and then applies an impulse. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/applyimpulse", "prefix": ["mc"], "body": [" const zombie = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.Zombie, targetLocation);", " zombie.clearVelocity();", @@ -318,7 +326,7 @@ " zombie.applyImpulse({ x: 0, y: 0.5, z: 0 });" ]}, "getFireworkVelocity": { - "description": "Gets a velocity of a firewor See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/getvelocity", + "description": "Gets a velocity of a firework. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/getvelocity", "prefix": ["mc"], "body": [" const fireworkRocket = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.FireworksRocket, targetLocation);", " system.runTimeout(() => {", @@ -327,7 +335,7 @@ " }, 5);" ]}, "applyDamageThenHeal": { - "description": "Applies damage then heals an entity See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/applydamage", + "description": "Applies damage, then heals an entity. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/applydamage", "prefix": ["mc"], "body": [" const skelly = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.Skeleton, targetLocation);", " skelly.applyDamage(19); // skeletons have max damage of 20 so this is a near-death skeleton", @@ -339,7 +347,7 @@ " }, 20);" ]}, "setOnFire": { - "description": "Applies damage then heals an entity See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/setonfire", + "description": "Sets an entity on fire. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/setonfire", "prefix": ["mc"], "body": [" const skelly = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.Skeleton, targetLocation);", " skelly.setOnFire(20, true);", @@ -351,7 +359,7 @@ " }, 20);" ]}, "teleport": { - "description": "Does a basic teleport action See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/teleport", + "description": "Does a basic teleport action. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/teleport", "prefix": ["mc"], "body": [" const cow = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.Cow, targetLocation);", " system.runTimeout(() => {", @@ -364,7 +372,7 @@ " }, 20);" ]}, "teleportMovement": { - "description": "Does a basic movements with frequent teleport actions See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/teleport", + "description": "Does a basic movements with frequent teleport actions. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entity/teleport", "prefix": ["mc"], "body": [" const pig = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.Pig, targetLocation);", " let inc = 1;", @@ -381,8 +389,100 @@ " inc++;", " }, 4);" ]}, +"shootArrow": { + "description": "Shoots an arrow. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityprojectilecomponent", + "prefix": ["mc"], + "body": [" const velocity = { x: 0, y: 1, z: 5 };", +" const arrow = targetLocation.dimension.spawnEntity('minecraft:arrow', {", +" x: targetLocation.x,", +" y: targetLocation.y + 2,", +" z: targetLocation.z,", +" });", +" const projectileComp = arrow.getComponent('minecraft:projectile') as EntityProjectileComponent;", +" projectileComp?.shoot(velocity);" +]}, +"blockConditional": { + "description": "Spawns a salmon next to every fox, if the fox is standing on stone. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityqueryoptions", + "prefix": ["mc"], + "body": [" targetLocation.dimension", +" .getEntities({", +" type: 'fox',", +" })", +" .filter((entity) => {", +" const block = targetLocation.dimension.getBlock({", +" x: entity.location.x,", +" y: entity.location.y - 1,", +" z: entity.location.z,", +" });", +" return block !== undefined && block.matches('minecraft:stone');", +" })", +" .forEach((entity) => {", +" targetLocation.dimension.spawnEntity('salmon', entity.location);", +" });" +]}, +"findEntitiesHavingPropertyEqualsTo": { + "description": "Find entities having a property that is equals to a value. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityqueryoptions", + "prefix": ["mc"], + "body": [" // Minecraft bees have a has_nectar boolean property", +" const queryOption: EntityQueryOptions = {", +" propertyOptions: [{ propertyId: 'minecraft:has_nectar', value: { equals: true } }],", +" };", +" const entities = targetLocation.dimension.getEntities(queryOption);" +]}, +"playSoundChained": { + "description": "Plays a sound for every player, based on armor stands. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityqueryoptions", + "prefix": ["mc"], + "body": [" const targetPlayers = targetLocation.dimension.getPlayers();", +" const originEntities = targetLocation.dimension.getEntities({", +" type: 'armor_stand',", +" name: 'myArmorStand',", +" tags: ['dummyTag1'],", +" excludeTags: ['dummyTag2'],", +" });", +" originEntities.forEach((entity) => {", +" targetPlayers.forEach((player) => {", +" player.playSound('raid.horn');", +" });", +" });" +]}, +"setScoreboardChained": { + "description": "Sets a scoreboard, based on the presence of armor stands. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityqueryoptions", + "prefix": ["mc"], + "body": [" const objective = world.scoreboard.addObjective('scoreObjective1', 'dummy');", +" targetLocation.dimension", +" .getEntities({", +" type: 'armor_stand',", +" name: 'myArmorStand',", +" })", +" .forEach((entity) => {", +" if (entity.scoreboardIdentity !== undefined) {", +" objective.setScore(entity.scoreboardIdentity, -1);", +" }", +" });" +]}, +"summonMobChained": { + "description": "Summons a mob near every player, based on a number of armor stands. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityqueryoptions", + "prefix": ["mc"], + "body": [" const armorStandArray = targetLocation.dimension.getEntities({", +" type: 'armor_stand',", +" });", +" const playerArray = targetLocation.dimension.getPlayers({", +" location: { x: 0, y: -60, z: 0 },", +" closest: 4,", +" maxDistance: 15,", +" });", +" armorStandArray.forEach((entity) => {", +" playerArray.forEach((player) => {", +" targetLocation.dimension.spawnEntity('pig', {", +" x: player.location.x + 1,", +" y: player.location.y,", +" z: player.location.z,", +" });", +" });", +" });" +]}, "bounceSkeletons": { - "description": "Amongst a set of entities, uses entity query to find specific entities and bounce them with applyKnockback See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/getentities", + "description": "Amongst a set of entities, uses entity query to find specific entities and bounce them with applyKnockback. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/getentities", "prefix": ["mc"], "body": [" const mobs = ['creeper', 'skeleton', 'sheep'];", " // create some sample mob data", @@ -397,7 +497,7 @@ " }" ]}, "tagsQuery": { - "description": "Amongst a set of entities, uses entity query to find specific entities based on a tag See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/getentities", + "description": "Amongst a set of entities, uses entity query to find specific entities based on a tag. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/getentities", "prefix": ["mc"], "body": [" const mobs = ['creeper', 'skeleton', 'sheep'];", " // create some sample mob data", @@ -414,31 +514,34 @@ " }" ]}, "logEntitySpawnEvent": { - "description": "Registers and contains an entity spawned event handler See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityspawnaftereventsignal/subscribe", + "description": "Registers and contains an entity spawned event handler. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityspawnaftereventsignal/subscribe", "prefix": ["mc"], "body": [" // register a new function that is called when a new entity is created.", " world.afterEvents.entitySpawn.subscribe((entityEvent: EntitySpawnAfterEvent) => {", " if (entityEvent && entityEvent.entity) {", -" log(`New entity of type '${entityEvent.entity.typeId}' created!`, 1);", +" log(`New entity of type ${entityEvent.entity.typeId} created!`, 1);", " } else {", -" log(`The entity event didn't work as expected.`, -1);", +" log(`The entity event did not work as expected.`, -1);", " }", " });", " system.runTimeout(() => {", -" spawnAdultHorse(log, targetLocation);", +" targetLocation.dimension.spawnEntity(", +" 'minecraft:horse',", +" Vector3Utils.add(targetLocation, { x: 0, y: 1, z: 0 })", +" );", " }, 20);" ]}, "spawnAdultHorse": { - "description": "A simple function to create a horse See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/spawnentity", + "description": "A simple function to create an adult horse. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/spawnentity", "prefix": ["mc"], - "body": [" log('Create a horse and triggering the 'ageable_grow_up' event, ensuring the horse is created as an adult');", + "body": [" log('Create a horse and triggering the ageable_grow_up event, ensuring the horse is created as an adult');", " targetLocation.dimension.spawnEntity(", " 'minecraft:horse',", " Vector3Utils.add(targetLocation, { x: 0, y: 1, z: 0 })", " );" ]}, "givePlayerElytra": { - "description": "Give a player elytra See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityequipmentinventorycomponent", + "description": "Gives a player an elytra. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityequipmentinventorycomponent", "prefix": ["mc"], "body": [" const players = world.getAllPlayers();", " const equipment = players[0].getComponent(EntityComponentTypes.Equippable) as EntityEquippableComponent;", @@ -446,7 +549,7 @@ " log('Player given Elytra');" ]}, "givePlayerEquipment": { - "description": "Give a player, and an armorstand, a full set of equipment See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/itemstack", + "description": "Give a player, and an armorstand, a full set of equipment. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/itemstack", "prefix": ["mc"], "body": [" const players = world.getAllPlayers();", " const armorStandLoc = { x: targetLocation.x, y: targetLocation.y, z: targetLocation.z + 4 };", @@ -470,8 +573,132 @@ " equipmentCompArmorStand.setEquipment(EquipmentSlot.Offhand, new ItemStack(MinecraftItemTypes.Shield));", " }" ]}, +"giveHurtDiamondSword": { + "description": "Gives a player a half-damaged diamond sword. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/itemstack/getcomponent", + "prefix": ["mc"], + "body": [" const hurtDiamondSword = new ItemStack(MinecraftItemTypes.DiamondSword);", +" const durabilityComponent = hurtDiamondSword.getComponent(ItemComponentTypes.Durability) as ItemDurabilityComponent;", +" if (durabilityComponent !== undefined) {", +" durabilityComponent.damage = durabilityComponent.maxDurability / 2;", +" }", +" for (const player of world.getAllPlayers()) {", +" const inventory = player.getComponent(EntityComponentTypes.Inventory) as EntityInventoryComponent;", +" if (inventory && inventory.container) {", +" inventory.container.addItem(hurtDiamondSword);", +" }", +" }" +]}, +"giveDestroyRestrictedPickaxe": { + "description": "Gives a player a restricted pickaxe. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/itemstack/setcandestroy", + "prefix": ["mc"], + "body": [" for (const player of world.getAllPlayers()) {", +" const specialPickaxe = new ItemStack(MinecraftItemTypes.DiamondPickaxe);", +" specialPickaxe.setCanDestroy([MinecraftItemTypes.Cobblestone, MinecraftItemTypes.Obsidian]);", +" const inventory = player.getComponent('inventory') as EntityInventoryComponent;", +" if (inventory === undefined || inventory.container === undefined) {", +" return;", +" }", +" inventory.container.addItem(specialPickaxe);", +" }" +]}, +"givePlaceRestrictedGoldBlock": { + "description": "Gives a player a restricted gold block. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/itemstack/setcanplaceon", + "prefix": ["mc"], + "body": [" for (const player of world.getAllPlayers()) {", +" const specialGoldBlock = new ItemStack(MinecraftItemTypes.GoldBlock);", +" specialGoldBlock.setCanPlaceOn([MinecraftItemTypes.GrassBlock, MinecraftItemTypes.Dirt]);", +" const inventory = player.getComponent(EntityComponentTypes.Inventory) as EntityInventoryComponent;", +" if (inventory === undefined || inventory.container === undefined) {", +" return;", +" }", +" inventory.container.addItem(specialGoldBlock);", +" }" +]}, +"diamondAwesomeSword": { + "description": "Gives a player a diamond sword with custom lore text. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/itemstack/addlore", + "prefix": ["mc"], + "body": [" for (const player of world.getAllPlayers()) {", +" const diamondAwesomeSword = new ItemStack(MinecraftItemTypes.DiamondSword, 1);", +" diamondAwesomeSword.setLore(['§c§lDiamond Sword of Awesome§r', '+10 coolness', '§p+4 shiny§r']);", +" // hover over/select the item in your inventory to see the lore.", +" const inventory = player.getComponent(EntityComponentTypes.Inventory) as EntityInventoryComponent;", +" if (inventory === undefined || inventory.container === undefined) {", +" return;", +" }", +" inventory.container.setItem(0, diamondAwesomeSword);", +" }" +]}, +"getFirstHotbarItem": { + "description": "Gets the first hotbar item. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/container/getitem", + "prefix": ["mc"], + "body": [" for (const player of world.getAllPlayers()) {", +" const inventory = player.getComponent(EntityInventoryComponent.componentId) as EntityInventoryComponent;", +" if (inventory && inventory.container) {", +" const firstItem = inventory.container.getItem(0);", +" if (firstItem) {", +" log('First item in hotbar is: ' + firstItem.typeId);", +" }", +" return inventory.container.getItem(0);", +" }", +" return undefined;", +" }" +]}, +"moveBetweenContainers": { + "description": "Move an item between containers. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/container/moveitem", + "prefix": ["mc"], + "body": [" const players = world.getAllPlayers();", +" const chestCart = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.ChestMinecart, {", +" x: targetLocation.x + 1,", +" y: targetLocation.y,", +" z: targetLocation.z,", +" });", +" if (players.length > 0) {", +" const fromPlayer = players[0];", +" const fromInventory = fromPlayer.getComponent(EntityComponentTypes.Inventory) as EntityInventoryComponent;", +" const toInventory = chestCart.getComponent(EntityComponentTypes.Inventory) as EntityInventoryComponent;", +" if (fromInventory && toInventory && fromInventory.container && toInventory.container) {", +" fromInventory.container.moveItem(0, 0, toInventory.container);", +" }", +" }" +]}, +"swapBetweenContainers": { + "description": "Swap an item between containers. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/container/swapitem", + "prefix": ["mc"], + "body": [" const players = world.getAllPlayers();", +" const chestCart = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.ChestMinecart, {", +" x: targetLocation.x + 1,", +" y: targetLocation.y,", +" z: targetLocation.z,", +" });", +" if (players.length > 0) {", +" const fromPlayer = players[0];", +" const fromInventory = fromPlayer.getComponent(EntityComponentTypes.Inventory) as EntityInventoryComponent;", +" const toInventory = chestCart.getComponent(EntityComponentTypes.Inventory) as EntityInventoryComponent;", +" if (fromInventory && toInventory && fromInventory.container && toInventory.container) {", +" fromInventory.container.swapItems(0, 0, toInventory.container);", +" }", +" }" +]}, +"transferBetweenContainers": { + "description": "Transfer an item between containers. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/container/transferitem", + "prefix": ["mc"], + "body": [" const players = world.getAllPlayers();", +" const chestCart = targetLocation.dimension.spawnEntity(MinecraftEntityTypes.ChestMinecart, {", +" x: targetLocation.x + 1,", +" y: targetLocation.y,", +" z: targetLocation.z,", +" });", +" if (players.length > 0) {", +" const fromPlayer = players[0];", +" const fromInventory = fromPlayer.getComponent(EntityComponentTypes.Inventory) as EntityInventoryComponent;", +" const toInventory = chestCart.getComponent(EntityComponentTypes.Inventory) as EntityInventoryComponent;", +" if (fromInventory && toInventory && fromInventory.container && toInventory.container) {", +" fromInventory.container.transferItem(0, toInventory.container);", +" }", +" }" +]}, "playMusicAndSound": { - "description": "Plays some music and sound effects See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/world/playmusic", + "description": "Plays some music and sound effects. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/world/playmusic", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " const musicOptions: MusicOptions = {", @@ -492,7 +719,7 @@ " players[0].playSound('bucket.fill_water', playerSoundOptions);" ]}, "spawnParticle": { - "description": "Spawns a cloud of colored flame particles See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/spawnparticle", + "description": "Spawns a cloud of colored flame particles. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/dimension/spawnparticle", "prefix": ["mc"], "body": [" for (let i = 0; i < 100; i++) {", " const molang = new MolangVariableMap();", @@ -506,7 +733,7 @@ " }" ]}, "pistonAfterEvent": { - "description": "A simple piston after activate even See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/pistonactivateaftereventsignal/subscribe", + "description": "A simple piston after activate event. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/pistonactivateaftereventsignal/subscribe", "prefix": ["mc"], "body": [" // set up a couple of piston blocks", " const piston = targetLocation.dimension.getBlock(targetLocation);", @@ -536,8 +763,30 @@ " }", " });" ]}, +"sendPlayerMessages": { + "description": "Sends player a number of diverse message types. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/player/sendmessage", + "prefix": ["mc"], + "body": [" for (const player of world.getAllPlayers()) {", +" // Displays 'First or Second'", +" const rawMessage = { translate: 'accessibility.list.or.two', with: ['First', 'Second'] };", +" player.sendMessage(rawMessage);", +" // Displays 'Hello, world!'", +" player.sendMessage('Hello, world!');", +" // Displays 'Welcome, Amazing Player 1!'", +" player.sendMessage({ translate: 'authentication.welcome', with: ['Amazing Player 1'] });", +" // Displays the player's score for objective 'obj'. Each player will see their own score.", +" const rawMessageWithScore = { score: { name: '*', objective: 'obj' } };", +" player.sendMessage(rawMessageWithScore);", +" // Displays 'Apple or Coal'", +" const rawMessageWithNestedTranslations = {", +" translate: 'accessibility.list.or.two',", +" with: { rawtext: [{ translate: 'item.apple.name' }, { translate: 'item.coal.name' }] },", +" };", +" player.sendMessage(rawMessageWithNestedTranslations);", +" }" +]}, "updateScoreboard": { - "description": "Creates and updates a scoreboard objective, plus a player score See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/scoreboard", + "description": "Creates and updates a scoreboard objective, plus a player score. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/scoreboard", "prefix": ["mc"], "body": [" const scoreboardObjectiveId = 'scoreboard_demo_objective';", " const scoreboardObjectiveDisplayName = 'Demo Objective';", @@ -564,7 +813,7 @@ " objective.setScore(player0Identity, playerScore + 10);" ]}, "setTitle": { - "description": "Sets a title overlay on the player's scree See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/screendisplay", + "description": "Sets a title overlay on the player's screen. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/screendisplay", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " if (players.length > 0) {", @@ -572,7 +821,7 @@ " }" ]}, "setTitleAndSubtitle": { - "description": "Sets a title and subtitle overlay on the player's scree See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/screendisplay", + "description": "Sets a title and subtitle overlay on the player's screen. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/screendisplay", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " players[0].onScreenDisplay.setTitle('Chapter 1', {", @@ -583,7 +832,7 @@ " });" ]}, "countdown": { - "description": "Runs a countdown from 10 to 0 See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/screendisplay", + "description": "Runs a countdown from 10 to 0. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/screendisplay", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " players[0].onScreenDisplay.setTitle('Get ready!', {", @@ -602,19 +851,36 @@ " }, 20);" ]}, "sendBasicMessage": { - "description": "Sends a basic message See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/player/sendmessage", + "description": "Sends a basic message. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/player/sendmessage", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " players[0].sendMessage('Hello World!');" ]}, "sendTranslatedMessage": { - "description": "Sends a translated message See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/player/sendmessage", + "description": "Sends a translated message. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/player/sendmessage", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " players[0].sendMessage({ translate: 'authentication.welcome', with: ['Amazing Player 1'] });" ]}, +"nestedTranslation": { + "description": "Sends a message with nested translation. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/player/sendmessage", + "prefix": ["mc"], + "body": [" // Displays 'Apple or Coal'", +" const rawMessage = {", +" translate: 'accessibility.list.or.two',", +" with: { rawtext: [{ translate: 'item.apple.name' }, { translate: 'item.coal.name' }] },", +" };", +" world.sendMessage(rawMessage);" +]}, +"scoreWildcard": { + "description": "Sends a message with a wildcard score. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/player/sendmessage", + "prefix": ["mc"], + "body": [" // Displays the player's score for objective 'obj'. Each player will see their own score.", +" const rawMessage = { score: { name: '*', objective: 'obj' } };", +" world.sendMessage(rawMessage);" +]}, "showTranslatedMessageForm": { - "description": "Shows an example translated two-button dialog dialog See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/rawmessage", + "description": "Shows an example translated two-button dialog dialog. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/rawmessage", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " const messageForm = new MessageFormData()", @@ -637,7 +903,7 @@ " });" ]}, "addSign": { - "description": "Creates a single-sided simple sig See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blocksigncomponent", + "description": "Creates a single-sided simple sign. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blocksigncomponent", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " const dim = players[0].dimension;", @@ -652,7 +918,7 @@ " signComponent?.setText(`Basic sign!/nThis is green on the front.`);" ]}, "addTranslatedSign": { - "description": "Creates a single-sided simple sig See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blockpermutation", + "description": "Creates a single-sided simple sign. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blockpermutation", "prefix": ["mc"], "body": [" const players = world.getPlayers();", " const dim = players[0].dimension;", @@ -667,7 +933,7 @@ " signComponent?.setText({ translate: 'item.skull.player.name', with: [players[0].name] });" ]}, "addTwoSidedSign": { - "description": "Creates a two-sided sign with custom colors and a read-only statu See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blocksigncomponent", + "description": "Creates a two-sided sign with custom colors and a read-only status. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blocksigncomponent", "prefix": ["mc"], "body": [" const signBlock = targetLocation.dimension.getBlock(targetLocation);", " if (!signBlock) {", @@ -688,15 +954,37 @@ " log('Could not find sign component.');", " }" ]}, +"updateSignText": { + "description": "Updates sign text. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/blocksigncomponent", + "prefix": ["mc"], + "body": [" const block = targetLocation.dimension.getBlock(targetLocation);", +" if (!block) {", +" console.warn('Could not find a block at specified location.');", +" return;", +" }", +" const sign = block.getComponent(BlockComponentTypes.Sign) as BlockSignComponent;", +" if (sign) {", +" // RawMessage", +" const helloWorldMessage: RawMessage = { text: 'Hello World' };", +" sign.setText(helloWorldMessage);", +" // RawText", +" const helloWorldText: RawText = { rawtext: [{ text: 'Hello World' }] };", +" sign.setText(helloWorldText);", +" // Regular string", +" sign.setText('Hello World');", +" } else {", +" console.warn('Could not find a sign component on the block.');", +" }" +]}, "spawnFeatherItem": { - "description": "Creates a free-floating feather item in the world See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/itemstack", + "description": "Creates a free-floating feather item in the world. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/itemstack", "prefix": ["mc"], "body": [" const featherItem = new ItemStack(MinecraftItemTypes.Feather, 1);", " targetLocation.dimension.spawnItem(featherItem, targetLocation);", " log(`New feather created at ${targetLocation.x}, ${targetLocation.y}, ${targetLocation.z}!`);" ]}, "testThatEntityIsFeatherItem": { - "description": "Tests whether there is a feather nearby a spot See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityitemcomponent", + "description": "Tests whether there is a feather nearby a spot. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/entityitemcomponent", "prefix": ["mc"], "body": [" const items = targetLocation.dimension.getEntities({", " location: targetLocation,", @@ -712,7 +1000,7 @@ " }" ]}, "trapTick": { - "description": "A simple tick timer that runs a command every minute See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/system/run", + "description": "A simple tick timer that runs a command every minute. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/system/run", "prefix": ["mc"], "body": [" try {", " // Minecraft runs at 20 ticks per second.", @@ -725,11 +1013,17 @@ " system.run(trapTick);" ]}, "every30Seconds": { - "description": "An alternate interval timer that runs a command every 30 seconds See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/system/runinterval", + "description": "An alternate interval timer that runs a command every 30 seconds. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/system/runinterval", "prefix": ["mc"], "body": [" const intervalRunIdentifier = Math.floor(Math.random() * 10000);", " system.runInterval(() => {", " world.sendMessage('This is an interval run ' + intervalRunIdentifier + ' sending a message every 30 seconds.');", " }, 600);" +]}, +"cubeGenerator": { + "description": "Uses a generator function to, over the span of multiple ticks, provision blocks in a cube. See https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/system/runjob", + "prefix": ["mc"], + "body": [" const blockPerm = BlockPermutation.resolve('minecraft:cobblestone');", +" system.runJob(blockPlacingGenerator(blockPerm, targetLocation, 15));" ]} } \ No newline at end of file diff --git a/app/src/UX/App.tsx b/app/src/UX/App.tsx index 4b5897a5..81e44425 100644 --- a/app/src/UX/App.tsx +++ b/app/src/UX/App.tsx @@ -13,7 +13,6 @@ import CartoApp from "../app/CartoApp"; import StorageUtilities from "../storage/StorageUtilities"; import { ThemeInput } from "@fluentui/react-northstar"; import { CartoEditorViewMode } from "../app/ICartoData"; -import MCWorld from "../minecraft/MCWorld"; import ProjectItem from "../app/ProjectItem"; import ZipStorage from "../storage/ZipStorage"; import ProjectUtilities from "../app/ProjectUtilities"; @@ -22,6 +21,7 @@ import WebUtilities from "./WebUtilities"; import ProjectEditorUtilities, { ProjectEditorMode } from "./ProjectEditorUtilities"; import HttpStorage from "../storage/HttpStorage"; import { ProjectImportExclusions } from "../app/ProjectExporter"; +import Database from "../minecraft/Database"; export enum NewProjectTemplateType { empty, @@ -43,7 +43,6 @@ interface AppProps { } interface AppState { - carto?: Carto; mode: AppMode; isPersisted?: boolean; errorMessage?: string; @@ -86,75 +85,10 @@ export default class App extends Component { this._tick = this._tick.bind(this); - if (this.props.fileContentRetriever) { - this.props.fileContentRetriever(this._getFileContent); - } - - if (this.props.saveAllRetriever) { - this.props.saveAllRetriever(this._saveAll); - } - - if (CartoApp.carto === undefined) { - this.state = { - carto: undefined, - mode: AppMode.loading, - activeProject: null, - }; - - CartoApp.onInitialized.subscribe(this._handleCartoInit); - - // for a potential race condition where carto gets set right in between - // initting of the state and registering the event. - if (CartoApp.carto !== undefined) { - this.state = { - carto: CartoApp.carto, - mode: AppMode.home, - activeProject: null, - }; - } - } else { - const stateFromUrl = this._getStateFromUrl(); - let initialAppMode = AppMode.home; - - if (stateFromUrl) { - initialAppMode = stateFromUrl.mode; - } else if (CartoApp.initialMode) { - const mode = this._getModeFromString(CartoApp.initialMode); - - if (mode) { - initialAppMode = mode; - } - } - - let selectedItem = undefined; - - if (CartoApp.modeParameter && CartoApp.modeParameter.startsWith("project/")) { - const segments = CartoApp.modeParameter.split("/"); - - if (segments.length === 2) { - this._handleNewProject("Project", NewProjectTemplateType.gameTest); - - selectedItem = segments[1]; - } - } else if ( - CartoApp.initialMode && - (CartoApp.modeParameter || CartoApp.initialMode === "info") && - CartoApp.projectPath - ) { - this._loadLocalStorageProject(); - } - - this.state = { - carto: CartoApp.carto, - mode: initialAppMode, - selectedItem: selectedItem, - activeProject: null, - }; - - if (!CartoApp.carto.isLoaded) { - this.loadCarto(CartoApp.carto); - } - } + this.state = { + mode: AppMode.loading, + activeProject: null, + }; } public async _saveAll() { @@ -168,19 +102,13 @@ export default class App extends Component { } public async _loadLocalStorageProject() { - let carto = this.state?.carto; - - if (!carto) { - carto = CartoApp.carto; - } - - if (!carto) { + if (CartoApp.carto === undefined || !CartoApp.carto.isLoaded) { return; } let newProject = undefined; - newProject = await carto.ensureProjectFromLocalStoragePath(CartoApp.projectPath); + newProject = await CartoApp.carto.ensureProjectFromLocalStoragePath(CartoApp.projectPath); if (newProject) { let mode = this._getModeFromString(CartoApp.initialMode); @@ -201,7 +129,6 @@ export default class App extends Component { if (this.state) { const newState = { - carto: carto, mode: mode, isPersisted: this.state.isPersisted, activeProject: newProject, @@ -260,7 +187,6 @@ export default class App extends Component { } this.setState({ - carto: this.state.carto, mode: this.state.mode, isPersisted: this.state.isPersisted, loadingMessage: this.state.loadingMessage, @@ -274,11 +200,10 @@ export default class App extends Component { } private _handleHashChange() { - const result = this._getStateFromUrl(); + const result = this._getStateFromUrlWithSideEffects(false); if (result && this._isMountedInternal) { this.setState({ - carto: this.state.carto, mode: result.mode, isPersisted: this.state.isPersisted, loadingMessage: this.state.loadingMessage, @@ -305,7 +230,8 @@ export default class App extends Component { const isPersisted = await WebUtilities.getIsPersisted(); - const newState = this._getStateFromUrl(); + const newState = this._getStateFromUrlWithSideEffects(); + let nextMode = this.state.mode; if (newState) { @@ -316,23 +242,16 @@ export default class App extends Component { this._updateWindowTitle(nextMode, this.state.activeProject); - const newComponentState = { - carto: CartoApp.carto, + this.setState({ mode: nextMode, isPersisted: isPersisted, activeProject: this.state.activeProject, hasBanner: this.state.hasBanner, visualSeed: this.state.visualSeed, - }; - - if (this._isMountedInternal) { - this.setState(newComponentState); - } else { - this.state = newComponentState; - } + }); } - private _getStateFromUrl(): AppState | undefined { + private _getStateFromUrlWithSideEffects(dontProcessQueryStrings?: boolean): AppState | undefined { const hash = window.location.hash; const query = window.location.search; const queryVals: { [path: string]: string } = {}; @@ -356,7 +275,7 @@ export default class App extends Component { } } - if (queryVals["open"] !== undefined || queryVals["view"] !== undefined) { + if (!dontProcessQueryStrings && (queryVals["open"] !== undefined || queryVals["view"] !== undefined)) { let openQuery = queryVals["view"]; const updateContent = queryVals["updates"]; @@ -442,7 +361,6 @@ export default class App extends Component { setHomeWithError(errorMessage: string) { this.setState({ - carto: CartoApp.carto, mode: AppMode.home, activeProject: null, isPersisted: this.state.isPersisted, @@ -454,10 +372,64 @@ export default class App extends Component { } componentDidMount() { - if (typeof window !== "undefined") { - window.addEventListener("hashchange", this._handleHashChange, false); - window.addEventListener("resize", this._incrementVisualSeed, false); - this._intervalId = window.setInterval(this._tick, 50); + if (!this._isMountedInternal) { + if (this.props.fileContentRetriever) { + this.props.fileContentRetriever(this._getFileContent); + } + + if (this.props.saveAllRetriever) { + this.props.saveAllRetriever(this._saveAll); + } + + if (CartoApp.carto && !CartoApp.carto.isLoaded) { + CartoApp.onInitialized.subscribe(this._handleCartoInit); + + this.loadCarto(CartoApp.carto); + } else { + const stateFromUrl = this._getStateFromUrlWithSideEffects(); + + let initialAppMode = AppMode.home; + + if (stateFromUrl) { + initialAppMode = stateFromUrl.mode; + } else if (CartoApp.initialMode) { + const mode = this._getModeFromString(CartoApp.initialMode); + + if (mode) { + initialAppMode = mode; + } + } + + let selectedItem = undefined; + + if (CartoApp.modeParameter && CartoApp.modeParameter.startsWith("project/")) { + const segments = CartoApp.modeParameter.split("/"); + + if (segments.length === 2) { + this._handleNewProject("Project", NewProjectTemplateType.gameTest); + + selectedItem = segments[1]; + } + } else if ( + CartoApp.initialMode && + (CartoApp.modeParameter || CartoApp.initialMode === "info") && + CartoApp.projectPath + ) { + this._loadLocalStorageProject(); + } + + this.setState({ + mode: initialAppMode, + selectedItem: selectedItem, + activeProject: null, + }); + } + + if (typeof window !== "undefined") { + window.addEventListener("hashchange", this._handleHashChange, false); + window.addEventListener("resize", this._incrementVisualSeed, false); + this._intervalId = window.setInterval(this._tick, 50); + } } this._isMountedInternal = true; @@ -473,14 +445,7 @@ export default class App extends Component { } private async _doLog(message: string) { - let carto = this.state?.carto; - - if (!carto) { - carto = CartoApp.carto; - } - this.setState({ - carto: carto, isPersisted: this.state.isPersisted, mode: AppMode.loading, hasBanner: this.state.hasBanner, @@ -500,13 +465,7 @@ export default class App extends Component { editorStartMode?: ProjectEditorMode, startInReadOnly?: boolean ) { - let carto = this.state?.carto; - - if (!carto) { - carto = CartoApp.carto; - } - - if (!carto) { + if (CartoApp.carto === undefined || !CartoApp.carto.isLoaded) { return; } @@ -526,7 +485,7 @@ export default class App extends Component { } if (newProjectPath === undefined) { - newProject = await carto.createNewProject( + newProject = await CartoApp.carto.createNewProject( newProjectName, newProjectPath, focus, @@ -534,7 +493,7 @@ export default class App extends Component { ProjectScriptLanguage.typeScript ); } else { - newProject = await carto.ensureProjectForFolder(newProjectPath, newProjectName, false); + newProject = await CartoApp.carto.ensureProjectForFolder(newProjectPath, newProjectName, false); await newProject.ensureProjectFolder(); @@ -548,7 +507,7 @@ export default class App extends Component { } await newProject.save(true); - await carto.save(); + await CartoApp.carto.save(); this._updateWindowTitle(AppMode.project, newProject); @@ -566,7 +525,6 @@ export default class App extends Component { if (this.state && this._isMountedInternal) { this.setState({ - carto: carto, mode: nextMode, isPersisted: this.state.isPersisted, activeProject: newProject, @@ -579,14 +537,15 @@ export default class App extends Component { } private async _handleNewProjectFromFolder(folderPath: string) { - if (this.state.carto === undefined) { + if (CartoApp.carto === undefined || !CartoApp.carto.isLoaded) { return; } - const newProject = await this.state.carto.ensureProjectForFolder(folderPath); + const newProject = await CartoApp.carto.ensureProjectForFolder(folderPath); newProject.save(); - this.state.carto.save(); + + CartoApp.carto.save(); this._setProject(newProject); } @@ -605,11 +564,11 @@ export default class App extends Component { } private async _handleNewProjectFromFolderInstance(folder: IFolder, name?: string, isDocumentationProject?: boolean) { - if (this.state.carto === undefined) { + if (CartoApp.carto === undefined || !CartoApp.carto.isLoaded) { return; } - const newProject = new Project(this.state.carto, name ? name : folder.name, null); + const newProject = new Project(CartoApp.carto, name ? name : folder.name, null); newProject.setProjectFolder(folder); @@ -620,20 +579,20 @@ export default class App extends Component { await newProject.inferProjectItemsFromFiles(); newProject.save(); - this.state.carto.save(); + CartoApp.carto.save(); this._setProject(newProject); } private async _ensureProjectFromGalleryId(galleryId: string, updateContent?: string) { - if (this.state.carto === undefined) { + if (CartoApp.carto === undefined || !CartoApp.carto.isLoaded) { return; } - const gp = await this.state.carto.getGalleryProjectById(galleryId); + const gp = await CartoApp.carto.getGalleryProjectById(galleryId); if (gp === undefined) { - this.setHomeWithError("We could not find a gallery project with an identifier of '" + galleryId + "' to open."); + this.setHomeWithError("We could not find a starter/sample named '" + galleryId + "' that could be opened."); return; } @@ -641,7 +600,7 @@ export default class App extends Component { } private async _ensureProjectFromGallery(project: IGalleryItem, updateContent?: string) { - if (this.state === null || this.state.carto === undefined) { + if (CartoApp.carto === undefined || !CartoApp.carto.isLoaded) { return; } @@ -654,6 +613,7 @@ export default class App extends Component { project.gitHubFolder, project.fileList, project.id, + project.sampleSet, project.type === GalleryItemType.codeSample ? project.id : undefined, updateContent ); @@ -668,13 +628,19 @@ export default class App extends Component { gitHubFolder?: string, fileList?: string[], projectId?: string, + sampleSet?: string, sampleId?: string, updateContent?: string, description?: string ) { const carto = CartoApp.carto; - if (this.state === null || carto === undefined) { + if ( + this.state === null || + carto === undefined || + this._loadingMessage !== undefined || + (window.document.title && window.document.title.indexOf("Loading") >= 0) + ) { return; } @@ -742,6 +708,7 @@ export default class App extends Component { gitHubFolder, fileList, projectId, + sampleSet, sampleId, updateContent, description @@ -749,34 +716,35 @@ export default class App extends Component { } private async _newProjectFromGallery( - project: IGalleryItem, + galleryItem: IGalleryItem, name?: string, creator?: string, shortName?: string, description?: string ) { - if (this.state === null || this.state.carto === undefined) { + if (CartoApp.carto === undefined || !CartoApp.carto.isLoaded) { return; } this._newProjectFromGitHubTemplate( - project.title, - project.gitHubOwner, - project.gitHubRepoName, + galleryItem.title, + galleryItem.gitHubOwner, + galleryItem.gitHubRepoName, false, - project.gitHubBranch, - project.gitHubFolder, - project.fileList, - project.id, - project.type === GalleryItemType.codeSample || project.type === GalleryItemType.editorCodeSample - ? project.id + galleryItem.gitHubBranch, + galleryItem.gitHubFolder, + galleryItem.fileList, + galleryItem.id, + galleryItem.sampleSet, + galleryItem.type === GalleryItemType.codeSample || galleryItem.type === GalleryItemType.editorCodeSample + ? galleryItem.id : undefined, undefined, name, creator, shortName, description, - project.type + galleryItem.type ); } @@ -789,6 +757,7 @@ export default class App extends Component { gitHubFolder?: string, fileList?: string[], galleryId?: string, + sampleSet?: string, sampleId?: string, updateContent?: string, suggestedName?: string, @@ -886,7 +855,6 @@ export default class App extends Component { ); } catch (e: any) { this.setState({ - carto: this.state.carto, mode: AppMode.home, activeProject: this.state.activeProject, selectedItem: this.state.selectedItem, @@ -922,8 +890,8 @@ export default class App extends Component { } } - if (sampleId !== undefined) { - const snippet = ProjectUtilities.getSnippet(sampleId); + if (sampleSet !== undefined && sampleId !== undefined) { + const snippet = await Database.getSnippet(sampleSet, sampleId); Log.assertDefined(snippet, "Snippet " + sampleId + " could not be found."); @@ -977,7 +945,6 @@ export default class App extends Component { private async _handlePersistenceUpgraded() { this.setState({ mode: this.state.mode, - carto: this.state.carto, isPersisted: true, activeProject: this.state.activeProject, selectedItem: this.state.selectedItem, @@ -997,7 +964,6 @@ export default class App extends Component { this.setState({ mode: this.state.mode, - carto: this.state.carto, isPersisted: this.state.isPersisted, activeProject: this.state.activeProject, selectedItem: this.state.selectedItem, @@ -1008,35 +974,6 @@ export default class App extends Component { }); } - private async _newProjectFromMinecraftFolder(folderType: LocalFolderType, folder: IFolder) { - if (this.state === null || this.state.carto === undefined) { - return; - } - - let proposedProjectName = StorageUtilities.getBaseFromName(folder.fullPath); - const mcw = await MCWorld.ensureMCWorldOnFolder(folder); - - if (mcw && mcw.name) { - proposedProjectName = mcw.name; - } - - const newProject = await this.state.carto.ensureProjectForFolder(folder.fullPath, proposedProjectName); - - newProject.save(); - this.state.carto.save(); - - this._updateWindowTitle(AppMode.project, newProject); - this.initProject(newProject); - - this.setState({ - mode: AppMode.project, - isPersisted: this.state.isPersisted, - hasBanner: this.state.hasBanner, - visualSeed: this.state.visualSeed, - activeProject: newProject, - }); - } - private async _ensureProjectFromMinecraftFolder(folderType: LocalFolderType, folder: IFolder, isReadOnly: boolean) { const carto = CartoApp.carto; @@ -1186,7 +1123,6 @@ export default class App extends Component { if (cbElt?.hasChildNodes()) { if (this.state && !this.state.hasBanner) { this.setState({ - carto: this.state.carto, mode: this.state.mode, isPersisted: this.state.isPersisted, loadingMessage: this.state.loadingMessage, @@ -1200,7 +1136,6 @@ export default class App extends Component { } } else if (this.state && this.state.hasBanner) { this.setState({ - carto: this.state.carto, mode: this.state.mode, isPersisted: this.state.isPersisted, loadingMessage: this.state.loadingMessage, @@ -1267,10 +1202,6 @@ export default class App extends Component { render() { let interior = <>; - if (this.state.carto === undefined) { - return
Loading...
; - } - let isReadOnly = false; if (this.state.mode === AppMode.projectReadOnly) { @@ -1293,7 +1224,15 @@ export default class App extends Component { heightOffset = bannerHeight; } - if (this.state.mode === AppMode.loading) { + if (CartoApp.carto === undefined || !CartoApp.carto.isLoaded) { + interior = ( +
+
+ Loading... +
+
+ ); + } else if (this.state.mode === AppMode.loading) { let message = "loading..."; let additionalLoadingMessage = ""; @@ -1319,8 +1258,8 @@ export default class App extends Component { } else if (this.state.mode === AppMode.home) { interior = ( { } else if (this.state.activeProject !== null && CartoApp.initialMode === "projectitem") { interior = ( { } else if (this.state.activeProject !== null && CartoApp.initialMode === "info") { interior = ( { interior = ( { // show main view (no sidebar) if it's a code sample. interior = ( { } else { interior = ( ; +} + +interface IBlockTypeComponentSetEditorState { + loadedFormCount?: number; + activeComponentId: string | undefined; +} + +export default class BlockTypeComponentSetEditor extends Component< + IBlockTypeComponentSetEditorProps, + IBlockTypeComponentSetEditorState +> { + constructor(props: IBlockTypeComponentSetEditorProps) { + super(props); + + this._addComponentClick = this._addComponentClick.bind(this); + this._addComponent = this._addComponent.bind(this); + this._handleCloseClick = this._handleCloseClick.bind(this); + this._handleComponentSelected = this._handleComponentSelected.bind(this); + + let id = undefined; + + const componentListing = this.getUsableComponents(); + + if (componentListing && componentListing.length > 0) { + id = componentListing[0].id; + } + + this.state = { + loadedFormCount: undefined, + activeComponentId: id, + }; + } + + componentDidUpdate(prevProps: IBlockTypeComponentSetEditorProps, prevState: IBlockTypeComponentSetEditorState) { + if (prevProps.blockTypeItem !== this.props.blockTypeItem) { + let id = undefined; + + const componentListing = this.getUsableComponents(); + + if (componentListing && componentListing.length > 0) { + id = componentListing[0].id; + } + + this.setState({ + loadedFormCount: Database.loadedFormCount, + activeComponentId: id, + }); + } + } + + _addComponentClick() { + this.forceUpdate(); + } + + async _addComponent(name: string) { + if (Database.uxCatalog === null) { + return; + } + + const form = await Database.ensureFormLoaded(name); + + if (form !== undefined) { + const newDataObject = DataFormUtilities.generateDefaultItem(form); + + this.props.blockTypeItem.addComponent(name, newDataObject); + } + } + + getFormIdFromComponentId(componentId: string) { + return "block_" + componentId.replace(/:/gi, "_").replace(/_/gi, "_"); + } + + async _updateManager() { + if (!this.props.blockTypeItem) { + return; + } + + const components = this.props.blockTypeItem.getComponents(); + + for (let i = 0; i < components.length; i++) { + const component = components[i]; + + if (typeof component === "object" && component.id !== undefined) { + const formId = this.getFormIdFromComponentId(component.id); + await Database.ensureFormLoaded(formId); + } + } + + this.setState({ + loadedFormCount: Database.loadedFormCount, + activeComponentId: this.state.activeComponentId, + }); + } + + _handleComponentSelected(elt: any, event: ListProps | undefined) { + if (event === undefined || event.selectedIndex === undefined || this.state == null) { + return; + } + + const componentListing = this.getUsableComponents(); + + const id = componentListing[event.selectedIndex].id; + + if (id) { + this.setState({ + activeComponentId: id, + }); + } + } + + _handleCloseClick(props: IDataFormProps) { + if (!props.tag) { + return; + } + + const componentId = props.tag; + + if (componentId) { + this.props.blockTypeItem.removeComponent(componentId); + this.forceUpdate(); + } + } + + getUsableComponents() { + const components = this.props.blockTypeItem.getComponents(); + const componentList = []; + + for (let i = 0; i < components.length; i++) { + const component = components[i]; + + if (typeof component === "object" && component.id !== undefined) { + componentList.push(component); + } + } + + return componentList; + } + + render() { + if (this.state === undefined || this.state.loadedFormCount === undefined) { + this._updateManager(); + + return
Loading...
; + } + + const components = this.props.blockTypeItem.getComponents(); + const componentForms = []; + const componentList = []; + + let selectedIndex = 0; + + for (let i = 0; i < components.length; i++) { + const component = components[i]; + + if (typeof component === "object" && component.id !== undefined) { + const formId = component.id.replace(/:/gi, "_").replace(/_/gi, "_"); + + const form = Database.getForm("block_" + formId); + + componentList.push({ + key: component.id, + content: ( +
+ {Utilities.humanifyMinecraftName(component.id)} +
+ ), + }); + + if (component && component.id) { + if (form !== undefined && component.id === this.state?.activeComponentId) { + selectedIndex = i; + componentForms.push( +
+ +
+ ); + } else if (component.id === this.state?.activeComponentId) { + selectedIndex = i; + componentForms.push( +
(No editor is available for the {component.id} type.)
+ ); + } + } + } + } + + const toolbarItems: any[] = []; + + const splitButtonMenuItems = [ + { + id: "tameable", + key: "tameable", + onClick: this._addComponentClick, + content: "Add tameability", + }, + { + id: "rideable", + key: "rideable", + onClick: this._addComponentClick, + content: "Add rideability", + }, + { + id: "inventory", + key: "inventory", + onClick: this._addComponentClick, + content: "Add inventory capabilities", + }, + { + id: "healable", + key: "healable", + onClick: this._addComponentClick, + content: "Add healability", + }, + ]; + + let title = <>; + + if (this.props.title) { + title = {this.props.title}; + } + + const areaHeight = "calc(100vh - " + String(this.props.heightOffset + 34) + "px)"; + + return ( +
+
+
{title}
+
+ +
+
+ +
+
+
+ +
+
+ {componentForms} +
+
+ ); + } +} diff --git a/app/src/UX/BlockTypeEditor.tsx b/app/src/UX/BlockTypeEditor.tsx index 414ef4ed..44839a42 100644 --- a/app/src/UX/BlockTypeEditor.tsx +++ b/app/src/UX/BlockTypeEditor.tsx @@ -2,29 +2,40 @@ import { Component } from "react"; import IFileProps from "./IFileProps"; import IFile from "../storage/IFile"; import "./BlockTypeEditor.css"; -import IPersistable from "./IPersistable"; -import BlockType from "../minecraft/BlockType"; import Database from "../minecraft/Database"; import DataFormUtilities from "../dataform/DataFormUtilities"; -import ComponentSetEditor from "./ComponentSetEditor"; import { ThemeInput } from "@fluentui/styles"; import BlockTypeBehaviorDefinition from "../minecraft/BlockTypeBehaviorDefinition"; +import ProjectItem from "../app/ProjectItem"; +import BlockTypeComponentSetEditor from "./BlockTypeComponentSetEditor"; +import { CustomTabLabel } from "./Labels"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import WebUtilities from "./WebUtilities"; +import { faBolt, faBone, faCow, faSliders } from "@fortawesome/free-solid-svg-icons"; +import { Toolbar } from "@fluentui/react-northstar"; + +export enum BlockTypeEditorMode { + properties = 0, + actions = 1, + visuals = 2, + audio = 3, + loot = 5, +} interface IBlockTypeEditorProps extends IFileProps { heightOffset: number; readOnly: boolean; + item: ProjectItem; theme: ThemeInput; } interface IBlockTypeEditorState { fileToEdit: IFile; + mode: BlockTypeEditorMode; isLoaded: boolean; } -export default class BlockTypeEditor - extends Component - implements IPersistable -{ +export default class BlockTypeEditor extends Component { private _lastFileEdited?: IFile; constructor(props: IBlockTypeEditorProps) { @@ -33,9 +44,15 @@ export default class BlockTypeEditor this._handleBlockTypeLoaded = this._handleBlockTypeLoaded.bind(this); this._addComponentClick = this._addComponentClick.bind(this); this._addComponent = this._addComponent.bind(this); + this._setPropertiesMode = this._setPropertiesMode.bind(this); + this._setActionsMode = this._setActionsMode.bind(this); + this._setAudioMode = this._setAudioMode.bind(this); + this._setLootMode = this._setLootMode.bind(this); + this._setVisualsMode = this._setVisualsMode.bind(this); this.state = { fileToEdit: props.file, + mode: BlockTypeEditorMode.properties, isLoaded: false, }; @@ -46,6 +63,7 @@ export default class BlockTypeEditor if (state === undefined || state === null) { state = { fileToEdit: props.file, + mode: BlockTypeEditorMode.properties, isLoaded: false, }; @@ -78,8 +96,8 @@ export default class BlockTypeEditor if ( this.state.fileToEdit && this.state.fileToEdit.manager !== undefined && - this.state.fileToEdit.manager instanceof BlockType && - (this.state.fileToEdit.manager as BlockType).isLoaded && + this.state.fileToEdit.manager instanceof BlockTypeBehaviorDefinition && + (this.state.fileToEdit.manager as BlockTypeBehaviorDefinition).isLoaded && !this.state.isLoaded ) { this._doUpdate(setState); @@ -99,6 +117,7 @@ export default class BlockTypeEditor } else { this.state = { fileToEdit: this.props.file, + mode: this.state.mode, isLoaded: true, }; } @@ -109,13 +128,41 @@ export default class BlockTypeEditor const file = this.state.fileToEdit; if (file.manager !== null) { - const bt = file.manager as BlockType; + const bt = file.manager as BlockTypeBehaviorDefinition; bt.persist(); } } } + _setPropertiesMode() { + this._setMode(BlockTypeEditorMode.properties); + } + + _setActionsMode() { + this._setMode(BlockTypeEditorMode.actions); + } + + _setVisualsMode() { + this._setMode(BlockTypeEditorMode.visuals); + } + + _setAudioMode() { + this._setMode(BlockTypeEditorMode.audio); + } + + _setLootMode() { + this._setMode(BlockTypeEditorMode.loot); + } + + _setMode(mode: BlockTypeEditorMode) { + this.setState({ + fileToEdit: this.state.fileToEdit, + isLoaded: this.state.isLoaded, + mode: mode, + }); + } + async _addComponentClick() { await this._addComponent("minecraft:tameable"); @@ -132,7 +179,7 @@ export default class BlockTypeEditor if (form !== undefined) { const newDataObject = DataFormUtilities.generateDefaultItem(form); - const bt = this.state.fileToEdit.manager as BlockType; + const bt = this.state.fileToEdit.manager as BlockTypeBehaviorDefinition; if (bt.behaviorPackBlockTypeDef === undefined) { return; @@ -144,6 +191,13 @@ export default class BlockTypeEditor render() { const height = "calc(100vh - " + this.props.heightOffset + "px)"; + const toolbarItems = []; + const width = WebUtilities.getWidth(); + let isButtonCompact = false; + + if (width < 1016) { + isButtonCompact = true; + } if ( this.state === null || @@ -164,12 +218,87 @@ export default class BlockTypeEditor this.props.setActivePersistable(this); } - const et = this.state.fileToEdit.manager as BlockType; - - if (et.behaviorPackBlockTypeDef === undefined) { + toolbarItems.push({ + icon: ( + } + text={"Properties"} + isCompact={isButtonCompact} + isSelected={this.state.mode === BlockTypeEditorMode.properties} + theme={this.props.theme} + /> + ), + key: "btePropertiesTab", + onClick: this._setPropertiesMode, + title: "Edit documentation by types", + }); + + toolbarItems.push({ + icon: ( + } + text={"Actions"} + isCompact={isButtonCompact} + isSelected={this.state.mode === BlockTypeEditorMode.actions} + theme={this.props.theme} + /> + ), + key: "bteActionsTab", + onClick: this._setActionsMode, + title: "Edit documentation by types that need edits", + }); + + toolbarItems.push({ + icon: ( + } + text={"Visuals"} + isCompact={isButtonCompact} + isSelected={this.state.mode === BlockTypeEditorMode.visuals} + theme={this.props.theme} + /> + ), + key: "bteVisualsTab", + onClick: this._setVisualsMode, + title: "Edit documentation by types that need edits", + }); + + toolbarItems.push({ + icon: ( + } + text={"Loot"} + isCompact={isButtonCompact} + isSelected={this.state.mode === BlockTypeEditorMode.loot} + theme={this.props.theme} + /> + ), + key: "bteLootTableTab", + onClick: this._setLootMode, + title: "Loot", + }); + + const bt = this.state.fileToEdit.manager as BlockTypeBehaviorDefinition; + + if (bt.behaviorPackBlockTypeDef === undefined) { return
Loading behavior pack...
; } + let mode = <>; + + if (this.state.mode === BlockTypeEditorMode.properties) { + mode = ( +
+ +
+ ); + } + return (
-
{et.id}
-
default components:
-
- +
+ {bt.id} +
+
+
+ {mode}
); } diff --git a/app/src/UX/CartoSettings.tsx b/app/src/UX/CartoSettings.tsx index dc2f44fc..544d9bdb 100644 --- a/app/src/UX/CartoSettings.tsx +++ b/app/src/UX/CartoSettings.tsx @@ -13,7 +13,7 @@ interface ICartoSettingsProps extends IAppProps { interface ICartoSettingsState {} -export default class CartoSettings extends Component implements IPersistable { +export default class CartoSettings extends Component { private _activeEditorPersistable?: IPersistable; constructor(props: ICartoSettingsProps) { diff --git a/app/src/UX/EntityTypeComponentSetEditor.css b/app/src/UX/EntityTypeComponentSetEditor.css new file mode 100644 index 00000000..195dea9a --- /dev/null +++ b/app/src/UX/EntityTypeComponentSetEditor.css @@ -0,0 +1,129 @@ +.cose-area { + width: calc(100% - 2px); + min-width: calc(100% - 2px); + max-width: calc(100% - 2px); + padding-top: 12px; + padding-bottom: 12px; + padding-right: 12px; + display: grid; + max-width: 590px; + grid-template-columns: 280px 1fr; +} + +.cose-header { + font-size: 12pt; + font-size: 17pt; +} + +.cose-componentForm { + margin-bottom: 12px; + padding: 3px; + padding-left: 10px; + display: inline-grid; + min-height: 220px; +} + +.cose-componentWrapper { + margin-top: 1px; + margin-bottom: 1px; + + padding: 10px 6px 10px 6px; + border: 2px outset; + font-size: small; +} + +.cose-componentForm .ui-form__input { + padding-top: 3px; + margin-bottom: 4px; +} + +.cose-componentForm .ui-form__checkbox { + padding-top: 3px; + margin-top: 0px !important; + margin-bottom: 0px !important; +} + +.cose-componentForm .pu > :last-child { + margin-top: 0px; +} +.cose-componentForm .pt > :not(:last-child) { + margin-bottom: 0px; +} + +.cose-componentHeader { + padding-top: 20px; + font-size: 11pt; + padding-bottom: 14px; +} + +.cose-noeditor { + padding: 10px; +} + +.cose-title { + width: 200px; +} + +.cose-extraArea { + margin-bottom: 5px; +} + +.cose-componentList { + grid-column: 1; + overflow-y: scroll; + overflow-x: hidden; + border-left: solid 1px; + border-bottom: solid 1px; + border-top: solid 1px; +} + +.cose-componentList li { + padding-left: 5px; + padding-right: 2px; +} + +.cose-componentList .ui-list__itemcontent { + margin-right: 3px; +} + +.cose-componentBin { + padding: 4px; + height: 100%; + min-height: 200px; + vertical-align: top; + grid-column: 2; + overflow-y: scroll; + overflow-x: hidden; + border-right: solid 1px; + border-bottom: solid 1px; + border-top: solid 1px; +} + +.cose-componentBin:first-child { + margin-top: 1px; +} + +.cose-componentArea { + display: grid; + padding: 3px; + grid-column-start: 1; + grid-column-end: 3; + grid-template-columns: 190px 1fr 100px; +} + +.cose-titleArea { + grid-column: 2; + grid-row: 1; + padding-top: 5px; +} + +.cose-toolBarArea { + grid-column: 3; + grid-row: 1; +} + +.cose-extraArea { + grid-column: 1; + grid-row: 1; + padding-left: 5px; +} diff --git a/app/src/UX/ComponentSetEditor.tsx b/app/src/UX/EntityTypeComponentSetEditor.tsx similarity index 95% rename from app/src/UX/ComponentSetEditor.tsx rename to app/src/UX/EntityTypeComponentSetEditor.tsx index 2894b128..da045100 100644 --- a/app/src/UX/ComponentSetEditor.tsx +++ b/app/src/UX/EntityTypeComponentSetEditor.tsx @@ -1,5 +1,5 @@ import { Component } from "react"; -import "./ComponentSetEditor.css"; +import "./EntityTypeComponentSetEditor.css"; import DataForm, { IDataFormProps } from "../dataform/DataForm"; import Database from "../minecraft/Database"; import { Toolbar, SplitButton, ThemeInput, List, ListProps, selectableListBehavior } from "@fluentui/react-northstar"; @@ -7,7 +7,7 @@ import IManagedComponentSetItem from "../minecraft/IManagedComponentSetItem"; import DataFormUtilities from "../dataform/DataFormUtilities"; import Utilities from "../core/Utilities"; -interface IComponentSetEditorProps { +interface IEntityTypeComponentSetEditorProps { componentSetItem: IManagedComponentSetItem; isDefault: boolean; heightOffset: number; @@ -15,13 +15,16 @@ interface IComponentSetEditorProps { theme: ThemeInput; } -interface IComponentSetEditorState { +interface IEntityTypeComponentSetEditorState { loadedFormCount?: number; activeComponentId: string | undefined; } -export default class ComponentSetEditor extends Component { - constructor(props: IComponentSetEditorProps) { +export default class EntityTypeComponentSetEditor extends Component< + IEntityTypeComponentSetEditorProps, + IEntityTypeComponentSetEditorState +> { + constructor(props: IEntityTypeComponentSetEditorProps) { super(props); this._addComponentClick = this._addComponentClick.bind(this); @@ -43,7 +46,7 @@ export default class ComponentSetEditor extends Component this.props.heightOffset + 100 ? "calc(100vh - " + (this.props.heightOffset - 10) + "px)" : "inherit"; + let folderAreaHeight = + height > this.props.heightOffset + 100 ? "calc(100vh - " + (this.props.heightOffset + 106) + "px)" : "inherit"; + let accessoryArea = <>; if (this.props.mode === FileExplorerMode.folderPicker && this.state.selectedItem) { - const label = "Create a new folder at " + this.state.selectedItem.name + ":"; accessoryArea = (
-
- {label} +
+ (create a new folder at + + {this.state.selectedItem.name} + + ):
@@ -175,12 +181,15 @@ export default class FileExplorer extends Component
implements IPersistable { +export default class GridEditor extends Component { private rootElt: React.RefObject; private grid: TuiGrid | null = null; private gridElement: HTMLDivElement | null = null; diff --git a/app/src/UX/Home.css b/app/src/UX/Home.css index 62af75f9..d74385e8 100644 --- a/app/src/UX/Home.css +++ b/app/src/UX/Home.css @@ -25,6 +25,7 @@ .home-main { grid-template-columns: 400px 1fr; display: grid; + width: 100vw; } .home-header { @@ -34,13 +35,12 @@ } .home-toolTile { - height: 126px; - display: inline-flex; + height: 130px; margin-top: 10px; margin-left: 10px; - width: 330px; + width: 310px; text-align: left; - border: solid 1.5px black; + vertical-align: middle; } .home-header-area { @@ -51,10 +51,6 @@ border-top: inset 2px; } - .home-usage-interior { - border-top: outset 2px; - } - .home-gallery { grid-column-start: 2; grid-column-end: 3; @@ -411,9 +407,7 @@ .home-toolTileInner { height: 100%; - width: 100%; padding: 8px 10px 8px 10px; - border: outset 3px; } .home-tileDown { @@ -433,6 +427,8 @@ margin-left: 1px; margin-top: 0px; margin-bottom: 0px; + display: grid; + grid-template-columns: 24px 1fr; } .home-toolTile-instruction { @@ -448,6 +444,11 @@ border: solid 1px rgb(72, 73, 74) !important; box-shadow: none !important; text-align: center; + margin-top: 2.5px; + padding-top: 8px; + width: 100%; + vertical-align: middle; + font-weight: bold; padding-left: 8px !important; padding-right: 8px !important; max-width: calc(100vw - 100px) !important; diff --git a/app/src/UX/Home.tsx b/app/src/UX/Home.tsx index 80225377..f82c98cf 100644 --- a/app/src/UX/Home.tsx +++ b/app/src/UX/Home.tsx @@ -27,7 +27,6 @@ import Log from "../core/Log"; import IGallery from "../app/IGallery"; import IFolder from "../storage/IFolder"; import IGalleryItem from "../app/IGalleryItem"; -import Database from "../minecraft/Database"; import { GalleryProjectCommand } from "./ProjectGallery"; import AppServiceProxy, { AppServiceProxyCommands } from "../core/AppServiceProxy"; import ProjectGallery from "./ProjectGallery"; @@ -45,6 +44,7 @@ import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons"; import WebUtilities from "./WebUtilities"; import FileSystemFolder from "../storage/FileSystemFolder"; import IStorage from "../storage/IStorage"; +import MinecraftBox from "./MinecraftBox"; enum HomeDialogMode { none = 0, @@ -543,11 +543,11 @@ export default class Home extends Component { private _onCartoLoaded(source: Carto, target: Carto) { this.forceUpdate(); - this._loadSnippets(); + this._loadAsync(); } - private async _loadSnippets() { - await Database.loadSnippets(); + private async _loadAsync() { + // add any async loading code here. this.forceUpdate(); } @@ -1001,29 +1001,18 @@ export default class Home extends Component { ); toolBin.push( -
-
+ +

- -   Validate/Inspect Content + + + +  Validate/Inspect Content

Upload a zip/MCAddon/MCPack/MCWorld of Minecraft files to get an Inspector report. @@ -1040,7 +1029,7 @@ export default class Home extends Component { onChange={this._handleInspectFileUpload} />
-
+
); mainToolArea.push( diff --git a/app/src/UX/ItemGallery.tsx b/app/src/UX/ItemGallery.tsx index c0e53ce5..63459c5b 100644 --- a/app/src/UX/ItemGallery.tsx +++ b/app/src/UX/ItemGallery.tsx @@ -8,6 +8,7 @@ import IGalleryItem, { GalleryItemType } from "../app/IGalleryItem"; import { ThemeInput } from "@fluentui/react-northstar"; import Project from "../app/Project"; import ItemTileButton, { ItemTileButtonDisplayMode } from "./ItemTileButton"; +import Database from "../minecraft/Database"; export enum GalleryItemCommand { newItem, @@ -126,23 +127,6 @@ export default class ItemGallery extends Component= 0) || - (galProject.description && galProject.description.toLowerCase().indexOf(searchKey) >= 0) - ) { - return true; - } - - return false; - } - render() { const galleryButtons = []; let itemGalleriesElt = <>; @@ -157,7 +141,7 @@ export default class ItemGallery extends Component; let description = []; - if ( this.props.project.type === GalleryItemType.codeSample || this.props.project.type === GalleryItemType.editorCodeSample ) { - const snippet = ProjectUtilities.getSnippet(this.props.project.id); - - if (snippet) { - const lines = snippet.body; - - if (lines.length >= 1) { - let curLine = this.props.project.codeLineStart; - - if (curLine === undefined) { - curLine = Math.ceil(lines.length / 2) - 1; - } - - let addedLines = 0; + const topics = this.props.project.topics; - while (curLine < lines.length && addedLines < 4) { - if (lines[curLine] && lines[curLine].indexOf("const overworld") <= 0 && lines[curLine].trim().length > 0) { - description.push(
{lines[curLine]}
); - addedLines++; - } - - curLine++; - } + if (topics) { + for (const topic of topics) { + description.push({topic}); } } } diff --git a/app/src/UX/ItemTypeEditor.tsx b/app/src/UX/ItemTypeEditor.tsx index 658a0f27..af4ff041 100644 --- a/app/src/UX/ItemTypeEditor.tsx +++ b/app/src/UX/ItemTypeEditor.tsx @@ -5,7 +5,7 @@ import "./ItemTypeEditor.css"; import IPersistable from "./IPersistable"; import ItemTypeBehaviorDefinition from "../minecraft/ItemTypeBehaviorDefinition"; import Database from "../minecraft/Database"; -import ComponentSetEditor from "./ComponentSetEditor"; +import EntityTypeComponentSetEditor from "./EntityTypeComponentSetEditor"; import { ThemeInput } from "@fluentui/styles"; interface IItemTypeEditorProps extends IFileProps { @@ -149,7 +149,7 @@ export default class ItemTypeEditor extends Componentdefault components:
- { @@ -69,7 +70,6 @@ export default class JavaScriptEditor extends Component | React.KeyboardEvent | null, - data: DropdownProps - ) { - if ( - this.editor === undefined || - data.value === undefined || - data.value === null || - typeof data.value !== "string" - ) { - return; - } - - const snippet = ProjectUtilities.getSnippet(data.value); - - if (!snippet) { - return; - } - - let result = undefined; - result = snippet.body.join("\n"); - - if (result !== undefined && this.props.project) { - const projName = this.props.project.loc.getTokenValueOrDefault(this.props.project.name); - - result = ProjectContent.replaceCommonItems(result, projName); - result = ProjectUtilities.adaptSample(result, ""); - - this.editor.trigger("keyboard", "type", { text: result }); - } - } - async _doUpdate() { - if (Database.snippetsFolder === null) { - await Database.loadSnippets(); - } - if (this.props.project && this.props.project.scriptVersion === ProjectScriptVersion.stable10) { if (!Database.stableTypeDefs) { await Database.loadStableScriptTypes(); @@ -619,8 +587,8 @@ export default class JavaScriptEditor extends Component ; + className?: string; + children: ReactChild[] | ReactChild; +} + +interface IMinecraftBoxState {} + +export default class MinecraftBox extends Component { + render() { + return ( +
+
+
+   +
+
+   +
+
+   +
+
+   +
+
+ {this.props.children} +
+
+   +
+
+   +
+
+   +
+
+   +
+
+
+ ); + } +} diff --git a/app/src/UX/MinecraftButton.css b/app/src/UX/MinecraftButton.css new file mode 100644 index 00000000..ce01fc6a --- /dev/null +++ b/app/src/UX/MinecraftButton.css @@ -0,0 +1,78 @@ +.micb-outer { + display: inline-flex; +} + +.micb-button { + border: 0px !important; + width: 100% !important; + min-width: 100% !important; + height: 100% !important; + background-color: inherit !important; + box-shadow: none !important; + padding-right: 0px !important; + text-align: left !important; + vertical-align: inherit !important; + padding-left: 0px !important; +} + +.micb-button .bi { + height: 100% !important; +} + +.micb-grid { + display: grid; + width: 100%; + grid-template-columns: 3px 1fr 3px; + grid-template-rows: 3px 1fr 3px; + border: solid 2px; +} + +.micb-edge { + font-size: 1pt; + font-family: sans-serif; +} + +.micb-7 { + grid-column: 1; + grid-row: 1; +} + +.micb-8 { + grid-column: 2; + grid-row: 1; +} + +.micb-9 { + grid-column: 3; + grid-row: 1; +} + +.micb-4 { + grid-column: 1; + grid-row: 2; +} + +.micb-5 { + grid-column: 2; + grid-row: 2; +} + +.micb-6 { + grid-column: 3; + grid-row: 2; +} + +.micb-1 { + grid-column: 1; + grid-row: 3; +} + +.micb-2 { + grid-column: 2; + grid-row: 3; +} + +.micb-3 { + grid-column: 3; + grid-row: 3; +} diff --git a/app/src/UX/MinecraftButton.tsx b/app/src/UX/MinecraftButton.tsx new file mode 100644 index 00000000..ba655ac5 --- /dev/null +++ b/app/src/UX/MinecraftButton.tsx @@ -0,0 +1,240 @@ +import { Component, ReactChild } from "react"; +import "./MinecraftButton.css"; +import { Button, ButtonProps, ComponentEventHandler, ThemeInput } from "@fluentui/react-northstar"; + +interface IMinecraftButtonProps { + theme: ThemeInput; + className?: string; + onClick: ComponentEventHandler; + children: ReactChild[] | ReactChild; +} + +interface IMinecraftButtonState { + isPushed: boolean; +} + +export default class MinecraftButton extends Component { + constructor(props: IMinecraftButtonProps) { + super(props); + + this._projectClick = this._projectClick.bind(this); + this._projectMouseDown = this._projectMouseDown.bind(this); + this._projectMouseUp = this._projectMouseUp.bind(this); + + this.state = { + isPushed: false, + }; + } + + _projectClick(event: React.SyntheticEvent, data?: ButtonProps) { + if (this.props.onClick) { + this.props.onClick(event, this.props); + } + + this.setState({ + isPushed: false, + }); + } + + _projectMouseDown(event: React.SyntheticEvent, data?: ButtonProps) { + this.setState({ + isPushed: true, + }); + } + + _projectMouseUp(event: React.SyntheticEvent, data?: ButtonProps) { + this.setState({ + isPushed: false, + }); + } + + render() { + if (this.state && this.state.isPushed) { + return ( +
+
+
+   +
+
+   +
+
+   +
+
+   +
+
+ +
+
+   +
+
+   +
+
+   +
+
+   +
+
+
+ ); + } + + return ( +
+
+
+   +
+
+   +
+
+   +
+
+   +
+
+ +
+
+   +
+
+   +
+
+   +
+
+   +
+
+
+ ); + } +} diff --git a/app/src/UX/NewItem.css b/app/src/UX/NewItem.css index 801a97da..3c7bcfb6 100644 --- a/app/src/UX/NewItem.css +++ b/app/src/UX/NewItem.css @@ -17,6 +17,10 @@ padding-top: 5px; } +.nitem-folderAreaLabel { + padding-bottom: 2px; +} + .nitem-nameArea { grid-column: 2; } diff --git a/app/src/UX/NewItem.tsx b/app/src/UX/NewItem.tsx index 2979f9c7..1989a640 100644 --- a/app/src/UX/NewItem.tsx +++ b/app/src/UX/NewItem.tsx @@ -103,7 +103,7 @@ export default class NewItem extends Component { let inputText = this.state.name; if (inputText === undefined) { - inputText = ""; + inputText = ProjectItemUtilities.getNewItemName(this.props.itemType); } let folderPicker = <>; diff --git a/app/src/UX/PackageManager.tsx b/app/src/UX/PackageManager.tsx index b4e2c9e5..2d07ebb0 100644 --- a/app/src/UX/PackageManager.tsx +++ b/app/src/UX/PackageManager.tsx @@ -24,7 +24,7 @@ interface IPackManagerState { packReferences: IPackageReference[]; } -export default class PackManager extends Component implements IPersistable { +export default class PackManager extends Component { #activeEditorPersistable?: IPersistable; constructor(props: IPackManagerProps) { diff --git a/app/src/UX/ProjectActions.css b/app/src/UX/ProjectActions.css index e1a58b36..49685344 100644 --- a/app/src/UX/ProjectActions.css +++ b/app/src/UX/ProjectActions.css @@ -8,32 +8,28 @@ font-size: large; font-weight: bold; padding-top: 14px; + line-height: 1; } .pact-toolTile { height: 110px !important; display: inline-flex !important; - margin-top: 10px; margin-left: 10px; padding: 0px !important; text-align: left; - width: 320px; - border: solid 1.5px black; } .pact-toolTile-button { margin-top: 3px; margin-left: 0px !important; height: 35px !important; - border: solid 1px rgb(72, 73, 74) !important; - box-shadow: none !important; } .pact-toolTileInner { height: 100%; - width: 100%; + width: 320px; + text-align: left; padding: 6px 8px 6px 8px; - border: outset 3px; } .pact-toolTile input[type="file"] { @@ -47,7 +43,7 @@ font-weight: bold; padding-bottom: 6px; display: grid; - grid-template-columns: 34px 1fr; + grid-template-columns: 30px 1fr; } .pact-toolTile-instruction { @@ -69,14 +65,13 @@ .pact-faIconWrap { grid-column: 1; - padding-top: 3px; - padding-left: 4px; + padding-top: 2px; } .pact-faIconWrapIn { grid-column: 1; - padding-top: 3px; - padding-left: 7px; + padding-top: 2px; + padding-left: 1px; } .pact-icon { @@ -88,8 +83,8 @@ max-width: 24px; min-height: 24px; max-height: 24px; - margin: 1px; - margin-top: 3px; + margin-left: 0px; + margin-top: 1px; border: solid 1px black; } diff --git a/app/src/UX/ProjectActions.tsx b/app/src/UX/ProjectActions.tsx index f3200bdd..34a1a378 100644 --- a/app/src/UX/ProjectActions.tsx +++ b/app/src/UX/ProjectActions.tsx @@ -1,13 +1,14 @@ -import { Component, MouseEvent } from "react"; +import { Component } from "react"; import IAppProps from "./IAppProps"; import Project from "../app/Project"; import "./ProjectActions.css"; -import { Button, ThemeInput } from "@fluentui/react-northstar"; +import { ThemeInput } from "@fluentui/react-northstar"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons"; import ProjectEditorUtilities, { ProjectEditorAction, ProjectEditorMode } from "./ProjectEditorUtilities"; import CartoApp from "../app/CartoApp"; import { faFileZipper, faFolder } from "@fortawesome/free-regular-svg-icons"; +import MinecraftButton from "./MinecraftButton"; interface IProjectActionsProps extends IAppProps { project: Project; @@ -54,42 +55,19 @@ export default class ProjectActions extends Component= 0) { - event.currentTarget.className = event.currentTarget.className.replace(" pact-tileDown", ""); - } - } - render() { const packageBin = []; const exportBin = []; const inspectBin = []; inspectBin.push( - + ); exportBin.push( - + ); if (window.showDirectoryPicker !== undefined) { exportBin.push( - + ); } packageBin.push( - + ); packageBin.push( - + ); const height = "calc(100vh - " + this.props.heightOffset + "px)"; diff --git a/app/src/UX/ProjectAddButton.css b/app/src/UX/ProjectAddButton.css new file mode 100644 index 00000000..0de21f83 --- /dev/null +++ b/app/src/UX/ProjectAddButton.css @@ -0,0 +1,23 @@ +.pab-outer { + padding: 7px; +} + +.pab-commands { + grid-column: 2; + grid-row: 1; + text-align: right; + max-width: 140px; + padding-top: 7px; + padding-bottom: 6px; + padding-left: 8px; +} + +.pab-dialog { + min-height: 90px; + margin-top: 20px; + vertical-align: top; +} + +.pab-extension { + padding-left: 8px; +} diff --git a/app/src/UX/ProjectAddButton.tsx b/app/src/UX/ProjectAddButton.tsx new file mode 100644 index 00000000..ca00a079 --- /dev/null +++ b/app/src/UX/ProjectAddButton.tsx @@ -0,0 +1,533 @@ +import { Component } from "react"; +import IAppProps from "./IAppProps"; +import Project from "./../app/Project"; +import { ProjectItemType } from "./../app/IProjectItemData"; +import ProjectItem from "./../app/ProjectItem"; +import { ProjectEditorMode } from "./ProjectEditorUtilities"; +import { SplitButton, Dialog, ThemeInput } from "@fluentui/react-northstar"; + +import { GitHubPropertyType } from "./ProjectPropertyEditor"; +import NewEntityType from "./NewEntityType"; +import ProjectUtilities, { NewEntityTypeAddMode } from "../app/ProjectUtilities"; +import IGitHubInfo from "../app/IGitHubInfo"; +import ProjectItemManager from "../app/ProjectItemManager"; +import "./ProjectAddButton.css"; +import NewBlockType from "./NewBlockType"; +import { ProjectScriptLanguage } from "../app/IProjectData"; +import IGalleryItem from "../app/IGalleryItem"; +import ProjectItemUtilities from "../app/ProjectItemUtilities"; +import ProjectInfoSet from "../info/ProjectInfoSet"; +import IProjectItemSeed from "../app/IProjectItemSeed"; +import NewItem from "./NewItem"; + +export enum EntityTypeCommand { + select, +} + +export enum BlockTypeCommand { + select, +} + +export enum ListItemType { + item, + typeSpacer, + pathSpacer, + references, +} + +interface IProjectAddButtonProps extends IAppProps { + theme: ThemeInput; + onActiveProjectItemChangeRequested?: (projectItem: ProjectItem, forceRawView: boolean) => void; + onActiveReferenceChangeRequested?: (reference: IGitHubInfo) => void; + onModeChangeRequested?: (mode: ProjectEditorMode) => void; + heightOffset: number; + project: Project | null; + visualSeed?: number; +} + +interface IProjectAddButtonState { + activeItem: ProjectItem | undefined; + dialogMode: ProjectAddButtonDialogType; + maxItemsToShow: number; + newItemType?: ProjectItemType; + activeProjectInfoSet?: ProjectInfoSet | undefined; + collapsedItemTypes: number[]; + contextFocusedItem?: number; + collapsedStoragePaths: string[]; +} + +export enum ProjectAddButtonDialogType { + noDialog = 0, + newEntityTypeDialog = 3, + newBlockTypeDialog = 5, + newItemDialog = 6, +} + +export default class ProjectAddButton extends Component { + private _newItemName?: string; + + private _tentativeNewItem: IProjectItemSeed | undefined; + + tentativeGitHubMode: string = "existing"; + tentativeGitHubRepoName?: string; + tentativeGitHubOwner?: string; + tentativeGitHubBranch?: string; + tentativeGitHubFolder?: string; + tentativeGitHubTitle?: string; + + tentativeNewEntityTypeAddMode?: NewEntityTypeAddMode; + tentativeNewEntityTypeName?: string; + tentativeNewEntityTypeItem?: IGalleryItem; + + tentativeNewBlockTypeName?: string; + tentativeNewBlockTypeItem?: IGalleryItem; + + constructor(props: IProjectAddButtonProps) { + super(props); + + this._updateNewItemSeed = this._updateNewItemSeed.bind(this); + + this._handleNewItem = this._handleNewItem.bind(this); + this._handleNewScriptClick = this._handleNewScriptClick.bind(this); + this._handleCancel = this._handleCancel.bind(this); + this._handleNewSpawnRuleClick = this._handleNewSpawnRuleClick.bind(this); + this._handleNewLootTableClick = this._handleNewLootTableClick.bind(this); + this._handleNewFeatureClick = this._handleNewFeatureClick.bind(this); + this._handleNewFunctionClick = this._handleNewFunctionClick.bind(this); + this._handleNewEntityTypeClick = this._handleNewEntityTypeClick.bind(this); + this._handleNewBlockTypeClick = this._handleNewBlockTypeClick.bind(this); + this._githubProjectUpdated = this._githubProjectUpdated.bind(this); + this._handleAddReference = this._handleAddReference.bind(this); + this._newEntityTypeUpdated = this._newEntityTypeUpdated.bind(this); + this._newBlockTypeUpdated = this._newBlockTypeUpdated.bind(this); + this._handleNewEntityType = this._handleNewEntityType.bind(this); + this._handleNewBlockType = this._handleNewBlockType.bind(this); + + this.state = { + activeItem: undefined, + dialogMode: ProjectAddButtonDialogType.noDialog, + maxItemsToShow: 300, + collapsedItemTypes: this.props.carto.collapsedTypes, + collapsedStoragePaths: this.props.project ? this.props.project.collapsedStoragePaths : [], + }; + } + + _newEntityTypeUpdated(newAddMode: NewEntityTypeAddMode, entityTypeItem: IGalleryItem, name: string) { + this.tentativeNewEntityTypeItem = entityTypeItem; + this.tentativeNewEntityTypeAddMode = newAddMode; + this.tentativeNewEntityTypeName = name; + } + + _newBlockTypeUpdated(blockTypeItem: IGalleryItem | undefined, name: string | undefined) { + this.tentativeNewBlockTypeItem = blockTypeItem; + this.tentativeNewBlockTypeName = name; + } + + _githubProjectUpdated(property: GitHubPropertyType, value?: string) { + switch (property) { + case GitHubPropertyType.repoName: + this.tentativeGitHubRepoName = value; + break; + + case GitHubPropertyType.owner: + this.tentativeGitHubOwner = value; + break; + + case GitHubPropertyType.branch: + this.tentativeGitHubBranch = value; + break; + + case GitHubPropertyType.folder: + this.tentativeGitHubFolder = value; + break; + + case GitHubPropertyType.mode: + if (value !== undefined) { + this.tentativeGitHubMode = value; + } + break; + + case GitHubPropertyType.title: + this.tentativeGitHubTitle = value; + break; + } + } + + _handleNewScriptClick() { + if (this.props.project !== null) { + this.launchNewItemType( + this.props.project.preferredScriptLanguage === ProjectScriptLanguage.javaScript + ? ProjectItemType.js + : ProjectItemType.ts + ); + } + } + + _handleNewFeatureClick() { + if (this.props.project !== null) { + this.launchNewItemType(ProjectItemType.featureBehavior); + } + } + + _handleNewLootTableClick() { + if (this.props.project !== null) { + this.launchNewItemType(ProjectItemType.lootTableBehavior); + } + } + + _handleNewSpawnRuleClick() { + if (this.props.project !== null) { + this.launchNewItemType(ProjectItemType.spawnRuleBehavior); + } + } + + _handleNewFormClick() { + if (this.props.project !== null) { + this.launchNewItemType(ProjectItemType.dataForm); + } + } + + _handleNewFunctionClick() { + if (this.props.project !== null) { + ProjectItemManager.createNewFunction(this.props.project); + } + } + + launchNewItemType(itemType: ProjectItemType, suggestedName?: string) { + this._tentativeNewItem = { + name: suggestedName, + itemType: itemType, + }; + + this.setState({ + activeItem: this.state.activeItem, + dialogMode: ProjectAddButtonDialogType.newItemDialog, + newItemType: itemType, + maxItemsToShow: this.state.maxItemsToShow, + contextFocusedItem: this.state.contextFocusedItem, + collapsedItemTypes: this.state.collapsedItemTypes, + collapsedStoragePaths: this.state.collapsedStoragePaths, + }); + } + + async _handleNewEntityTypeClick() { + if (this.state === null) { + return; + } + + this.setState({ + activeItem: this.state.activeItem, + dialogMode: ProjectAddButtonDialogType.newEntityTypeDialog, + contextFocusedItem: this.state.contextFocusedItem, + maxItemsToShow: this.state.maxItemsToShow, + collapsedItemTypes: this.state.collapsedItemTypes, + collapsedStoragePaths: this.state.collapsedStoragePaths, + }); + } + + async _handleNewBlockTypeClick() { + if (this.state === null) { + return; + } + + this.setState({ + activeItem: this.state.activeItem, + dialogMode: ProjectAddButtonDialogType.newBlockTypeDialog, + maxItemsToShow: this.state.maxItemsToShow, + contextFocusedItem: this.state.contextFocusedItem, + collapsedItemTypes: this.state.collapsedItemTypes, + collapsedStoragePaths: this.state.collapsedStoragePaths, + }); + } + + async _handleNewBlockType() { + if (this.state === null) { + return; + } + + if (this.tentativeNewBlockTypeName && this.tentativeNewBlockTypeItem && this.props.project !== null) { + await ProjectUtilities.addBlockTypeFromGallery( + this.props.project, + this.tentativeNewBlockTypeItem, + this.tentativeNewBlockTypeName + ); + } + + if (this.props.project) { + await this.props.project.save(); + } + + this.setState({ + activeItem: undefined, + dialogMode: ProjectAddButtonDialogType.noDialog, + maxItemsToShow: this.state.maxItemsToShow, + contextFocusedItem: this.state.contextFocusedItem, + collapsedItemTypes: this.state.collapsedItemTypes, + collapsedStoragePaths: this.state.collapsedStoragePaths, + }); + } + + async _handleNewEntityType() { + if (this.state === null) { + return; + } + + if (this.tentativeNewEntityTypeItem !== undefined && this.props.project !== null) { + await ProjectUtilities.addEntityTypeFromGallery( + this.props.project, + this.tentativeNewEntityTypeItem, + this.tentativeNewEntityTypeName, + this.tentativeNewEntityTypeAddMode + ); + } + + if (this.props.project) { + await this.props.project.save(); + } + + this.setState({ + activeItem: undefined, + dialogMode: ProjectAddButtonDialogType.noDialog, + maxItemsToShow: this.state.maxItemsToShow, + contextFocusedItem: this.state.contextFocusedItem, + collapsedItemTypes: this.state.collapsedItemTypes, + collapsedStoragePaths: this.state.collapsedStoragePaths, + }); + } + + async _handleNewItem() { + if (this.state === null) { + return; + } + + let projectItem = undefined; + + if (this._tentativeNewItem !== undefined && this.props.project !== null) { + projectItem = await ProjectItemManager.createNewItem(this.props.project, this._tentativeNewItem); + } + + if (this.props.project) { + await this.props.project.save(); + } + + this.setState({ + activeItem: projectItem, + dialogMode: ProjectAddButtonDialogType.noDialog, + maxItemsToShow: this.state.maxItemsToShow, + contextFocusedItem: this.state.contextFocusedItem, + collapsedItemTypes: this.state.collapsedItemTypes, + collapsedStoragePaths: this.state.collapsedStoragePaths, + }); + + if (projectItem && this.props.onActiveProjectItemChangeRequested) { + this.props.onActiveProjectItemChangeRequested(projectItem, false); + } + } + + _handleAddReference() { + if (this.state === null) { + return; + } + + this.setState({ + activeItem: undefined, + dialogMode: ProjectAddButtonDialogType.noDialog, + maxItemsToShow: this.state.maxItemsToShow, + contextFocusedItem: this.state.contextFocusedItem, + collapsedItemTypes: this.state.collapsedItemTypes, + collapsedStoragePaths: this.state.collapsedStoragePaths, + }); + } + + _handleCancel() { + if (this.state === null) { + return; + } + + this.setState({ + activeItem: undefined, + dialogMode: ProjectAddButtonDialogType.noDialog, + maxItemsToShow: this.state.maxItemsToShow, + contextFocusedItem: this.state.contextFocusedItem, + collapsedItemTypes: this.state.collapsedItemTypes, + collapsedStoragePaths: this.state.collapsedStoragePaths, + }); + } + + _updateNewItemSeed(newSeed: IProjectItemSeed) { + this._tentativeNewItem = newSeed; + } + + render() { + const splitButtonMenuItems: any[] = [ + { + id: "gpscript", + key: "gpscript", + onClick: this._handleNewScriptClick, + content: + this.props.project?.preferredScriptLanguage === ProjectScriptLanguage.typeScript + ? "New TypeScript" + : "New JavaScript", + }, + { + id: "function", + key: "function", + onClick: this._handleNewFunctionClick, + content: "New function", + }, + { + id: "entityType", + key: "entityType", + onClick: this._handleNewEntityTypeClick, + content: "New mob (entity type)", + }, + { + id: "blockType", + key: "blockType", + onClick: this._handleNewBlockTypeClick, + content: "New block type", + }, + { + id: "definitions", + key: "definitions", + content: "New definition", + on: "hover", + menu: { + items: [ + { + key: "1", + onClick: this._handleNewSpawnRuleClick, + content: "Spawn rule", + }, + { + key: "2", + onClick: this._handleNewLootTableClick, + content: "Loot table", + }, + { + key: "3", + onClick: this._handleNewBlockTypeClick, + content: "Feature", + }, + ], + }, + }, + ]; + + let dialogArea = <>; + if ( + this.state !== null && + this.props.project !== null && + this.state.dialogMode === ProjectAddButtonDialogType.newEntityTypeDialog + ) { + dialogArea = ( + + } + header={"New mob"} + /> + ); + } else if ( + this.state !== null && + this.props.project !== null && + this.state.newItemType !== undefined && + this.state.dialogMode === ProjectAddButtonDialogType.newItemDialog + ) { + dialogArea = ( + + } + header={"New " + ProjectItemUtilities.getDescriptionForType(this.state.newItemType)} + /> + ); + } else if ( + this.state !== null && + this.props.project !== null && + this.state.dialogMode === ProjectAddButtonDialogType.newBlockTypeDialog + ) { + dialogArea = ( + + } + header={"New Block Type"} + /> + ); + } + + let splitButton = <>; + + if (this.props.project) { + splitButton = ( +
+ +
+ ); + } + + return ( +
+ {splitButton} + {dialogArea} +
+ ); + } +} diff --git a/app/src/UX/ProjectGallery.css b/app/src/UX/ProjectGallery.css index 468c87f6..551991c9 100644 --- a/app/src/UX/ProjectGallery.css +++ b/app/src/UX/ProjectGallery.css @@ -6,13 +6,11 @@ border-left: solid 1px; border-right: solid 1px; border-bottom: solid 1px; - border-top: solid 1px; + border-top: solid 0px; } .pg-tabArea { display: block; - position: relative; - top: 0.75px; padding-top: 10px; padding-left: 10px; padding-bottom: 0px; @@ -27,7 +25,7 @@ border-left: solid 0px; border-right: solid 0px; border-bottom: solid 1px; - border-top: solid 1px; + border-top: solid 0px; text-align: center; } @@ -37,8 +35,7 @@ .pg-tabArea { display: block; - position: relative; - top: 1px; + padding-top: 10px; padding-left: 5px; padding-bottom: 0px; @@ -65,9 +62,25 @@ padding: 20px !important; } +.pg-tabsTop { + width: 100%; + display: grid; + margin-right: 28px; + grid-template-columns: 375px 1fr; +} + .pg-underline { display: inline-block; - height: 32px; + height: 32.5px; width: 6px; + grid-column: 1; + border-bottom: solid 1px; +} + +.pg-tabsFiller { + display: inline-block; + height: 51px; + margin-right: 10px; + grid-column: 2; border-bottom: solid 1px; } diff --git a/app/src/UX/ProjectGallery.tsx b/app/src/UX/ProjectGallery.tsx index e7ceed6c..def54d35 100644 --- a/app/src/UX/ProjectGallery.tsx +++ b/app/src/UX/ProjectGallery.tsx @@ -9,6 +9,7 @@ import IGalleryItem, { GalleryItemType } from "../app/IGalleryItem"; import { Button, ThemeInput } from "@fluentui/react-northstar"; import Project from "../app/Project"; import Log from "../core/Log"; +import Database from "../minecraft/Database"; export enum GalleryProjectCommand { newProject, @@ -178,23 +179,6 @@ export default class ProjectGallery extends Component= 0) || - (galProject.description && galProject.description.toLowerCase().indexOf(searchKey) >= 0) - ) { - return true; - } - - return false; - } - render() { const snippetGalleries = []; const projectGalleries = []; @@ -309,7 +293,7 @@ export default class ProjectGallery extends Component - {tabsElt} +
+ {tabsElt} +
+   +
+
{projectGalleriesElt} {snippetGalleriesElt}
diff --git a/app/src/UX/ProjectItemEditor.tsx b/app/src/UX/ProjectItemEditor.tsx index 3754f06c..9da17e35 100644 --- a/app/src/UX/ProjectItemEditor.tsx +++ b/app/src/UX/ProjectItemEditor.tsx @@ -357,7 +357,7 @@ export default class ProjectItemEditor extends Component ); - } else if ( - this.state !== null && - this.props.project !== null && - this.state.dialogMode === ProjectItemListDialogType.newEntityTypeDialog - ) { - dialogArea = ( - - } - header={"New mob"} - /> - ); - } else if ( - this.state !== null && - this.props.project !== null && - this.state.dialogMode === ProjectItemListDialogType.newEntityTypeDialog - ) { - dialogArea = ( - - } - header={"New mob"} - /> - ); - } else if ( - this.state !== null && - this.props.project !== null && - this.state.newItemType !== undefined && - this.state.dialogMode === ProjectItemListDialogType.newItemDialog - ) { - dialogArea = ( - - } - header={"New " + ProjectItemUtilities.getDescriptionForType(this.state.newItemType)} - /> - ); - } else if ( - this.state !== null && - this.props.project !== null && - this.state.dialogMode === ProjectItemListDialogType.newBlockTypeDialog - ) { - dialogArea = ( - - } - header={"New Block Type"} - /> - ); } let splitButton = <>; if (this.props.project && !this.props.readOnly && this.props.project.role !== ProjectRole.explorer) { splitButton = ( -
- -
+ ); } diff --git a/app/src/UX/ProjectTile.css b/app/src/UX/ProjectTile.css index a1f74eed..2d79164f 100644 --- a/app/src/UX/ProjectTile.css +++ b/app/src/UX/ProjectTile.css @@ -40,7 +40,7 @@ display: grid; height: 100%; grid-template-columns: 56px 1fr; - grid-template-rows: 1fr 80px 45px; + grid-template-rows: 59px 84px 45px; } .pt-mainArea { @@ -159,7 +159,7 @@ } .pt-ghpath { - padding-bottom: 10px; + padding-bottom: 8px; } .pt-ghlogo { @@ -174,7 +174,7 @@ } .pts-button { - width: 425px; + width: 360px; height: 138px; max-width: calc(100vw - 46px); border: solid 2px #202020; @@ -189,7 +189,7 @@ height: 100%; cursor: pointer; text-align: left; - grid-template-rows: 20px 68px 50px; + grid-template-rows: 88px 1px 50px; } .pts-tag { @@ -248,8 +248,17 @@ grid-column-end: 3; padding: 3px; margin-top: 4px; + height: 60px; + overflow: hidden; padding-left: 8px; padding-right: 8px; - font-size: x-small; - overflow: hidden; + font-size: small; +} + +.pt-docLink { + display: inline-block; +} + +.pts-code-topic { + padding-left: 4px; } diff --git a/app/src/UX/ProjectTile.tsx b/app/src/UX/ProjectTile.tsx index cc50d309..b0ff9366 100644 --- a/app/src/UX/ProjectTile.tsx +++ b/app/src/UX/ProjectTile.tsx @@ -89,33 +89,36 @@ export default class ProjectTile extends Component= 1) { - let curLine = this.props.project.codeLineStart; - - if (curLine === undefined) { - curLine = Math.ceil(lines.length / 2) - 1; - } - - let addedLines = 0; - - while (curLine < lines.length && addedLines < 4) { - if (lines[curLine] && lines[curLine].indexOf("const overworld") <= 0 && lines[curLine].trim().length > 0) { - description.push( -
- {lines[curLine]} -
- ); - addedLines++; - } - - curLine++; + const topics = this.props.project.topics; + + if (topics && topics.length > 0) { + description.push({"Uses"}); + for (var i = 0; i < Math.min(topics.length, 5); i++) { + const topic = topics[i]; + const url = ProjectUtilities.getTopicUrl(topic); + + if (url) { + description.push( + + {topic} + {i < Math.min(topics.length - 1, 4) ? ", " : " "} + + ); + } else { + description.push( + + {topic} + {i < Math.min(topics.length - 1, 4) ? ", " : " "} + + ); } } } @@ -218,7 +221,7 @@ export default class ProjectTile extends Component +

{proj.title}

{tags} -
-
-
- {description} +
+
+ {description} +
+
{additionalButtons} -
diff --git a/app/src/UX/ToolTile.tsx b/app/src/UX/ToolTile.tsx index ac387ad4..538f291f 100644 --- a/app/src/UX/ToolTile.tsx +++ b/app/src/UX/ToolTile.tsx @@ -33,10 +33,6 @@ export default class ToolTile extends Component render() { let imageElement = <>; - // const imagePath = "/test.gif"; - - // imageElement = ; - if (this.props.displayMode === TT_TILE_SMALL) { let outerClassName = "tts-outer"; diff --git a/app/src/UX/WebUtilities.ts b/app/src/UX/WebUtilities.ts index 24517b77..17a152d3 100644 --- a/app/src/UX/WebUtilities.ts +++ b/app/src/UX/WebUtilities.ts @@ -19,6 +19,17 @@ export default class WebUtilities { ); } + static getElementLeft(element: any) { + let left = 0; + + do { + left += (element.offsetLeft as number | undefined) ? (element.offsetLeft as number) : 0; + element = element.offsetParent as Element | undefined; + } while (element); + + return left; + } + static async requestPersistence() { if (!navigator.storage || !navigator.storage.persist) { return false; diff --git a/app/src/app/Carto.ts b/app/src/app/Carto.ts index b7a78470..eda58845 100644 --- a/app/src/app/Carto.ts +++ b/app/src/app/Carto.ts @@ -37,6 +37,7 @@ import ZipStorage from "../storage/ZipStorage"; import { MaxItemTypes, ProjectItemType } from "./IProjectItemData"; import DeploymentStorageMinecraft from "./DeploymentStorageMinecraft"; import MinecraftUtilities from "../minecraft/MinecraftUtilities"; +import { GalleryItemType } from "./IGalleryItem"; export enum CartoMinecraftState { none = 0, @@ -1175,6 +1176,21 @@ export default class Carto { if (this._gallery) { for (const item of this._gallery.items) { + if (item.sampleSet) { + item.gitHubOwner = "microsoft"; + item.gitHubRepoName = "minecraft-scripting-samples"; + + if (item.type === GalleryItemType.editorCodeSample) { + item.gitHubFolder = "/editor-script-box"; + + if (!item.tags) { + item.tags = ["editor"]; + } + } else { + item.gitHubFolder = "/script-box"; + } + } + if (item.fileList) { const newFileList = []; diff --git a/app/src/app/IGalleryItem.ts b/app/src/app/IGalleryItem.ts index 3309cfcd..7ae07f58 100644 --- a/app/src/app/IGalleryItem.ts +++ b/app/src/app/IGalleryItem.ts @@ -27,6 +27,8 @@ export default interface IGalleryItem { thumbnailImage: string; logoImage?: string; localLogo?: string; + sampleSet?: string; + topics?: string[]; logoLocation?: LogoLocation; title: string; description: string; diff --git a/app/src/app/IProjectItemData.ts b/app/src/app/IProjectItemData.ts index 62305997..4c09fe63 100644 --- a/app/src/app/IProjectItemData.ts +++ b/app/src/app/IProjectItemData.ts @@ -29,7 +29,7 @@ export enum ProjectItemType { MCFunction = 9, entityTypeBehavior = 10, entityTypeBaseJs = 11, - actionSetJson = 12, + actionSet = 12, ts = 13, resourcePackManifestJson = 14, worldTest = 15, @@ -38,10 +38,10 @@ export enum ProjectItemType { resourcePackListJson = 18, animationBehaviorJson = 19, animationControllerBehaviorJson = 20, - blockTypeBehaviorJson = 21, + blockTypeBehavior = 21, blockMaterialsBehaviorJson = 22, itemTypeBehaviorJson = 23, - lootTableBehaviorJson = 24, + lootTableBehavior = 24, biomeResourceJson = 25, blocksCatalogResourceJson = 26, soundsCatalogResourceJson = 27, @@ -58,7 +58,7 @@ export enum ProjectItemType { biomeBehaviorJson = 38, dialogueBehaviorJson = 39, featureRuleBehaviorJson = 40, - featureBehaviorJson = 41, + featureBehavior = 41, functionEventJson = 42, recipeBehaviorJson = 43, spawnRuleBehavior = 44, @@ -111,7 +111,7 @@ export enum ProjectItemType { itemTextureJson = 92, terrainTextureCatalogResourceJson = 93, globalVariablesJson = 94, - dataFormJson = 95, + dataForm = 95, dimensionJson = 96, behaviorPackHistoryListJson = 97, resourcePackHistoryListJson = 98, diff --git a/app/src/app/Project.ts b/app/src/app/Project.ts index a0caa1cf..3407639d 100644 --- a/app/src/app/Project.ts +++ b/app/src/app/Project.ts @@ -1315,7 +1315,7 @@ export default class Project { async hasScript() { for (const pi of this.#items) { if ( - pi.itemType === ProjectItemType.actionSetJson || + pi.itemType === ProjectItemType.actionSet || pi.itemType === ProjectItemType.testJs || pi.itemType === ProjectItemType.js || pi.itemType === ProjectItemType.buildProcessedJs || @@ -1729,9 +1729,19 @@ export default class Project { folderContext = FolderContext.metaData; } else if (folderPathA.indexOf("/.vscode") >= 0) { folderContext = FolderContext.vscodeFolder; - } else if (folderPathA.indexOf("/behavior") >= 0 || folderPathA.indexOf("/bp") >= 0) { + } else if ( + folderPathA.indexOf("/behavior") >= 0 || + folderPathA.indexOf("/bp/") >= 0 || + folderPathA.indexOf("/bp ") >= 0 || + folderPathA.indexOf(" bp/") >= 0 + ) { folderContext = FolderContext.behaviorPack; - } else if (folderPathA.indexOf("/resource") >= 0 || folderPathA.indexOf("/rp") >= 0) { + } else if ( + folderPathA.indexOf("/resource") >= 0 || + folderPathA.indexOf("/rp/") >= 0 || + folderPathA.indexOf("/rp ") >= 0 || + folderPathA.indexOf(" rp/") >= 0 + ) { folderContext = FolderContext.resourcePack; } else if (folderPathA.indexOf("/subpacks") >= 0) { folderContext = FolderContext.resourcePackSubPack; @@ -1771,7 +1781,12 @@ export default class Project { folder.folders["ui"] ) { folderContext = FolderContext.resourcePack; - } else if (folder.fullPath.indexOf("rp") >= 0 || folder.fullPath.indexOf("esource") >= 0) { + } else if ( + folder.fullPath.indexOf("/rp/") >= 0 || + folder.fullPath.indexOf(" rp/") >= 0 || + folder.fullPath.indexOf("/rp ") >= 0 || + folder.fullPath.indexOf("esource") >= 0 + ) { folderContext = FolderContext.resourcePack; } else { folderContext = FolderContext.behaviorPack; @@ -2290,7 +2305,7 @@ export default class Project { let itemName = candidateFile.name; if (folderContext === FolderContext.behaviorPack && folderPathLower.indexOf("/loot_tables/") >= 0) { - newJsonType = ProjectItemType.lootTableBehaviorJson; + newJsonType = ProjectItemType.lootTableBehavior; } else if (folderContext === FolderContext.behaviorPack && folderPathLower.indexOf("/dialogue/") >= 0) { newJsonType = ProjectItemType.dialogueBehaviorJson; } else if (folderContext === FolderContext.behaviorPack && folderPathLower.indexOf("/recipes/") >= 0) { @@ -2325,7 +2340,7 @@ export default class Project { } else if (folderContext === FolderContext.behaviorPack && folderPathLower.indexOf("/dimension/") >= 0) { newJsonType = ProjectItemType.dimensionJson; } else if (folderContext === FolderContext.behaviorPack && folderPathLower.indexOf("/features/") >= 0) { - newJsonType = ProjectItemType.featureBehaviorJson; + newJsonType = ProjectItemType.featureBehavior; } else if ( folderContext === FolderContext.behaviorPack && folderPathLower.indexOf("/feature_rules/") >= 0 @@ -2465,7 +2480,7 @@ export default class Project { } else if (folderContext === FolderContext.behaviorPack && folderPathLower.indexOf("/items/") >= 0) { newJsonType = ProjectItemType.itemTypeBehaviorJson; } else if (folderContext === FolderContext.behaviorPack && folderPathLower.indexOf("/blocks/") >= 0) { - newJsonType = ProjectItemType.blockTypeBehaviorJson; + newJsonType = ProjectItemType.blockTypeBehavior; } else if (folderContext === FolderContext.docs && baseName === "info") { newJsonType = ProjectItemType.docInfoJson; this.role = ProjectRole.documentation; @@ -2491,7 +2506,7 @@ export default class Project { (folderContext === FolderContext.metaData && folderPathLower.indexOf("/forms") >= 0) || projectPath.endsWith(".form.json") ) { - newJsonType = ProjectItemType.dataFormJson; + newJsonType = ProjectItemType.dataForm; this.role = ProjectRole.meta; } else if (folderContext === FolderContext.typeDefs && folderPathLower.indexOf("/script_modules") >= 0) { newJsonType = ProjectItemType.scriptTypesJson; diff --git a/app/src/app/ProjectItem.ts b/app/src/app/ProjectItem.ts index 361e01f5..0575b5fd 100644 --- a/app/src/app/ProjectItem.ts +++ b/app/src/app/ProjectItem.ts @@ -301,11 +301,11 @@ export default class ProjectItem { return "behavior/animation_controllers/animation_controller.json"; case ProjectItemType.animationBehaviorJson: return "behavior/animations/animations.json"; - case ProjectItemType.blockTypeBehaviorJson: + case ProjectItemType.blockTypeBehavior: return "behavior/blocks/blocks.json"; case ProjectItemType.itemTypeBehaviorJson: return "behavior/items/items.json"; - case ProjectItemType.lootTableBehaviorJson: + case ProjectItemType.lootTableBehavior: return "behavior/loot_tables/loot_tables.json"; case ProjectItemType.biomeBehaviorJson: return "behavior/blocks/blocks.json"; @@ -337,7 +337,7 @@ export default class ProjectItem { // return "resource/textures/ui_texture_definition.json"; case ProjectItemType.languagesCatalogResourceJson: return "language/languages.json"; - case ProjectItemType.featureBehaviorJson: + case ProjectItemType.featureBehavior: return "behavior/features/features.json"; case ProjectItemType.featureRuleBehaviorJson: return "behavior/feature_rules/feature_rules.json"; @@ -1021,7 +1021,7 @@ export default class ProjectItem { return undefined; } - if (this.itemType === ProjectItemType.actionSetJson) { + if (this.itemType === ProjectItemType.actionSet) { const functionTwinName = StorageUtilities.canonicalizeName( StorageUtilities.getBaseFromName(this._file.name) + ".mcfunction" ); diff --git a/app/src/app/ProjectItemManager.ts b/app/src/app/ProjectItemManager.ts index b902ee29..30df6da4 100644 --- a/app/src/app/ProjectItemManager.ts +++ b/app/src/app/ProjectItemManager.ts @@ -11,6 +11,7 @@ import IProjectItemSeed from "./IProjectItemSeed"; import ProjectItemUtilities from "./ProjectItemUtilities"; import IFolder from "../storage/IFolder"; import ProjectAutogeneration from "./ProjectAutogeneration"; +import ProjectItem from "./ProjectItem"; export default class ProjectItemManager { private static async _getDefaultBehaviorPackPath(project: Project) { @@ -24,6 +25,22 @@ export default class ProjectItemManager { return defaultPath; } + + private static async _getDefaultBehaviorPackFolderPath(project: Project, name: string) { + const bpFolder = await project.ensureDefaultBehaviorPackFolder(); + + if (project.projectFolder === null) { + return undefined; + } + + const subfolder = bpFolder.ensureFolder(name); + await subfolder.ensureExists(); + + const defaultPath = subfolder.getFolderRelativePath(project.projectFolder); + + return defaultPath; + } + private static async _getDefaultScriptsFolderPath(project: Project) { const scriptsFolder = await project.ensureMainScriptsFolder(); @@ -36,7 +53,7 @@ export default class ProjectItemManager { return defaultPath; } - static async createNewItem(project: Project, itemSeed: IProjectItemSeed) { + static async createNewItem(project: Project, itemSeed: IProjectItemSeed): Promise { if (itemSeed.name === "" || itemSeed.name === undefined) { itemSeed.name = ProjectItemUtilities.getNewItemName(itemSeed.itemType); } @@ -46,6 +63,12 @@ export default class ProjectItemManager { case ProjectItemType.js: case ProjectItemType.ts: return ProjectItemManager.createNewScript(project, itemSeed.itemType, itemSeed.name, itemSeed.folder); + + case ProjectItemType.dataForm: + return ProjectItemManager.createNewForm(project, itemSeed.name, itemSeed.folder); + + case ProjectItemType.spawnRuleBehavior: + return ProjectItemManager.createNewSpawnRule(project, itemSeed.name, itemSeed.folder); } } @@ -247,18 +270,13 @@ export default class ProjectItemManager { } static async createNewGameTestScript(project: Project) { - const defaultPath = await ProjectItemManager._getDefaultBehaviorPackPath(project); + const defaultPath = await ProjectItemManager._getDefaultBehaviorPackFolderPath(project, "scripts"); if (defaultPath === undefined) { return; } - const candidateFilePath = await ProjectItemManager._generateFileNameForNewItem( - project, - defaultPath + "scripts/", - "test", - "js" - ); + const candidateFilePath = await ProjectItemManager._generateFileNameForNewItem(project, defaultPath, "test", "js"); if (candidateFilePath === undefined) { return; @@ -280,7 +298,7 @@ export default class ProjectItemManager { const baseName = StorageUtilities.getBaseFromName(file.name); if (project.preferredScriptLanguage === ProjectScriptLanguage.typeScript) { - const tsFile = await file.parentFolder.ensureFile(baseName + ".ts"); + const tsFile = file.parentFolder.ensureFile(baseName + ".ts"); if (tsFile !== null) { const content = ProjectContent.getEmptyTestTypeScript(project.name, baseName); @@ -298,7 +316,7 @@ export default class ProjectItemManager { } static async createNewFunction(project: Project) { - const defaultPath = await this._getDefaultBehaviorPackPath(project); + const defaultPath = await this._getDefaultBehaviorPackFolderPath(project, "functions"); if (defaultPath === undefined) { return; @@ -306,7 +324,7 @@ export default class ProjectItemManager { const candidateFilePath = await ProjectItemManager._generateFileNameForNewItem( project, - defaultPath + "functions/", + defaultPath, "action", "mcfunction" ); @@ -337,7 +355,7 @@ export default class ProjectItemManager { } static async createNewStructure(project: Project) { - const defaultPath = await ProjectItemManager._getDefaultBehaviorPackPath(project); + const defaultPath = await ProjectItemManager._getDefaultBehaviorPackFolderPath(project, "structures"); if (defaultPath === undefined) { return; @@ -345,7 +363,7 @@ export default class ProjectItemManager { const candidateFilePath = await ProjectItemManager._generateFileNameForNewItem( project, - defaultPath + "structures/", + defaultPath, "structure", "mcstructure" ); @@ -377,7 +395,7 @@ export default class ProjectItemManager { } static async createNewActionSet(project: Project) { - const defaultPath = await this._getDefaultBehaviorPackPath(project); + const defaultPath = await this._getDefaultBehaviorPackFolderPath(project, "actions"); if (defaultPath === undefined) { return; @@ -385,7 +403,7 @@ export default class ProjectItemManager { const candidateFilePath = await ProjectItemManager._generateFileNameForNewItem( project, - defaultPath + "scripts/", + defaultPath, "actionset", "json" ); @@ -398,7 +416,7 @@ export default class ProjectItemManager { candidateFilePath, ProjectItemStorageType.singleFile, StorageUtilities.getLeafName(candidateFilePath), - ProjectItemType.actionSetJson, + ProjectItemType.actionSet, undefined, ProjectItemCreationType.normal ); @@ -417,17 +435,13 @@ export default class ProjectItemManager { } static async createNewDocumentedType(project: Project) { - const defaultPath = await this._getDefaultBehaviorPackPath(project); + const defaultPath = await this._getDefaultBehaviorPackFolderPath(project, "docs"); if (defaultPath === undefined) { return; } - const candidateFolderPath = await ProjectItemManager._generateFolderNameForNewItem( - project, - defaultPath + "docs/", - "type" - ); + const candidateFolderPath = await ProjectItemManager._generateFolderNameForNewItem(project, defaultPath, "type"); if (candidateFolderPath === undefined) { return; @@ -449,29 +463,119 @@ export default class ProjectItemManager { await project.save(); } - static async createNewForm(project: Project) { - const defaultPath = await this._getDefaultBehaviorPackPath(project); + static getExistingPath(project: Project, itemType: ProjectItemType) { + if (!project.projectFolder) { + return undefined; + } - if (defaultPath === undefined) { - return; + for (const item of project.items) { + if (item.itemType === itemType && item.file) { + return item.file.parentFolder.getFolderRelativePath(project.projectFolder); + } } - const candidateFilePath = await ProjectItemManager._generateFileNameForNewItem( - project, - defaultPath + "forms/", - "form", - "json" + return undefined; + } + + static async createNewForm(project: Project, name?: string, folder?: IFolder) { + let path: string | undefined = undefined; + + if (!project.projectFolder) { + return undefined; + } + + if (folder) { + path = folder.getFolderRelativePath(project.projectFolder); + } else { + path = ProjectItemManager.getExistingPath(project, ProjectItemType.dataForm); + + if (path === undefined) { + path = await this._getDefaultBehaviorPackFolderPath(project, "forms"); + + if (path === undefined) { + return undefined; + } + } + } + + if (!path) { + return undefined; + } + + if (!name) { + name = "form"; + } + + const candidateFilePath = await ProjectItemManager._generateFileNameForNewItem(project, path, name, "form.json"); + + if (candidateFilePath === undefined) { + return undefined; + } + + const pi = project.ensureItemByProjectPath( + candidateFilePath, + ProjectItemStorageType.singleFile, + StorageUtilities.getLeafName(candidateFilePath), + ProjectItemType.dataForm, + undefined, + ProjectItemCreationType.normal ); + const file = await pi.ensureFileStorage(); + + if (file !== null) { + const content = "{}"; + + file.setContent(content); + } + + await ProjectAutogeneration.updateProjectAutogeneration(project); + + await project.save(); + + return pi; + } + + static async createNewSpawnRule(project: Project, name?: string, folder?: IFolder): Promise { + let path: string | undefined = undefined; + + if (!project.projectFolder) { + return undefined; + } + + if (folder) { + path = folder.getFolderRelativePath(project.projectFolder); + } else { + path = ProjectItemManager.getExistingPath(project, ProjectItemType.spawnRuleBehavior); + + if (path === undefined) { + path = await this._getDefaultBehaviorPackFolderPath(project, "spawn_rules"); + + if (path === undefined) { + return undefined; + } + } + } + + if (!path) { + return undefined; + } + + if (!name) { + name = "spawn_rule"; + } + + const candidateFilePath = await ProjectItemManager._generateFileNameForNewItem(project, path, name, "json"); + if (candidateFilePath === undefined) { - return; + return undefined; } const pi = project.ensureItemByProjectPath( candidateFilePath, ProjectItemStorageType.singleFile, StorageUtilities.getLeafName(candidateFilePath), - ProjectItemType.worldTest, + ProjectItemType.spawnRuleBehavior, undefined, ProjectItemCreationType.normal ); @@ -487,18 +591,42 @@ export default class ProjectItemManager { await ProjectAutogeneration.updateProjectAutogeneration(project); await project.save(); + + return pi; } - static async createNewWorldTest(project: Project) { - const defaultPath = await this._getDefaultBehaviorPackPath(project); + static async createNewWorldTest(project: Project, name?: string, folder?: IFolder) { + let path: string | undefined = undefined; - if (defaultPath === undefined) { + if (!project.projectFolder) { + return; + } + + if (folder) { + path = folder.getFolderRelativePath(project.projectFolder); + } else { + path = ProjectItemManager.getExistingPath(project, ProjectItemType.dataForm); + + if (path === undefined) { + path = await this._getDefaultBehaviorPackFolderPath(project, "forms"); + + if (path === undefined) { + return; + } + } + } + + if (!path) { return; } + if (!name) { + name = "form"; + } + const candidateFilePath = await ProjectItemManager._generateFileNameForNewItem( project, - defaultPath + "scripts/", + path + "tests/", "worldtest", "json" ); diff --git a/app/src/app/ProjectItemTemplates.ts b/app/src/app/ProjectItemTemplates.ts new file mode 100644 index 00000000..d49ad453 --- /dev/null +++ b/app/src/app/ProjectItemTemplates.ts @@ -0,0 +1,13 @@ +export default class ProjectItemTemplates { + spawnRules = [ + "{", + ' format_version: "1.8.0",', + ' "minecraft:spawn_rules": {', + " description: {", + ' identifier: "custom:identifier",', + " },", + " conditions: [],", + " },", + "};", + ]; +} diff --git a/app/src/app/ProjectItemUtilities.ts b/app/src/app/ProjectItemUtilities.ts index 34fe8e82..cb9e4fcb 100644 --- a/app/src/app/ProjectItemUtilities.ts +++ b/app/src/app/ProjectItemUtilities.ts @@ -8,6 +8,7 @@ import StorageUtilities from "../storage/StorageUtilities"; import { ProjectItemCategory, ProjectItemType } from "./IProjectItemData"; import Project from "./Project"; import ProjectItem from "./ProjectItem"; +import ProjectUtilities from "./ProjectUtilities"; export default class ProjectItemUtilities { static inferTypeFromContent( @@ -114,7 +115,7 @@ export default class ProjectItemUtilities { case ProjectItemType.entityTypeBehavior: return 1851; - case ProjectItemType.blockTypeBehaviorJson: + case ProjectItemType.blockTypeBehavior: return 1852; case ProjectItemType.itemTypeBehaviorJson: @@ -152,7 +153,7 @@ export default class ProjectItemUtilities { switch (itemType) { case ProjectItemType.MCFunction: case ProjectItemType.testJs: - case ProjectItemType.actionSetJson: + case ProjectItemType.actionSet: case ProjectItemType.animationBehaviorJson: case ProjectItemType.animationControllerBehaviorJson: case ProjectItemType.tickJson: @@ -196,7 +197,7 @@ export default class ProjectItemUtilities { case ProjectItemType.entityTypeResource: case ProjectItemType.entityTypeBaseJs: case ProjectItemType.entityTypeBaseTs: - case ProjectItemType.blockTypeBehaviorJson: + case ProjectItemType.blockTypeBehavior: case ProjectItemType.blocksCatalogResourceJson: case ProjectItemType.blockTypeResourceJson: case ProjectItemType.itemTypeBehaviorJson: @@ -207,13 +208,13 @@ export default class ProjectItemUtilities { case ProjectItemType.recipeBehaviorJson: case ProjectItemType.biomeBehaviorJson: case ProjectItemType.biomeResourceJson: - case ProjectItemType.lootTableBehaviorJson: + case ProjectItemType.lootTableBehavior: case ProjectItemType.spawnRuleBehavior: case ProjectItemType.dialogueBehaviorJson: case ProjectItemType.MCWorld: case ProjectItemType.worldTemplateManifestJson: case ProjectItemType.itemTypeResourceJson: - case ProjectItemType.featureBehaviorJson: + case ProjectItemType.featureBehavior: case ProjectItemType.featureRuleBehaviorJson: return ProjectItemCategory.types; @@ -315,16 +316,16 @@ export default class ProjectItemUtilities { case ProjectItemType.entityTypeBehavior: case ProjectItemType.tickJson: case ProjectItemType.cameraJson: - case ProjectItemType.actionSetJson: + case ProjectItemType.actionSet: case ProjectItemType.worldTest: case ProjectItemType.behaviorPackListJson: case ProjectItemType.resourcePackListJson: case ProjectItemType.animationBehaviorJson: case ProjectItemType.animationControllerBehaviorJson: - case ProjectItemType.blockTypeBehaviorJson: + case ProjectItemType.blockTypeBehavior: case ProjectItemType.blockMaterialsBehaviorJson: case ProjectItemType.itemTypeBehaviorJson: - case ProjectItemType.lootTableBehaviorJson: + case ProjectItemType.lootTableBehavior: case ProjectItemType.biomeResourceJson: case ProjectItemType.fileListArrayJson: case ProjectItemType.blocksCatalogResourceJson: @@ -342,7 +343,7 @@ export default class ProjectItemUtilities { case ProjectItemType.biomeBehaviorJson: case ProjectItemType.dialogueBehaviorJson: case ProjectItemType.featureRuleBehaviorJson: - case ProjectItemType.featureBehaviorJson: + case ProjectItemType.featureBehavior: case ProjectItemType.functionEventJson: case ProjectItemType.recipeBehaviorJson: case ProjectItemType.spawnRuleBehavior: @@ -380,7 +381,7 @@ export default class ProjectItemUtilities { case ProjectItemType.itemTextureJson: case ProjectItemType.terrainTextureCatalogResourceJson: case ProjectItemType.globalVariablesJson: - case ProjectItemType.dataFormJson: + case ProjectItemType.dataForm: case ProjectItemType.dimensionJson: case ProjectItemType.behaviorPackHistoryListJson: case ProjectItemType.resourcePackHistoryListJson: @@ -485,7 +486,7 @@ export default class ProjectItemUtilities { return "Resource pack"; case ProjectItemType.skinPackFolder: return "Skin pack"; - case ProjectItemType.actionSetJson: + case ProjectItemType.actionSet: return "Action Set"; case ProjectItemType.worldTest: return "World test"; @@ -497,13 +498,13 @@ export default class ProjectItemUtilities { return "Behavior pack animation"; case ProjectItemType.animationControllerBehaviorJson: return "Behavior pack animation controller"; - case ProjectItemType.blockTypeBehaviorJson: + case ProjectItemType.blockTypeBehavior: return "Block type"; case ProjectItemType.blockMaterialsBehaviorJson: return "Block type materials"; case ProjectItemType.itemTypeBehaviorJson: return "Item type"; - case ProjectItemType.lootTableBehaviorJson: + case ProjectItemType.lootTableBehavior: return "Loot table"; case ProjectItemType.biomeResourceJson: return "Biome resources"; @@ -537,7 +538,7 @@ export default class ProjectItemUtilities { return "Entity dialogue"; case ProjectItemType.featureRuleBehaviorJson: return "World feature rule"; - case ProjectItemType.featureBehaviorJson: + case ProjectItemType.featureBehavior: return "Feature"; case ProjectItemType.functionEventJson: return "Function event"; @@ -641,7 +642,7 @@ export default class ProjectItemUtilities { return "Terrain texture"; case ProjectItemType.globalVariablesJson: return "UI global variables"; - case ProjectItemType.dataFormJson: + case ProjectItemType.dataForm: return "Form"; case ProjectItemType.dimensionJson: return "Dimension"; @@ -655,7 +656,7 @@ export default class ProjectItemUtilities { } static getNewItemName(type: ProjectItemType) { - return ProjectItemUtilities.getDescriptionForType(type); + return "New " + ProjectItemUtilities.getDescriptionForType(type).toLowerCase(); } static getColorForType(type: ProjectItemType): IColor { @@ -762,6 +763,12 @@ export default class ProjectItemUtilities { return defaultRpFolder.ensureFolderFromRelativePath(path[0]); + case ProjectItemType.spawnRuleBehavior: + return await ProjectUtilities.getDefaultSpawnRulesFolder(project); + + case ProjectItemType.lootTableBehavior: + return await ProjectUtilities.getDefaultLootTableFolder(project); + case ProjectItemType.uiTexture: const defaultRpFolderA = await project.getDefaultResourcePackFolder(); @@ -824,13 +831,13 @@ export default class ProjectItemUtilities { case ProjectItemType.itemTypeBehaviorJson: case ProjectItemType.itemTypeResourceJson: return ["items"]; - case ProjectItemType.blockTypeBehaviorJson: + case ProjectItemType.blockTypeBehavior: return ["blocks"]; case ProjectItemType.documentedTypeFolder: return ["script_modules"]; case ProjectItemType.commandSetDefinitionJson: return ["command_modules"]; - case ProjectItemType.lootTableBehaviorJson: + case ProjectItemType.lootTableBehavior: return ["loot_tables"]; case ProjectItemType.recipeBehaviorJson: return ["recipes"]; @@ -849,7 +856,7 @@ export default class ProjectItemUtilities { return ["dimensions"]; case ProjectItemType.fogResourceJson: return ["fogs"]; - case ProjectItemType.dataFormJson: + case ProjectItemType.dataForm: return ["forms"]; case ProjectItemType.scriptTypesJson: return ["checkpoint_input", "script_modules"]; diff --git a/app/src/app/ProjectUtilities.ts b/app/src/app/ProjectUtilities.ts index 5c0d1f36..f57924ab 100644 --- a/app/src/app/ProjectUtilities.ts +++ b/app/src/app/ProjectUtilities.ts @@ -550,6 +550,34 @@ export default class ProjectUtilities { } } + static async getDefaultSpawnRulesFolder(project: Project) { + const bpFolder = await project.getDefaultBehaviorPackFolder(); + + if (!bpFolder) { + return undefined; + } + + const srFolder = bpFolder.ensureFolder("spawn_rules"); + + await srFolder.ensureExists(); + + return srFolder; + } + + static async getDefaultLootTableFolder(project: Project) { + const bpFolder = await project.getDefaultBehaviorPackFolder(); + + if (!bpFolder) { + return undefined; + } + + const ltFolder = bpFolder.ensureFolder("loot_table"); + + await ltFolder.ensureExists(); + + return ltFolder; + } + static async getIsAddon(project: Project) { const itemsCopy = project.getItemsCopy(); let rpCount = 0; @@ -946,28 +974,23 @@ export default class ProjectUtilities { return content; } - static getSnippet(snippetId: string) { - if (Database.snippetsFolder !== null && Database.snippetsFolder.files) { - for (const fileName in Database.snippetsFolder.files) { - const file = Database.snippetsFolder.files[fileName]; - - if (file) { - const snipSet = StorageUtilities.getJsonObject(file) as { [snippetName: string]: ISnippet }; - - if (snipSet && snipSet[snippetId]) { - return snipSet[snippetId]; - } - } - } - } - - return undefined; - } - static CodeReplaceTokens = ["say Hello", 'sendMessage("Hello world']; static ImportTypes = { - vanilla: ["MinecraftDimensionTypes", "MinecraftBlockTypes", "MinecraftItemTypes", "MinecraftEntityTypes"], + vanilla: [ + "MinecraftDimensionTypes", + "MinecraftBlockTypes", + "MinecraftItemTypes", + "MinecraftEntityTypes", + "MinecraftEffectTypes", + "MinecraftEnchantmentTypes", + "MinecraftBiomeTypes", + "MinecraftFeatureTypes", + "MinecraftPotionEffectTypes", + "MinecraftPotionLiquidTypes", + "MinecraftPotionModifierTypes", + "MinecraftCooldownCategoryTypes", + ], math: ["Vector3Utils"], mcui: [ "MessageFormResponse", @@ -981,8 +1004,13 @@ export default class ProjectUtilities { "system", "BlockPermutation", "BlockSignComponent", + "CompoundBlockVolume", "SignSide", "DyeColor", + "ItemDurabilityComponent", + "RawMessage", + "RawText", + "EntityProjectileComponent", "EntityQueryOptions", "ButtonPushAfterEvent", "ItemStack", @@ -1004,15 +1032,68 @@ export default class ProjectUtilities { "PlayerSoundOptions", "DisplaySlotId", "ObjectiveSortOrder", - "TripWireAfterEvent", + "TripWireTripAfterEvent", "BlockComponentTypes", "EntityComponentTypes", "ItemComponentTypes", "LeverActionAfterEvent", "Vector3", ], + mcgt: ["Test", "register"], + mcsa: ["secrets", "variables"], + mcnet: ["http", "HttpRequest", "HttpResponse", "HttpRequestMethod", "HttpHeader"], + mced: [ + "IPlayerUISession", + "ExtensionContext", + "IModalToolContainer", + "registerEditorExtension", + "ActionManager", + "IModalTool", + "ActionTypes", + "MouseProps", + "MouseActionType", + "MouseInputType", + "KeyboardKey", + "InputModifier", + "EditorInputContext", + "IPropertyPane", + ], }; + static getTopicUrl(topic: string) { + const tokens = topic.split("."); + + if (tokens.length < 1) { + return undefined; + } + + if ( + this.ImportTypes.mc.includes(tokens[0]) || + tokens[0] === "World" || + tokens[0] === "System" || + tokens[0] === "Dimension" + ) { + return ( + "https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server/" + + topic.toLowerCase().replace(".", "#") + ); + } + if (this.ImportTypes.mcui.includes(tokens[0])) { + return ( + "https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-ui/" + + topic.toLowerCase().replace(".", "#") + ); + } + if (this.ImportTypes.mced.includes(tokens[0])) { + return ( + "https://learn.microsoft.com/minecraft/creator/scriptapi/minecraft/server-editor/" + + topic.toLowerCase().replace(".", "#") + ); + } + + return undefined; + } + static adaptFullSample(content: string) { const registerFunction = content.indexOf("export function register"); @@ -1036,6 +1117,14 @@ export default class ProjectUtilities { sampleContent = sampleContent.replace(/mcui./gi, ""); } + if (sampleContent.indexOf(" mcnet.") >= 0 && fileContent.indexOf(" as mcnet") <= 0) { + sampleContent = sampleContent.replace(/mcnet./gi, ""); + } + + if (sampleContent.indexOf(" mcsa.") >= 0 && fileContent.indexOf(" as mcsa") <= 0) { + sampleContent = sampleContent.replace(/mcsa./gi, ""); + } + return sampleContent; } @@ -1084,6 +1173,27 @@ export default class ProjectUtilities { this.ImportTypes.vanilla ); + introContent = ProjectUtilities.ensureImportLines( + introContent, + restOfContent, + "@minecraft/server-admin", + this.ImportTypes.mcsa + ); + + introContent = ProjectUtilities.ensureImportLines( + introContent, + restOfContent, + "@minecraft/server-net", + this.ImportTypes.mcnet + ); + + introContent = ProjectUtilities.ensureImportLines( + introContent, + restOfContent, + "@minecraft/server-gametest", + this.ImportTypes.mcgt + ); + introContent = ProjectUtilities.ensureImportLines( introContent, restOfContent, @@ -1260,7 +1370,7 @@ export default class ProjectUtilities { candidateFilePath, ProjectItemStorageType.singleFile, nextBlockTypeName, - ProjectItemType.blockTypeBehaviorJson, + ProjectItemType.blockTypeBehavior, undefined, ProjectItemCreationType.normal ); diff --git a/app/src/core/StandardInit.ts b/app/src/core/StandardInit.ts index 808b9c6e..f136ec58 100644 --- a/app/src/core/StandardInit.ts +++ b/app/src/core/StandardInit.ts @@ -6,6 +6,13 @@ export const minecraftToolDarkTheme = { bodyFontFamily: '"Noto Sans", "Segoe UI", system-ui, "Apple Color Emoji", "Segoe UI Emoji", sans-serif', colorScheme: { brand: { + mc0: "#131313", + mc1: "#212121", + mc2: "#212121", + mc3: "#373737", + mc4: "#3a3a3a", + mc5: "#4e4e4e", + mcc1: "#ffffff", background: "#3c8527", // accent background for things like buttons. complemented with fore*4 background1: "#312f2d", // main outermost background. in a dark theme, this should be dark. complemented with fore*1 background2: "#484644", @@ -96,6 +103,13 @@ export const minecraftToolLightTheme = { bodyFontFamily: '"Noto Sans", "Segoe UI", system-ui, "Apple Color Emoji", "Segoe UI Emoji", sans-serif', colorScheme: { brand: { + mc0: "#131313", + mc1: "#595759", + mc2: "#656465", + mc3: "#C0BFC0", + mc4: "#C6C6C6", + mc5: "#f7f7f7", + mcc1: "#4C4C4C", background: "#6bc349", // accent background for things like buttons. complemented with fore*4 background1: "#e6e4e2", // main outermost background. in a dark theme, this should be dark. complemented with fore*1 background2: "#cccac7", // complement background. in a dark theme, this should be dark, but slightly different vs back*1. complemented with fore*2 diff --git a/app/src/dataform/DataForm.css b/app/src/dataform/DataForm.css index 3b0aec7d..b59653ab 100644 --- a/app/src/dataform/DataForm.css +++ b/app/src/dataform/DataForm.css @@ -122,7 +122,10 @@ .df-elementBin { padding: 10px; margin-left: 10px; + min-height: 121px; border: inset 1px; + max-height: calc(100vh - 300px); + overflow-y: auto; } .df-stringArray { @@ -193,3 +196,42 @@ .df-ro-table { display: table; } + +.df-keyedBooleanCollection { + display: grid; +} + +.df-keyedBooleanCollectionTitle { + grid-column: 1; +} + +.df-keyedBooleanCollectionData { + grid-column: 2; +} + +.df-keyedBooleanCollectionClose { + grid-column: 3; +} + +.df-keyedStringCollection { + display: grid; + grid-template-columns: 220px 1fr 100px; +} + +.df-keyedStringCollectionTitle { + grid-column: 1; +} + +.df-keyedStringCollectionData { + grid-column: 2; +} + +.df-keyedStringCollectionData input { + width: 500px; + max-width: calc(100vw - 550px); + margin-bottom: 1px; +} + +.df-keyedStringCollectionClose { + grid-column: 3; +} diff --git a/app/src/dataform/DataForm.tsx b/app/src/dataform/DataForm.tsx index 5b79224c..aa516b4e 100644 --- a/app/src/dataform/DataForm.tsx +++ b/app/src/dataform/DataForm.tsx @@ -17,6 +17,8 @@ import { Slider, SliderProps, ThemeInput, + CheckboxProps, + Button, } from "@fluentui/react-northstar"; import Log from "./../core/Log"; import Point3, { IPoint3Props } from "./Point3"; @@ -38,6 +40,7 @@ export interface IDataFormProps extends IDataContainer { displayTitle?: boolean; displaySubTitle?: boolean; title?: string; + titleFieldBinding?: string; subTitle?: string; indentLevel?: number; tag?: any; @@ -56,6 +59,7 @@ export interface IDataFormProps extends IDataContainer { interface IDataFormState { objectIncrement: number; + keyAliases: { [name: string]: string }; } export default class DataForm extends Component { @@ -85,9 +89,20 @@ export default class DataForm extends Component this._handleIndexedArraySubFormPropertyChange = this._handleIndexedArraySubFormPropertyChange.bind(this); this._handleKeyedObjectArraySubFormPropertyChange = this._handleKeyedObjectArraySubFormPropertyChange.bind(this); this._handleObjectSubFormPropertyChange = this._handleObjectSubFormPropertyChange.bind(this); + this._handleKeyedObjectArraySubFormClose = this._handleKeyedObjectArraySubFormClose.bind(this); + this._handleIndexedArraySubFormClose = this._handleIndexedArraySubFormClose.bind(this); + this._addKeyedBooleanItem = this._addKeyedBooleanItem.bind(this); + this._addKeyedStringItem = this._addKeyedStringItem.bind(this); + this._handleKeyedBooleanTextChange = this._handleKeyedBooleanTextChange.bind(this); + this._handleKeyedBooleanValueChange = this._handleKeyedBooleanValueChange.bind(this); + this._handleKeyedBooleanValueClose = this._handleKeyedBooleanValueClose.bind(this); + this._handleKeyedStringTextChange = this._handleKeyedStringTextChange.bind(this); + this._handleKeyedStringValueChange = this._handleKeyedStringValueChange.bind(this); + this._handleKeyedStringValueClose = this._handleKeyedStringValueClose.bind(this); this.state = { objectIncrement: 0, + keyAliases: {}, }; } @@ -102,7 +117,14 @@ export default class DataForm extends Component } _handleBlockChanged() { - this.forceUpdate(); + this._doUpdate(); + } + + _doUpdate() { + this.setState({ + objectIncrement: this.state.objectIncrement + 1, + keyAliases: this.state.keyAliases, + }); } _getObjectId() { @@ -249,6 +271,7 @@ export default class DataForm extends Component this._fixupDirectObject(dirObj); } + Log.assert( dirObj !== undefined || protogsObj !== undefined || protoObj !== undefined, "Could not find a backing object to edit." @@ -304,6 +327,196 @@ export default class DataForm extends Component this.processInputUpdate(event, data?.value); } + _handleKeyedBooleanTextChange( + event: SyntheticEvent | React.KeyboardEvent | null, + data: (InputProps & { value: string }) | undefined + ) { + if (!data || !data.id) { + return; + } + + const keySplit = data.id.split("."); + + if (keySplit.length !== 3) { + Log.unexpectedState("DFKBTFC1"); + return; + } + + const arrayOfDataVal = this._getProperty(keySplit[0], []); + const field = this._getFieldById(keySplit[0]); + + if (field === undefined) { + Log.unexpectedUndefined("DFKBTFC3"); + return; + } + + const val = arrayOfDataVal[keySplit[1]]; + arrayOfDataVal[keySplit[1]] = undefined; + + arrayOfDataVal[data.value] = val; + + const keyAliases = this.state.keyAliases; + keyAliases[data.value] = this.state.keyAliases[keySplit[1]] ? this.state.keyAliases[keySplit[1]] : keySplit[1]; + + this.setState({ + objectIncrement: this.state.objectIncrement, + keyAliases: keyAliases, + }); + } + + _handleKeyedBooleanValueChange( + event: SyntheticEvent | React.KeyboardEvent | null, + data?: CheckboxProps & { checked: boolean; id?: string } + ) { + if (!data || !data.id) { + return; + } + + const keySplit = data.id.split("."); + + if (keySplit.length !== 3) { + Log.unexpectedState("DFKBVFC1"); + return; + } + + const arrayOfDataVal = this._getProperty(keySplit[0], []); + const field = this._getFieldById(keySplit[0]); + + if (field === undefined) { + Log.unexpectedUndefined("DFKBVFC3"); + return; + } + + arrayOfDataVal[keySplit[1]] = data.checked; + + this.forceUpdate(); + } + + _handleKeyedBooleanValueClose(event: React.SyntheticEvent, data?: ButtonProps & { id?: string }) { + if (!data || !data.id) { + return; + } + + if (this.props.onClose) { + this.props.onClose(this.props); + } + + const keySplit = data.id.split("."); + + if (keySplit.length !== 3) { + Log.unexpectedState("DFKBVFC1"); + return; + } + + const arrayOfDataVal = this._getProperty(keySplit[0], []); + const field = this._getFieldById(keySplit[0]); + + if (field === undefined) { + Log.unexpectedUndefined("DFKBVFC3"); + return; + } + + arrayOfDataVal[keySplit[1]] = undefined; + + this.forceUpdate(); + } + + _handleKeyedStringTextChange( + event: SyntheticEvent | React.KeyboardEvent | null, + data: (InputProps & { value: string }) | undefined + ) { + if (!data || !data.id) { + return; + } + + const keySplit = data.id.split("."); + + if (keySplit.length !== 3) { + Log.unexpectedState("DFKBTFC1"); + return; + } + + const arrayOfDataVal = this._getProperty(keySplit[0], []); + const field = this._getFieldById(keySplit[0]); + + if (field === undefined) { + Log.unexpectedUndefined("DFKBTFC3"); + return; + } + + const val = arrayOfDataVal[keySplit[1]]; + arrayOfDataVal[keySplit[1]] = undefined; + + arrayOfDataVal[data.value] = val; + + const keyAliases = this.state.keyAliases; + keyAliases[data.value] = this.state.keyAliases[keySplit[1]] ? this.state.keyAliases[keySplit[1]] : keySplit[1]; + + this.setState({ + objectIncrement: this.state.objectIncrement, + keyAliases: keyAliases, + }); + + this.forceUpdate(); + } + + _handleKeyedStringValueChange( + event: SyntheticEvent | React.KeyboardEvent | null, + data?: (InputProps & { value: string }) | undefined + ) { + if (!data || !data.id) { + return; + } + + const keySplit = data.id.split("."); + + if (keySplit.length !== 3) { + Log.unexpectedState("DFKBVFC1"); + return; + } + + const arrayOfDataVal = this._getProperty(keySplit[0], []); + const field = this._getFieldById(keySplit[0]); + + if (field === undefined) { + Log.unexpectedUndefined("DFKBVFC3"); + return; + } + + arrayOfDataVal[keySplit[1]] = data.value; + + this.forceUpdate(); + } + + _handleKeyedStringValueClose(event: React.SyntheticEvent, data?: ButtonProps & { id?: string }) { + if (!data || !data.id) { + return; + } + + if (this.props.onClose) { + this.props.onClose(this.props); + } + + const keySplit = data.id.split("."); + + if (keySplit.length !== 3) { + Log.unexpectedState("DFKBVFC1"); + return; + } + + const arrayOfDataVal = this._getProperty(keySplit[0], []); + const field = this._getFieldById(keySplit[0]); + + if (field === undefined) { + Log.unexpectedUndefined("DFKBVFC3"); + return; + } + + arrayOfDataVal[keySplit[1]] = undefined; + + this.forceUpdate(); + } + _getTypedData(field: IField, value: any) { if ( field.dataType === FieldDataType.int || @@ -396,7 +609,7 @@ export default class DataForm extends Component this.props.onPropertyChanged(this.props, { id: id, value: val }, val); } - this.forceUpdate(); + this._doUpdate(); } _handleDropdownChange( @@ -448,7 +661,7 @@ export default class DataForm extends Component } } - this.forceUpdate(); + this._doUpdate(); } } @@ -481,6 +694,51 @@ export default class DataForm extends Component } } + _handleKeyedObjectArraySubFormClose(props: IDataFormProps) { + const formId = props.formId; + + if (formId === undefined) { + Log.unexpectedState("DFKOASFC1"); + return; + } + + const lastPeriod = formId.lastIndexOf("."); + + if (lastPeriod < 0) { + Log.unexpectedState("DFKOASFC1"); + return; + } + + const objectFieldIndex: string = formId.substring(lastPeriod + 1); + const fieldId = formId.substring(0, lastPeriod); + + if (fieldId === undefined || objectFieldIndex === undefined) { + Log.unexpectedUndefined("DFKOASFC2"); + return; + } + + const arrayOfDataVal = this._getProperty(fieldId, []); + const field = this._getFieldById(fieldId); + + if (field === undefined || field.objectArrayToSubFieldKey === undefined) { + Log.unexpectedUndefined("DFKOASFC3"); + return; + } + + const dataVal = this.getObjectWithFieldIndex(arrayOfDataVal, field.objectArrayToSubFieldKey, objectFieldIndex); + + if (dataVal === undefined) { + Log.unexpectedUndefined("DFKOASFC4"); + return; + } + + // dataVal[property.id] = newValue; + + if (this.props.onPropertyChanged !== undefined) { + // this.props.onPropertyChanged(this.props, { id: fieldId, value: newValue }, newValue); + } + } + _handleKeyedObjectArraySubFormPropertyChange(props: IDataFormProps, property: IProperty, newValue: any) { const formId = props.formId; @@ -526,6 +784,61 @@ export default class DataForm extends Component } } + _handleIndexedArraySubFormClose(props: IDataFormProps) { + const formId = props.formId; + + if (formId === undefined) { + Log.unexpectedState("DFIASFC1"); + return; + } + + const lastPeriod = formId.lastIndexOf("."); + + if (lastPeriod < 0) { + Log.unexpectedState("DFIASFC1"); + return; + } + + const objectFieldIndex: string = formId.substring(lastPeriod + 1); + const fieldId = formId.substring(0, lastPeriod); + + if (fieldId === undefined || objectFieldIndex === undefined) { + Log.unexpectedUndefined("DFIASFC2"); + return; + } + + const arrayOfDataVal = this._getProperty(fieldId, []); + const field = this._getFieldById(fieldId); + + if (field === undefined) { + Log.unexpectedUndefined("DFIASFC3"); + return; + } + + try { + const arrayIndex = parseInt(objectFieldIndex); + if (isNaN(arrayIndex)) { + Log.unexpectedUndefined("DFIASFC4"); + return; + } + + const newArr = []; + + for (let i = 0; i < arrayOfDataVal.length; i++) { + if (i !== arrayIndex) { + newArr.push(arrayOfDataVal[i]); + } + } + + this._setPropertyValue(fieldId, newArr); + this._doUpdate(); + } catch (e) { + Log.unexpectedUndefined("DFIASFC5"); + + return; + } + } + _handleIndexedArraySubFormPropertyChange(props: IDataFormProps, property: IProperty, newValue: any) { const propId = props.formId; @@ -901,8 +1214,12 @@ export default class DataForm extends Component ); } else if (field.dataType === FieldDataType.keyedObjectCollection) { this.addKeyedObjectArrayComponent(field, formInterior, descriptionElement); + } else if (field.dataType === FieldDataType.keyedStringArrayCollection) { + this.addKeyedStringArrayCollectionComponent(field, formInterior, descriptionElement); } else if (field.dataType === FieldDataType.keyedStringCollection) { - this.addKeyedStringArrayComponent(field, formInterior, descriptionElement); + this.addKeyedStringComponent(field, formInterior, descriptionElement); + } else if (field.dataType === FieldDataType.keyedBooleanCollection) { + this.addKeyedBooleanComponent(field, formInterior, descriptionElement); } else if (field.dataType === FieldDataType.objectArray) { this.addObjectArrayComponent(field, formInterior, descriptionElement); } else if (field.dataType === FieldDataType.object) { @@ -1004,6 +1321,10 @@ export default class DataForm extends Component title = this.props.title; } + if (this.props.titleFieldBinding) { + title = this._getProperty(this.props.titleFieldBinding, title); + } + if (this.props.indentLevel || this.props.defaultVisualExperience === FieldVisualExperience.deemphasized) { header.push(
@@ -1195,6 +1516,7 @@ export default class DataForm extends Component displayTitle={true} indentLevel={indentLevel} onPropertyChanged={this._handleIndexedArraySubFormPropertyChange} + onClose={this._handleIndexedArraySubFormClose} definition={field.subForm} readOnly={this.props.readOnly} /> @@ -1227,7 +1549,7 @@ export default class DataForm extends Component ); } - addKeyedStringArrayComponent(field: IField, formInterior: any[], descriptionElement: JSX.Element) { + addKeyedStringArrayCollectionComponent(field: IField, formInterior: any[], descriptionElement: JSX.Element) { const val = this._getProperty(field.id, {}); const fieldInterior = []; const childElements = []; @@ -1304,6 +1626,298 @@ export default class DataForm extends Component ); } + addKeyedStringComponent(field: IField, formInterior: any[], descriptionElement: JSX.Element) { + const val = this._getProperty(field.id, {}); + const fieldInterior = []; + const childElements = []; + const fieldTopper = []; + + Log.assert(val !== undefined, "Keyed string boolean not available in data form."); + + if (val) { + const keys = []; + + const headerElement =
{FieldUtilities.getFieldTitle(field)}
; + this.formComponentNames.push(field.id); + this.formComponents.push(headerElement); + fieldTopper.push(headerElement); + + if (!this.props.readOnly) { + const toolbarItems = []; + + toolbarItems.push({ + icon: , + key: "add", + tag: field.id, + onClick: this._addKeyedStringItem, + title: "Add item", + }); + + const toolBarElement = ( +
+ +
+ ); + this.formComponentNames.push(field.id + "toolbar"); + this.formComponents.push(toolBarElement); + fieldTopper.push(toolBarElement); + } + + let index = 0; + + const valList = []; + + for (const key in val) { + if (this.state.keyAliases[key]) { + valList.push(this.state.keyAliases[key] + " |" + key); + } else { + valList.push(key); + } + } + + valList.sort(); + + for (let key of valList) { + const lastPeriod = key.lastIndexOf("|"); + + if (lastPeriod >= 0) { + key = key.substring(lastPeriod + 1); + } + + let strVal = val[key] as string | undefined; + + if (strVal !== undefined) { + keys.push(key); + + let objKey = field.id; + + if (this.props.objectKey) { + objKey += this.props.objectKey; + } + + objKey += "." + index; + + let title =
{key}
; + + if (!this.props.readOnly) { + title = ( + + ); + } + + let propertyId = field.id; + + propertyId += "." + key; + + const inputControl = ( + + ); + + this.formComponentNames.push(propertyId); + this.formComponents.push(inputControl); + + const closeRow = ( +