mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
Merge branch 'feature/194' of https://github.com/morantsur/scratch-vm into develop
+ Solve code conflicts, and handle deserialization of 3.0 projects. # Conflicts: # src/index.js # test/unit/serialization_sb2.js
This commit is contained in:
commit
c6c1a63ff0
8 changed files with 281 additions and 20 deletions
|
@ -52,9 +52,9 @@
|
||||||
<button id="projectLoadButton">Load</button>
|
<button id="projectLoadButton">Load</button>
|
||||||
<button id="createEmptyProject">New Project</button><br />
|
<button id="createEmptyProject">New Project</button><br />
|
||||||
<p>
|
<p>
|
||||||
<input type="button" value="Export to XML" onclick="toXml()">
|
<input type="button" value="Export to JSON" onclick="toJson()">
|
||||||
|
|
||||||
<input type="button" value="Import from XML" onclick="fromXml()" id="import">
|
<input type="button" value="Import from JSON" onclick="fromJson()" id="import">
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<textarea id="importExport"></textarea>
|
<textarea id="importExport"></textarea>
|
||||||
</p>
|
</p>
|
||||||
|
@ -70,18 +70,17 @@
|
||||||
<!-- Playground -->
|
<!-- Playground -->
|
||||||
<script src="./playground.js"></script>
|
<script src="./playground.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function toXml() {
|
function toJson() {
|
||||||
var output = document.getElementById('importExport');
|
var output = document.getElementById('importExport');
|
||||||
var xml = Blockly.Xml.workspaceToDom(workspace);
|
var json = window.vm.toPrettyJSON(workspace);
|
||||||
output.value = Blockly.Xml.domToPrettyText(xml);
|
output.value = json;
|
||||||
output.focus();
|
output.focus();
|
||||||
output.select();
|
output.select();
|
||||||
}
|
}
|
||||||
|
|
||||||
function fromXml() {
|
function fromJson() {
|
||||||
var input = document.getElementById('importExport');
|
var input = document.getElementById('importExport');
|
||||||
var xml = Blockly.Xml.textToDom(input.value);
|
window.vm.fromJSON(input.value);
|
||||||
Blockly.Xml.domToWorkspace(workspace, xml);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -11,7 +11,7 @@ var Sprite = require('../sprites/sprite');
|
||||||
var Color = require('../util/color.js');
|
var Color = require('../util/color.js');
|
||||||
var log = require('../util/log');
|
var log = require('../util/log');
|
||||||
var uid = require('../util/uid');
|
var uid = require('../util/uid');
|
||||||
var specMap = require('./sb2specmap');
|
var specMap = require('./sb2_specmap');
|
||||||
var Variable = require('../engine/variable');
|
var Variable = require('../engine/variable');
|
||||||
var List = require('../engine/list');
|
var List = require('../engine/list');
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ var parseScratchObject = function (object, runtime, topLevel) {
|
||||||
*/
|
*/
|
||||||
var sb2import = function (json, runtime, optForceSprite) {
|
var sb2import = function (json, runtime, optForceSprite) {
|
||||||
return parseScratchObject(
|
return parseScratchObject(
|
||||||
JSON.parse(json),
|
json,
|
||||||
runtime,
|
runtime,
|
||||||
!optForceSprite
|
!optForceSprite
|
||||||
);
|
);
|
||||||
|
@ -437,4 +437,6 @@ var parseBlock = function (sb2block) {
|
||||||
return activeBlock;
|
return activeBlock;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = sb2import;
|
module.exports = {
|
||||||
|
deserialize: sb2import
|
||||||
|
};
|
185
src/serialization/sb3.js
Normal file
185
src/serialization/sb3.js
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
/**
|
||||||
|
* @fileoverview
|
||||||
|
* Partial implementation of a SB3 serializer and deserializer. Parses provided
|
||||||
|
* JSON and then generates all needed scratch-vm runtime structures.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var package = require('../../package.json');
|
||||||
|
var Blocks = require('../engine/blocks');
|
||||||
|
var RenderedTarget = require('../sprites/rendered-target');
|
||||||
|
var Sprite = require('../sprites/sprite');
|
||||||
|
var Variable = require('../engine/variable');
|
||||||
|
var List = require('../engine/list');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the specified VM runtime.
|
||||||
|
* @param {!Runtime} runtime VM runtime instance to be serialized.
|
||||||
|
* @return {string} Serialized runtime instance.
|
||||||
|
*/
|
||||||
|
var serialize = function (runtime) {
|
||||||
|
// Fetch targets
|
||||||
|
var obj = Object.create(null);
|
||||||
|
obj.targets = runtime.targets;
|
||||||
|
|
||||||
|
// Assemble metadata
|
||||||
|
var meta = Object.create(null);
|
||||||
|
meta.semver = '3.0.0';
|
||||||
|
meta.vm = package.version;
|
||||||
|
|
||||||
|
// Attach full user agent string to metadata if available
|
||||||
|
meta.agent = null;
|
||||||
|
if (typeof navigator !== 'undefined') meta.agent = navigator.userAgent;
|
||||||
|
|
||||||
|
// Assemble payload and return
|
||||||
|
obj.meta = meta;
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a single "Scratch object" and create all its in-memory VM objects.
|
||||||
|
* @param {!Object} object From-JSON "Scratch object:" sprite, stage, watcher.
|
||||||
|
* @param {!Runtime} runtime Runtime object to load all structures into.
|
||||||
|
* @param {boolean} topLevel Whether this is the top-level object (stage).
|
||||||
|
* @return {?Target} Target created (stage or sprite).
|
||||||
|
*/
|
||||||
|
var parseScratchObject = function (object, runtime, topLevel) {
|
||||||
|
if (!object.hasOwnProperty('name')) {
|
||||||
|
// Watcher/monitor - skip this object until those are implemented in VM.
|
||||||
|
// @todo
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Blocks container for this object.
|
||||||
|
var blocks = new Blocks();
|
||||||
|
|
||||||
|
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
|
||||||
|
var sprite = new Sprite(blocks, runtime);
|
||||||
|
|
||||||
|
// Sprite/stage name from JSON.
|
||||||
|
if (object.hasOwnProperty('name')) {
|
||||||
|
sprite.name = object.name;
|
||||||
|
}
|
||||||
|
if (object.hasOwnProperty('blocks')) {
|
||||||
|
for (blockId in object.blocks) {
|
||||||
|
blocks.createBlock(object.blocks[blockId]);
|
||||||
|
}
|
||||||
|
console.log(blocks);
|
||||||
|
}
|
||||||
|
// Costumes from JSON.
|
||||||
|
if (object.hasOwnProperty('costumes') || object.hasOwnProperty('costume')) {
|
||||||
|
for (var i = 0; i < object.costumeCount; i++) {
|
||||||
|
var costume = object.costume; // [i]?
|
||||||
|
// @todo: Make sure all the relevant metadata is being pulled out.
|
||||||
|
sprite.costumes.push({
|
||||||
|
skin: costume.skin,
|
||||||
|
name: costume.costumeName,
|
||||||
|
bitmapResolution: costume.bitmapResolution,
|
||||||
|
rotationCenterX: costume.rotationCenterX,
|
||||||
|
rotationCenterY: costume.rotationCenterY
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sounds from JSON
|
||||||
|
if (object.hasOwnProperty('sounds')) {
|
||||||
|
for (var s = 0; s < object.sounds.length; s++) {
|
||||||
|
var sound = object.sounds[s];
|
||||||
|
sprite.sounds.push({
|
||||||
|
format: sound.format,
|
||||||
|
fileUrl: sound.fileUrl,
|
||||||
|
rate: sound.rate,
|
||||||
|
sampleCount: sound.sampleCount,
|
||||||
|
soundID: sound.soundID,
|
||||||
|
name: sound.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Create the first clone, and load its run-state from JSON.
|
||||||
|
var target = sprite.createClone();
|
||||||
|
// Add it to the runtime's list of targets.
|
||||||
|
runtime.targets.push(target);
|
||||||
|
// Load target properties from JSON.
|
||||||
|
if (object.hasOwnProperty('variables')) {
|
||||||
|
for (var j = 0; j < object.variables.length; j++) {
|
||||||
|
var variable = object.variables[j];
|
||||||
|
target.variables[variable.name] = new Variable(
|
||||||
|
variable.name,
|
||||||
|
variable.value,
|
||||||
|
variable.isPersistent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (object.hasOwnProperty('lists')) {
|
||||||
|
for (var k = 0; k < object.lists.length; k++) {
|
||||||
|
var list = object.lists[k];
|
||||||
|
// @todo: monitor properties.
|
||||||
|
target.lists[list.listName] = new List(
|
||||||
|
list.listName,
|
||||||
|
list.contents
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (object.hasOwnProperty('x')) {
|
||||||
|
target.x = object.x;
|
||||||
|
}
|
||||||
|
if (object.hasOwnProperty('y')) {
|
||||||
|
target.y = object.y;
|
||||||
|
}
|
||||||
|
if (object.hasOwnProperty('direction')) {
|
||||||
|
target.direction = object.direction;
|
||||||
|
}
|
||||||
|
if (object.hasOwnProperty('size')) {
|
||||||
|
target.size = object.size;
|
||||||
|
}
|
||||||
|
if (object.hasOwnProperty('visible')) {
|
||||||
|
target.visible = object.visible;
|
||||||
|
}
|
||||||
|
if (object.hasOwnProperty('currentCostume')) {
|
||||||
|
target.currentCostume = object.currentCostume;
|
||||||
|
}
|
||||||
|
if (object.hasOwnProperty('rotationStyle')) {
|
||||||
|
target.rotationStyle = object.rotationStyle;
|
||||||
|
}
|
||||||
|
target.isStage = topLevel;
|
||||||
|
target.updateAllDrawableProperties();
|
||||||
|
// The stage will have child objects; recursively process them.
|
||||||
|
if (object.children) {
|
||||||
|
for (var m = 0; m < object.children.length; m++) {
|
||||||
|
parseScratchObject(object.children[m], runtime, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("returning target:");
|
||||||
|
console.log(target);
|
||||||
|
return target;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top-level handler. Parse provided JSON,
|
||||||
|
* and process the top-level object (the stage object).
|
||||||
|
* @param {!string} json SB2-format JSON to load.
|
||||||
|
* @param {!Runtime} runtime Runtime object to load all structures into.
|
||||||
|
* @param {Boolean=} optForceSprite If set, treat as sprite (Sprite2).
|
||||||
|
* @return {?Target} Top-level target created (stage or sprite).
|
||||||
|
*/
|
||||||
|
var sb3import = function (json, runtime, optForceSprite) {
|
||||||
|
return parseScratchObject(
|
||||||
|
json,
|
||||||
|
runtime,
|
||||||
|
!optForceSprite
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes the specified representation of a VM runtime and loads it into
|
||||||
|
* the provided runtime instance.
|
||||||
|
* @param {string} json Stringified JSON representation of a VM runtime.
|
||||||
|
* @param {Runtime} runtime Runtime instance
|
||||||
|
*/
|
||||||
|
var deserialize = function (json, runtime) {
|
||||||
|
for (var i = 0; i < json.targets.length; i++) {
|
||||||
|
parseScratchObject(json.targets[i], runtime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
serialize: serialize,
|
||||||
|
deserialize: deserialize
|
||||||
|
};
|
|
@ -717,7 +717,7 @@ RenderedTarget.prototype.postSpriteInfo = function (data) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize sprite info, used when emitting events about the sprite
|
* Serialize sprite info, used when emitting events about the sprite
|
||||||
* @returns {object} sprite data as a simple object
|
* @returns {object} Sprite data as a simple object
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.toJSON = function () {
|
RenderedTarget.prototype.toJSON = function () {
|
||||||
return {
|
return {
|
||||||
|
@ -726,11 +726,15 @@ RenderedTarget.prototype.toJSON = function () {
|
||||||
isStage: this.isStage,
|
isStage: this.isStage,
|
||||||
x: this.x,
|
x: this.x,
|
||||||
y: this.y,
|
y: this.y,
|
||||||
|
size: this.size,
|
||||||
direction: this.direction,
|
direction: this.direction,
|
||||||
costume: this.getCurrentCostume(),
|
costume: this.getCurrentCostume(),
|
||||||
costumeCount: this.getCostumes().length,
|
costumeCount: this.getCostumes().length,
|
||||||
visible: this.visible,
|
visible: this.visible,
|
||||||
rotationStyle: this.rotationStyle
|
rotationStyle: this.rotationStyle,
|
||||||
|
blocks: this.blocks._blocks,
|
||||||
|
variables: this.variables,
|
||||||
|
lists: this.lists
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,9 @@ var EventEmitter = require('events');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
|
||||||
var Runtime = require('./engine/runtime');
|
var Runtime = require('./engine/runtime');
|
||||||
var sb2import = require('./import/sb2import');
|
|
||||||
|
var sb2 = require('./serialization/sb2');
|
||||||
|
var sb3 = require('./serialization/sb3');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles connections between blocks, stage, and extensions.
|
* Handles connections between blocks, stage, and extensions.
|
||||||
|
@ -143,8 +145,8 @@ VirtualMachine.prototype.postIOData = function (device, data) {
|
||||||
*/
|
*/
|
||||||
VirtualMachine.prototype.loadProject = function (json) {
|
VirtualMachine.prototype.loadProject = function (json) {
|
||||||
this.clear();
|
this.clear();
|
||||||
// @todo: Handle other formats, e.g., Scratch 1.4, Scratch 3.0.
|
// @todo: Handle other formats, e.g., Scratch 1.4.
|
||||||
sb2import(json, this.runtime);
|
this.fromJSON(json, this.runtime);
|
||||||
// Select the first target for editing, e.g., the first sprite.
|
// Select the first target for editing, e.g., the first sprite.
|
||||||
this.editingTarget = this.runtime.targets[1];
|
this.editingTarget = this.runtime.targets[1];
|
||||||
// Update the VM user's knowledge of targets and blocks on the workspace.
|
// Update the VM user's knowledge of targets and blocks on the workspace.
|
||||||
|
@ -153,13 +155,62 @@ VirtualMachine.prototype.loadProject = function (json) {
|
||||||
this.runtime.setEditingTarget(this.editingTarget);
|
this.runtime.setEditingTarget(this.editingTarget);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return a project in a Scratch 3.0 JSON representation.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.saveProjectSb3 = function () {
|
||||||
|
// @todo: Handle other formats, e.g., Scratch 1.4, Scratch 2.0.
|
||||||
|
return this.toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export project as a Scratch 3.0 JSON representation.
|
||||||
|
* @return {string} Serialized state of the runtime.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.toJSON = function () {
|
||||||
|
return JSON.stringify(sb3.serialize(this.runtime));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a project from a Scratch JSON representation.
|
||||||
|
* @param {string} json JSON string representing a project.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.fromJSON = function (json) {
|
||||||
|
// Clear the current runtime
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
// Validate & parse
|
||||||
|
if (typeof json !== 'string') return;
|
||||||
|
json = JSON.parse(json);
|
||||||
|
if (typeof json !== 'object') return;
|
||||||
|
|
||||||
|
// Establish version, deserialize, and load into runtime
|
||||||
|
// @todo Support Scratch 1.4
|
||||||
|
// @todo This is an extremely naïve / dangerous way of determining version.
|
||||||
|
// See `scratch-parser` for a more sophisticated validation
|
||||||
|
// methodology that should be adapted for use here
|
||||||
|
if ((typeof json.meta !== 'undefined') && (typeof json.meta.semver !== 'undefined') ) {
|
||||||
|
sb3.deserialize(json, this.runtime);
|
||||||
|
} else {
|
||||||
|
sb2.deserialize(json, this.runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the first target for editing, e.g., the first sprite.
|
||||||
|
this.editingTarget = this.runtime.targets[1];
|
||||||
|
|
||||||
|
// Update the VM user's knowledge of targets and blocks on the workspace.
|
||||||
|
this.emitTargetsUpdate();
|
||||||
|
this.emitWorkspaceUpdate();
|
||||||
|
this.runtime.setEditingTarget(this.editingTarget);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a single sprite from the "Sprite2" (i.e., SB2 sprite) format.
|
* Add a single sprite from the "Sprite2" (i.e., SB2 sprite) format.
|
||||||
* @param {?string} json JSON string representing the sprite.
|
* @param {?string} json JSON string representing the sprite.
|
||||||
*/
|
*/
|
||||||
VirtualMachine.prototype.addSprite2 = function (json) {
|
VirtualMachine.prototype.addSprite2 = function (json) {
|
||||||
// Select new sprite.
|
// Select new sprite.
|
||||||
this.editingTarget = sb2import(json, this.runtime, true);
|
this.editingTarget = sb2.deserialize(json, this.runtime, true);
|
||||||
// Update the VM user's knowledge of targets and blocks on the workspace.
|
// Update the VM user's knowledge of targets and blocks on the workspace.
|
||||||
this.emitTargetsUpdate();
|
this.emitTargetsUpdate();
|
||||||
this.emitWorkspaceUpdate();
|
this.emitWorkspaceUpdate();
|
||||||
|
|
|
@ -4,10 +4,11 @@ var extract = require('../fixtures/extract');
|
||||||
|
|
||||||
var renderedTarget = require('../../src/sprites/rendered-target');
|
var renderedTarget = require('../../src/sprites/rendered-target');
|
||||||
var runtime = require('../../src/engine/runtime');
|
var runtime = require('../../src/engine/runtime');
|
||||||
var sb2 = require('../../src/import/sb2import');
|
var sb2 = require('../../src/serialization/sb2');
|
||||||
|
|
||||||
test('spec', function (t) {
|
test('spec', function (t) {
|
||||||
t.type(sb2, 'function');
|
t.type(sb2, 'object');
|
||||||
|
t.type(sb2.deserialize, 'function');
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ test('default', function (t) {
|
||||||
|
|
||||||
// Create runtime instance & load SB2 into it
|
// Create runtime instance & load SB2 into it
|
||||||
var rt = new runtime();
|
var rt = new runtime();
|
||||||
sb2(file, rt);
|
sb2.deserialize(file, rt);
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
t.type(file, 'string');
|
t.type(file, 'string');
|
19
test/unit/serialization_sb3.js
Normal file
19
test/unit/serialization_sb3.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
var test = require('tap').test;
|
||||||
|
var VirtualMachine = require('../../src/index');
|
||||||
|
var sb3 = require('../../src/serialization/sb3');
|
||||||
|
|
||||||
|
test('serialize', function (t) {
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
vm.fromJSON(JSON.stringify(require('../fixtures/demo.json')));
|
||||||
|
var result = sb3.serialize(vm.runtime);
|
||||||
|
console.dir(JSON.stringify(result));
|
||||||
|
// @todo Analyize
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deserialize', function (t) {
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
var result = sb3.deserialize('', vm.runtime);
|
||||||
|
// @todo Analyize
|
||||||
|
t.end();
|
||||||
|
});
|
Loading…
Reference in a new issue