From 126f5a37cced61bf02b35daed432253c07394d0f Mon Sep 17 00:00:00 2001 From: Muffin Date: Tue, 25 Jul 2023 23:16:03 -0500 Subject: [PATCH] Include extension list in exported .sprite3 files closes https://github.com/TurboWarp/extensions/issues/767 --- src/serialization/sb3.js | 44 +++++++++++++++------ test/integration/tw_serialize_extensions.js | 33 +++++++++++----- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js index 59d2e1c9834..444b15bf667 100644 --- a/src/serialization/sb3.js +++ b/src/serialization/sb3.js @@ -610,24 +610,16 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {}) const serializedTargets = flattenedOriginalTargets.map(t => serializeTarget(t, extensions)); - if (targetId) { - return serializedTargets[0]; - } - - obj.targets = serializedTargets; - - obj.monitors = serializeMonitors(runtime.getMonitorState(), runtime, extensions); - - // Assemble extension list - obj.extensions = Array.from(extensions); + const serialiedExtensionList = Array.from(extensions); // Save list of URLs to load the current extensions // Extension manager only exists when runtime is wrapped by VirtualMachine + let serializedExtensionURLs = null; if (runtime.extensionManager) { // We'll save the extensions in the format: // { - // "extension_id": "https://...", - // "other_id": "https://..." + // "extensionid": "https://...", + // "otherid": "https://..." // } // Which lets the VM know which URLs correspond to which IDs, which is useful when the project // is being loaded. For example, if the extension is eventually converted to a builtin extension @@ -642,10 +634,36 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {}) } // Only save this object if any URLs would actually be saved. if (Object.keys(urlsToSave).length !== 0) { - obj.extensionURLs = urlsToSave; + serializedExtensionURLs = urlsToSave; } } + if (targetId) { + const target = serializedTargets[0]; + + // Scratch doesn't save extensions for sprites, so don't add if not needed. + if (serialiedExtensionList.length) { + target.extensions = serialiedExtensionList; + } + if (serializedExtensionURLs) { + target.extensionURLs = serializedExtensionURLs; + } + + return serializedTargets[0]; + } + + // This is part of Scratch + obj.extensions = serialiedExtensionList; + + // This is not part of Scratch, so don't add if not needed. + if (serializedExtensionURLs) { + obj.extensionURLs = serializedExtensionURLs; + } + + obj.targets = serializedTargets; + + obj.monitors = serializeMonitors(runtime.getMonitorState(), runtime, extensions); + // Assemble metadata const meta = Object.create(null); meta.semver = '3.0.0'; diff --git a/test/integration/tw_serialize_extensions.js b/test/integration/tw_serialize_extensions.js index c9cd77b4e5b..1ada702e832 100644 --- a/test/integration/tw_serialize_extensions.js +++ b/test/integration/tw_serialize_extensions.js @@ -4,6 +4,8 @@ const RenderedTarget = require('../../src/sprites/rendered-target'); const Sprite = require('../../src/sprites/sprite'); test('Serializes custom extensions', t => { + t.plan(6); + const vm = new VirtualMachine(); // Trick the extension manager into thinking a couple extensions are loaded. @@ -13,21 +15,34 @@ test('Serializes custom extensions', t => { vm.extensionManager._loadedExtensions.set('test1', 'test.0.0'); vm.extensionManager._loadedExtensions.set('test2', 'test.1.0'); - // Create a block that uses the first extension - const sprite = new Sprite(null, vm.runtime); - const target = new RenderedTarget(sprite, vm.runtime); - target.blocks.createBlock({ + const targetUsingBlock = new RenderedTarget(new Sprite(null, vm.runtime), vm.runtime); + vm.runtime.addTarget(targetUsingBlock); + targetUsingBlock.blocks.createBlock({ id: 'a', opcode: 'test1_something' }); - vm.runtime.addTarget(target); + + const targetNotUsingBlock = new RenderedTarget(new Sprite(null, vm.runtime), vm.runtime); + vm.runtime.addTarget(targetNotUsingBlock); // test2 isn't used, so it shouldn't be included in the JSON - const serialized = JSON.parse(vm.toJSON()); - t.same(serialized.extensions, ['test1']); - t.same(serialized.extensionURLs, { + + const serializedProject = JSON.parse(vm.toJSON()); + t.same(serializedProject.extensions, ['test1'], 'save extension IDs for project'); + t.same(serializedProject.extensionURLs, { test1: 'https://example.com/test1.js' - }); + }, 'save extension URLs for project'); + + const serializedTargetWithBlock = JSON.parse(vm.toJSON(targetUsingBlock.id)); + t.same(serializedTargetWithBlock.extensions, ['test1'], 'save extension IDs for sprite'); + t.same(serializedTargetWithBlock.extensionURLs, { + test1: 'https://example.com/test1.js' + }, 'save extension URLs for sprite'); + + // other sprite uses no extensions, so don't want extension stuff in the JSON + const serializedTargetWithoutBlock = JSON.parse(vm.toJSON(targetNotUsingBlock.id)); + t.notOk('extensions' in serializedTargetWithoutBlock, 'dont save extension IDs for empty sprite'); + t.notOk('extensionURLs' in serializedTargetWithoutBlock, 'dont save extension URLs for empty sprite'); t.end(); });