mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-03-14 17:30:26 -04:00
Merge pull request #498 from cwillisf/use-scratch-storage
Load projects & costumes through scratch-storage
This commit is contained in:
commit
c8b4871b19
21 changed files with 305 additions and 65 deletions
|
@ -40,6 +40,7 @@
|
||||||
"scratch-audio": "latest",
|
"scratch-audio": "latest",
|
||||||
"scratch-blocks": "latest",
|
"scratch-blocks": "latest",
|
||||||
"scratch-render": "latest",
|
"scratch-render": "latest",
|
||||||
|
"scratch-storage": "latest",
|
||||||
"script-loader": "0.7.0",
|
"script-loader": "0.7.0",
|
||||||
"stats.js": "^0.17.0",
|
"stats.js": "^0.17.0",
|
||||||
"tap": "^10.2.0",
|
"tap": "^10.2.0",
|
||||||
|
|
|
@ -311,6 +311,14 @@ Runtime.prototype.clearEdgeActivatedValues = function () {
|
||||||
this._edgeActivatedHatValues = {};
|
this._edgeActivatedHatValues = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach the audio engine
|
||||||
|
* @param {!AudioEngine} audioEngine The audio engine to attach
|
||||||
|
*/
|
||||||
|
Runtime.prototype.attachAudioEngine = function (audioEngine) {
|
||||||
|
this.audioEngine = audioEngine;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach the renderer
|
* Attach the renderer
|
||||||
* @param {!RenderWebGL} renderer The renderer to attach
|
* @param {!RenderWebGL} renderer The renderer to attach
|
||||||
|
@ -320,11 +328,11 @@ Runtime.prototype.attachRenderer = function (renderer) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach the audio engine
|
* Attach the storage module
|
||||||
* @param {!AudioEngine} audioEngine The audio engine to attach
|
* @param {!ScratchStorage} storage The storage module to attach
|
||||||
*/
|
*/
|
||||||
Runtime.prototype.attachAudioEngine = function (audioEngine) {
|
Runtime.prototype.attachStorage = function (storage) {
|
||||||
this.audioEngine = audioEngine;
|
this.storage = storage;
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
|
@ -5,10 +5,13 @@
|
||||||
* scratch-vm runtime structures.
|
* scratch-vm runtime structures.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var ScratchStorage = require('scratch-storage');
|
||||||
|
var AssetType = ScratchStorage.AssetType;
|
||||||
|
|
||||||
var Blocks = require('../engine/blocks');
|
var Blocks = require('../engine/blocks');
|
||||||
var RenderedTarget = require('../sprites/rendered-target');
|
var RenderedTarget = require('../sprites/rendered-target');
|
||||||
var Sprite = require('../sprites/sprite');
|
var Sprite = require('../sprites/sprite');
|
||||||
var Color = require('../util/color.js');
|
var Color = require('../util/color');
|
||||||
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('./sb2specmap');
|
||||||
|
@ -26,7 +29,7 @@ var parseScratchObject = function (object, runtime, topLevel) {
|
||||||
if (!object.hasOwnProperty('objName')) {
|
if (!object.hasOwnProperty('objName')) {
|
||||||
// Watcher/monitor - skip this object until those are implemented in VM.
|
// Watcher/monitor - skip this object until those are implemented in VM.
|
||||||
// @todo
|
// @todo
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
// Blocks container for this object.
|
// Blocks container for this object.
|
||||||
var blocks = new Blocks();
|
var blocks = new Blocks();
|
||||||
|
@ -37,33 +40,39 @@ var parseScratchObject = function (object, runtime, topLevel) {
|
||||||
sprite.name = object.objName;
|
sprite.name = object.objName;
|
||||||
}
|
}
|
||||||
// Costumes from JSON.
|
// Costumes from JSON.
|
||||||
|
var costumePromises = [];
|
||||||
if (object.hasOwnProperty('costumes')) {
|
if (object.hasOwnProperty('costumes')) {
|
||||||
for (var i = 0; i < object.costumes.length; i++) {
|
for (var i = 0; i < object.costumes.length; i++) {
|
||||||
var costume = object.costumes[i];
|
var costumeSource = object.costumes[i];
|
||||||
// @todo: Make sure all the relevant metadata is being pulled out.
|
var costume = {
|
||||||
sprite.costumes.push({
|
name: costumeSource.costumeName,
|
||||||
skin: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' +
|
bitmapResolution: costumeSource.bitmapResolution || 1,
|
||||||
costume.baseLayerMD5 + '/get/',
|
rotationCenterX: costumeSource.rotationCenterX,
|
||||||
name: costume.costumeName,
|
rotationCenterY: costumeSource.rotationCenterY,
|
||||||
bitmapResolution: costume.bitmapResolution,
|
skinId: null
|
||||||
rotationCenterX: costume.rotationCenterX,
|
};
|
||||||
rotationCenterY: costume.rotationCenterY
|
var costumePromise = loadCostume(costumeSource.baseLayerMD5, costume, runtime);
|
||||||
});
|
if (costumePromise) {
|
||||||
|
costumePromises.push(costumePromise);
|
||||||
|
}
|
||||||
|
sprite.costumes.push(costume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Sounds from JSON
|
// Sounds from JSON
|
||||||
if (object.hasOwnProperty('sounds')) {
|
if (object.hasOwnProperty('sounds')) {
|
||||||
for (var s = 0; s < object.sounds.length; s++) {
|
for (var s = 0; s < object.sounds.length; s++) {
|
||||||
var sound = object.sounds[s];
|
var soundSource = object.sounds[s];
|
||||||
sprite.sounds.push({
|
var sound = {
|
||||||
format: sound.format,
|
name: soundSource.soundName,
|
||||||
fileUrl: 'https://cdn.assets.scratch.mit.edu/internalapi/asset/' + sound.md5 + '/get/',
|
format: soundSource.format,
|
||||||
rate: sound.rate,
|
rate: soundSource.rate,
|
||||||
sampleCount: sound.sampleCount,
|
sampleCount: soundSource.sampleCount,
|
||||||
soundID: sound.soundID,
|
soundID: soundSource.soundID,
|
||||||
name: sound.soundName,
|
md5: soundSource.md5,
|
||||||
md5: sound.md5
|
data: null
|
||||||
});
|
};
|
||||||
|
loadSound(sound, runtime);
|
||||||
|
sprite.sounds.push(sound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If included, parse any and all scripts/blocks on the object.
|
// If included, parse any and all scripts/blocks on the object.
|
||||||
|
@ -127,7 +136,9 @@ var parseScratchObject = function (object, runtime, topLevel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
target.isStage = topLevel;
|
target.isStage = topLevel;
|
||||||
target.updateAllDrawableProperties();
|
Promise.all(costumePromises).then(function () {
|
||||||
|
target.updateAllDrawableProperties();
|
||||||
|
});
|
||||||
// The stage will have child objects; recursively process them.
|
// The stage will have child objects; recursively process them.
|
||||||
if (object.children) {
|
if (object.children) {
|
||||||
for (var m = 0; m < object.children.length; m++) {
|
for (var m = 0; m < object.children.length; m++) {
|
||||||
|
@ -137,6 +148,92 @@ var parseScratchObject = function (object, runtime, topLevel) {
|
||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a costume's asset into memory asynchronously.
|
||||||
|
* Do not call this unless there is a renderer attached.
|
||||||
|
* @param {string} md5ext - the MD5 and extension of the costume to be loaded.
|
||||||
|
* @param {!object} costume - the Scratch costume object.
|
||||||
|
* @property {int} skinId - the ID of the costume's render skin, once installed.
|
||||||
|
* @property {number} rotationCenterX - the X component of the costume's origin.
|
||||||
|
* @property {number} rotationCenterY - the Y component of the costume's origin.
|
||||||
|
* @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.
|
||||||
|
* @param {!Runtime} runtime - Scratch runtime, used to access the storage module.
|
||||||
|
* @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.
|
||||||
|
*/
|
||||||
|
var loadCostume = function (md5ext, costume, runtime) {
|
||||||
|
if (!runtime.storage) {
|
||||||
|
log.error('No storage module present; cannot load costume asset: ', md5ext);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!runtime.renderer) {
|
||||||
|
log.error('No rendering module present; cannot load costume asset: ', md5ext);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idParts = md5ext.split('.');
|
||||||
|
var md5 = idParts[0];
|
||||||
|
var ext = idParts[1].toUpperCase();
|
||||||
|
var assetType = (ext === 'SVG') ? AssetType.ImageVector : AssetType.ImageBitmap;
|
||||||
|
|
||||||
|
var rotationCenter = [
|
||||||
|
costume.rotationCenterX / costume.bitmapResolution,
|
||||||
|
costume.rotationCenterY / costume.bitmapResolution
|
||||||
|
];
|
||||||
|
|
||||||
|
var promise = runtime.storage.load(assetType, md5);
|
||||||
|
|
||||||
|
if (assetType === AssetType.ImageVector) {
|
||||||
|
promise = promise.then(function (costumeAsset) {
|
||||||
|
costume.skinId = runtime.renderer.createSVGSkin(costumeAsset.decodeText(), rotationCenter);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise = promise.then(function (costumeAsset) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
var imageElement = new Image();
|
||||||
|
var removeEventListeners; // fix no-use-before-define
|
||||||
|
var onError = function () {
|
||||||
|
removeEventListeners();
|
||||||
|
reject();
|
||||||
|
};
|
||||||
|
var onLoad = function () {
|
||||||
|
removeEventListeners();
|
||||||
|
resolve(imageElement);
|
||||||
|
};
|
||||||
|
removeEventListeners = function () {
|
||||||
|
imageElement.removeEventListener('error', onError);
|
||||||
|
imageElement.removeEventListener('load', onLoad);
|
||||||
|
};
|
||||||
|
imageElement.addEventListener('error', onError);
|
||||||
|
imageElement.addEventListener('load', onLoad);
|
||||||
|
imageElement.src = costumeAsset.encodeDataURI();
|
||||||
|
});
|
||||||
|
}).then(function (imageElement) {
|
||||||
|
costume.skinId = runtime.renderer.createBitmapSkin(imageElement, costume.bitmapResolution, rotationCenter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a sound's asset into memory asynchronously.
|
||||||
|
* @param {!object} sound - the Scratch sound object.
|
||||||
|
* @property {string} md5 - the MD5 and extension of the sound to be loaded.
|
||||||
|
* @property {Buffer} data - sound data will be written here once loaded.
|
||||||
|
* @param {!Runtime} runtime - Scratch runtime, used to access the storage module.
|
||||||
|
*/
|
||||||
|
var loadSound = function (sound, runtime) {
|
||||||
|
if (!runtime.storage) {
|
||||||
|
log.error('No storage module present; cannot load sound asset: ', sound.md5);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var idParts = sound.md5.split('.');
|
||||||
|
var md5 = idParts[0];
|
||||||
|
runtime.storage.load(AssetType.Sound, md5).then(function (soundAsset) {
|
||||||
|
sound.data = soundAsset.data;
|
||||||
|
// @todo register sound.data with scratch-audio
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top-level handler. Parse provided JSON,
|
* Top-level handler. Parse provided JSON,
|
||||||
* and process the top-level object (the stage object).
|
* and process the top-level object (the stage object).
|
||||||
|
|
|
@ -1,27 +1,56 @@
|
||||||
|
var Scratch = window.Scratch = window.Scratch || {};
|
||||||
|
|
||||||
|
var ASSET_SERVER = 'https://cdn.assets.scratch.mit.edu/';
|
||||||
|
var PROJECT_SERVER = 'https://cdn.projects.scratch.mit.edu/';
|
||||||
|
|
||||||
var loadProject = function () {
|
var loadProject = function () {
|
||||||
var id = location.hash.substring(1);
|
var id = location.hash.substring(1);
|
||||||
if (id.length < 1 || !isFinite(id)) {
|
if (id.length < 1 || !isFinite(id)) {
|
||||||
id = '119615668';
|
id = '119615668';
|
||||||
}
|
}
|
||||||
var url = 'https://projects.scratch.mit.edu/internalapi/project/' +
|
Scratch.vm.downloadProjectId(id);
|
||||||
id + '/get/';
|
};
|
||||||
var r = new XMLHttpRequest();
|
|
||||||
r.onreadystatechange = function () {
|
/**
|
||||||
if (this.readyState === 4) {
|
* @param {Asset} asset - calculate a URL for this asset.
|
||||||
if (r.status === 200) {
|
* @returns {string} a URL to download a project file.
|
||||||
window.vm.loadProject(this.responseText);
|
*/
|
||||||
}
|
var getProjectUrl = function (asset) {
|
||||||
}
|
var assetIdParts = asset.assetId.split('.');
|
||||||
};
|
var assetUrlParts = [PROJECT_SERVER, 'internalapi/project/', assetIdParts[0], '/get/'];
|
||||||
r.open('GET', url);
|
if (assetIdParts[1]) {
|
||||||
r.send();
|
assetUrlParts.push(assetIdParts[1]);
|
||||||
|
}
|
||||||
|
return assetUrlParts.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Asset} asset - calculate a URL for this asset.
|
||||||
|
* @returns {string} a URL to download a project asset (PNG, WAV, etc.)
|
||||||
|
*/
|
||||||
|
var getAssetUrl = function (asset) {
|
||||||
|
var assetUrlParts = [
|
||||||
|
ASSET_SERVER,
|
||||||
|
'internalapi/asset/',
|
||||||
|
asset.assetId,
|
||||||
|
'.',
|
||||||
|
asset.assetType.runtimeFormat,
|
||||||
|
'/get/'
|
||||||
|
];
|
||||||
|
return assetUrlParts.join('');
|
||||||
};
|
};
|
||||||
|
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
// Lots of global variables to make debugging easier
|
// Lots of global variables to make debugging easier
|
||||||
// Instantiate the VM.
|
// Instantiate the VM.
|
||||||
var vm = new window.VirtualMachine();
|
var vm = new window.VirtualMachine();
|
||||||
window.vm = vm;
|
Scratch.vm = vm;
|
||||||
|
|
||||||
|
var storage = new Scratch.Storage();
|
||||||
|
var AssetType = Scratch.Storage.AssetType;
|
||||||
|
storage.addWebSource([AssetType.Project], getProjectUrl);
|
||||||
|
storage.addWebSource([AssetType.ImageVector, AssetType.ImageBitmap, AssetType.Sound], getAssetUrl);
|
||||||
|
vm.attachStorage(storage);
|
||||||
|
|
||||||
// Loading projects from the server.
|
// Loading projects from the server.
|
||||||
document.getElementById('projectLoadButton').onclick = function () {
|
document.getElementById('projectLoadButton').onclick = function () {
|
||||||
|
@ -33,7 +62,7 @@ window.onload = function () {
|
||||||
// Instantiate the renderer and connect it to the VM.
|
// Instantiate the renderer and connect it to the VM.
|
||||||
var canvas = document.getElementById('scratch-stage');
|
var canvas = document.getElementById('scratch-stage');
|
||||||
var renderer = new window.RenderWebGL(canvas);
|
var renderer = new window.RenderWebGL(canvas);
|
||||||
window.renderer = renderer;
|
Scratch.renderer = renderer;
|
||||||
vm.attachRenderer(renderer);
|
vm.attachRenderer(renderer);
|
||||||
var audioEngine = new window.AudioEngine();
|
var audioEngine = new window.AudioEngine();
|
||||||
vm.attachAudioEngine(audioEngine);
|
vm.attachAudioEngine(audioEngine);
|
||||||
|
@ -57,7 +86,7 @@ window.onload = function () {
|
||||||
dragShadowOpacity: 0.6
|
dragShadowOpacity: 0.6
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.workspace = workspace;
|
Scratch.workspace = workspace;
|
||||||
|
|
||||||
// Attach scratch-blocks events to VM.
|
// Attach scratch-blocks events to VM.
|
||||||
workspace.addChangeListener(vm.blockListener);
|
workspace.addChangeListener(vm.blockListener);
|
||||||
|
@ -90,10 +119,10 @@ window.onload = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only request data from the VM thread if the appropriate tab is open.
|
// Only request data from the VM thread if the appropriate tab is open.
|
||||||
window.exploreTabOpen = false;
|
Scratch.exploreTabOpen = false;
|
||||||
var getPlaygroundData = function () {
|
var getPlaygroundData = function () {
|
||||||
vm.getPlaygroundData();
|
vm.getPlaygroundData();
|
||||||
if (window.exploreTabOpen) {
|
if (Scratch.exploreTabOpen) {
|
||||||
window.requestAnimationFrame(getPlaygroundData);
|
window.requestAnimationFrame(getPlaygroundData);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -182,7 +211,7 @@ window.onload = function () {
|
||||||
canvasWidth: rect.width,
|
canvasWidth: rect.width,
|
||||||
canvasHeight: rect.height
|
canvasHeight: rect.height
|
||||||
};
|
};
|
||||||
window.vm.postIOData('mouse', coordinates);
|
Scratch.vm.postIOData('mouse', coordinates);
|
||||||
});
|
});
|
||||||
canvas.addEventListener('mousedown', function (e) {
|
canvas.addEventListener('mousedown', function (e) {
|
||||||
var rect = canvas.getBoundingClientRect();
|
var rect = canvas.getBoundingClientRect();
|
||||||
|
@ -193,7 +222,7 @@ window.onload = function () {
|
||||||
canvasWidth: rect.width,
|
canvasWidth: rect.width,
|
||||||
canvasHeight: rect.height
|
canvasHeight: rect.height
|
||||||
};
|
};
|
||||||
window.vm.postIOData('mouse', data);
|
Scratch.vm.postIOData('mouse', data);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
canvas.addEventListener('mouseup', function (e) {
|
canvas.addEventListener('mouseup', function (e) {
|
||||||
|
@ -205,7 +234,7 @@ window.onload = function () {
|
||||||
canvasWidth: rect.width,
|
canvasWidth: rect.width,
|
||||||
canvasHeight: rect.height
|
canvasHeight: rect.height
|
||||||
};
|
};
|
||||||
window.vm.postIOData('mouse', data);
|
Scratch.vm.postIOData('mouse', data);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -215,7 +244,7 @@ window.onload = function () {
|
||||||
if (e.target !== document && e.target !== document.body) {
|
if (e.target !== document && e.target !== document.body) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.vm.postIOData('keyboard', {
|
Scratch.vm.postIOData('keyboard', {
|
||||||
keyCode: e.keyCode,
|
keyCode: e.keyCode,
|
||||||
isDown: true
|
isDown: true
|
||||||
});
|
});
|
||||||
|
@ -224,7 +253,7 @@ window.onload = function () {
|
||||||
document.addEventListener('keyup', function (e) {
|
document.addEventListener('keyup', function (e) {
|
||||||
// Always capture up events,
|
// Always capture up events,
|
||||||
// even those that have switched to other targets.
|
// even those that have switched to other targets.
|
||||||
window.vm.postIOData('keyboard', {
|
Scratch.vm.postIOData('keyboard', {
|
||||||
keyCode: e.keyCode,
|
keyCode: e.keyCode,
|
||||||
isDown: false
|
isDown: false
|
||||||
});
|
});
|
||||||
|
@ -268,7 +297,7 @@ window.onload = function () {
|
||||||
// Handlers to show different explorers.
|
// Handlers to show different explorers.
|
||||||
document.getElementById('threadexplorer-link').addEventListener('click',
|
document.getElementById('threadexplorer-link').addEventListener('click',
|
||||||
function () {
|
function () {
|
||||||
window.exploreTabOpen = true;
|
Scratch.exploreTabOpen = true;
|
||||||
getPlaygroundData();
|
getPlaygroundData();
|
||||||
tabBlockExplorer.style.display = 'none';
|
tabBlockExplorer.style.display = 'none';
|
||||||
tabRenderExplorer.style.display = 'none';
|
tabRenderExplorer.style.display = 'none';
|
||||||
|
@ -277,7 +306,7 @@ window.onload = function () {
|
||||||
});
|
});
|
||||||
document.getElementById('blockexplorer-link').addEventListener('click',
|
document.getElementById('blockexplorer-link').addEventListener('click',
|
||||||
function () {
|
function () {
|
||||||
window.exploreTabOpen = true;
|
Scratch.exploreTabOpen = true;
|
||||||
getPlaygroundData();
|
getPlaygroundData();
|
||||||
tabBlockExplorer.style.display = 'block';
|
tabBlockExplorer.style.display = 'block';
|
||||||
tabRenderExplorer.style.display = 'none';
|
tabRenderExplorer.style.display = 'none';
|
||||||
|
@ -286,7 +315,7 @@ window.onload = function () {
|
||||||
});
|
});
|
||||||
document.getElementById('renderexplorer-link').addEventListener('click',
|
document.getElementById('renderexplorer-link').addEventListener('click',
|
||||||
function () {
|
function () {
|
||||||
window.exploreTabOpen = false;
|
Scratch.exploreTabOpen = false;
|
||||||
tabBlockExplorer.style.display = 'none';
|
tabBlockExplorer.style.display = 'none';
|
||||||
tabRenderExplorer.style.display = 'block';
|
tabRenderExplorer.style.display = 'block';
|
||||||
tabThreadExplorer.style.display = 'none';
|
tabThreadExplorer.style.display = 'none';
|
||||||
|
@ -294,7 +323,7 @@ window.onload = function () {
|
||||||
});
|
});
|
||||||
document.getElementById('importexport-link').addEventListener('click',
|
document.getElementById('importexport-link').addEventListener('click',
|
||||||
function () {
|
function () {
|
||||||
window.exploreTabOpen = false;
|
Scratch.exploreTabOpen = false;
|
||||||
tabBlockExplorer.style.display = 'none';
|
tabBlockExplorer.style.display = 'none';
|
||||||
tabRenderExplorer.style.display = 'none';
|
tabRenderExplorer.style.display = 'none';
|
||||||
tabThreadExplorer.style.display = 'none';
|
tabThreadExplorer.style.display = 'none';
|
||||||
|
|
|
@ -367,7 +367,7 @@ RenderedTarget.prototype.setCostume = function (index) {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
var costume = this.sprite.costumes[this.currentCostume];
|
var costume = this.sprite.costumes[this.currentCostume];
|
||||||
var drawableProperties = {
|
var drawableProperties = {
|
||||||
skin: costume.skin,
|
skinId: costume.skinId,
|
||||||
costumeResolution: costume.bitmapResolution
|
costumeResolution: costume.bitmapResolution
|
||||||
};
|
};
|
||||||
if (
|
if (
|
||||||
|
@ -458,7 +458,7 @@ RenderedTarget.prototype.updateAllDrawableProperties = function () {
|
||||||
draggable: this.draggable,
|
draggable: this.draggable,
|
||||||
scale: renderedDirectionScale.scale,
|
scale: renderedDirectionScale.scale,
|
||||||
visible: this.visible,
|
visible: this.visible,
|
||||||
skin: costume.skin,
|
skinId: costume.skinId,
|
||||||
costumeResolution: bitmapResolution,
|
costumeResolution: bitmapResolution,
|
||||||
rotationCenter: [
|
rotationCenter: [
|
||||||
costume.rotationCenterX / bitmapResolution,
|
costume.rotationCenterX / bitmapResolution,
|
||||||
|
|
|
@ -24,7 +24,7 @@ var Sprite = function (blocks, runtime) {
|
||||||
* List of costumes for this sprite.
|
* List of costumes for this sprite.
|
||||||
* Each entry is an object, e.g.,
|
* Each entry is an object, e.g.,
|
||||||
* {
|
* {
|
||||||
* skin: "costume.svg",
|
* skinId: 1,
|
||||||
* name: "Costume Name",
|
* name: "Costume Name",
|
||||||
* bitmapResolution: 2,
|
* bitmapResolution: 2,
|
||||||
* rotationCenterX: 0,
|
* rotationCenterX: 0,
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
var EventEmitter = require('events');
|
var EventEmitter = require('events');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
|
||||||
|
var log = require('./util/log');
|
||||||
var Runtime = require('./engine/runtime');
|
var Runtime = require('./engine/runtime');
|
||||||
|
var ScratchStorage = require('scratch-storage');
|
||||||
var sb2import = require('./import/sb2import');
|
var sb2import = require('./import/sb2import');
|
||||||
var StringUtil = require('./util/string-util');
|
var StringUtil = require('./util/string-util');
|
||||||
|
|
||||||
var RESERVED_NAMES = ['_mouse_', '_stage_', '_edge_', '_myself_', '_random_'];
|
var RESERVED_NAMES = ['_mouse_', '_stage_', '_edge_', '_myself_', '_random_'];
|
||||||
|
|
||||||
|
var AssetType = ScratchStorage.AssetType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles connections between blocks, stage, and extensions.
|
* Handles connections between blocks, stage, and extensions.
|
||||||
* @constructor
|
* @constructor
|
||||||
|
@ -156,9 +160,25 @@ VirtualMachine.prototype.loadProject = function (json) {
|
||||||
this.runtime.setEditingTarget(this.editingTarget);
|
this.runtime.setEditingTarget(this.editingTarget);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a project from the Scratch web site, by ID.
|
||||||
|
* @param {string} id - the ID of the project to download, as a string.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.downloadProjectId = function (id) {
|
||||||
|
if (!this.runtime.storage) {
|
||||||
|
log.error('No storage module present; cannot load project: ', id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var vm = this;
|
||||||
|
var promise = this.runtime.storage.load(AssetType.Project, id);
|
||||||
|
promise.then(function (projectAsset) {
|
||||||
|
vm.loadProject(projectAsset.decodeText());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -253,6 +273,14 @@ VirtualMachine.prototype.deleteSprite = function (targetId) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the audio engine for the VM/runtime
|
||||||
|
* @param {!AudioEngine} audioEngine The audio engine to attach
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.attachAudioEngine = function (audioEngine) {
|
||||||
|
this.runtime.attachAudioEngine(audioEngine);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the renderer for the VM/runtime
|
* Set the renderer for the VM/runtime
|
||||||
* @param {!RenderWebGL} renderer The renderer to attach
|
* @param {!RenderWebGL} renderer The renderer to attach
|
||||||
|
@ -262,11 +290,11 @@ VirtualMachine.prototype.attachRenderer = function (renderer) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the audio engine for the VM/runtime
|
* Set the storage module for the VM/runtime
|
||||||
* @param {!AudioEngine} audioEngine The audio engine to attach
|
* @param {!ScratchStorage} storage The storage module to attach
|
||||||
*/
|
*/
|
||||||
VirtualMachine.prototype.attachAudioEngine = function (audioEngine) {
|
VirtualMachine.prototype.attachStorage = function (storage) {
|
||||||
this.runtime.attachAudioEngine(audioEngine);
|
this.runtime.attachStorage(storage);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
47
test/fixtures/attach-test-storage.js
vendored
Normal file
47
test/fixtures/attach-test-storage.js
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
var ScratchStorage = require('scratch-storage');
|
||||||
|
|
||||||
|
var ASSET_SERVER = 'https://cdn.assets.scratch.mit.edu/';
|
||||||
|
var PROJECT_SERVER = 'https://cdn.projects.scratch.mit.edu/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Asset} asset - calculate a URL for this asset.
|
||||||
|
* @returns {string} a URL to download a project file.
|
||||||
|
*/
|
||||||
|
var getProjectUrl = function (asset) {
|
||||||
|
var assetIdParts = asset.assetId.split('.');
|
||||||
|
var assetUrlParts = [PROJECT_SERVER, 'internalapi/project/', assetIdParts[0], '/get/'];
|
||||||
|
if (assetIdParts[1]) {
|
||||||
|
assetUrlParts.push(assetIdParts[1]);
|
||||||
|
}
|
||||||
|
return assetUrlParts.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Asset} asset - calculate a URL for this asset.
|
||||||
|
* @returns {string} a URL to download a project asset (PNG, WAV, etc.)
|
||||||
|
*/
|
||||||
|
var getAssetUrl = function (asset) {
|
||||||
|
var assetUrlParts = [
|
||||||
|
ASSET_SERVER,
|
||||||
|
'internalapi/asset/',
|
||||||
|
asset.assetId,
|
||||||
|
'.',
|
||||||
|
asset.assetType.runtimeFormat,
|
||||||
|
'/get/'
|
||||||
|
];
|
||||||
|
return assetUrlParts.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new instance of ScratchStorage, provide it with default web sources, and attach it to the provided VM.
|
||||||
|
* @param {VirtualMachine} vm - the VM which will own the new ScratchStorage instance.
|
||||||
|
*/
|
||||||
|
var attachTestStorage = function (vm) {
|
||||||
|
var storage = new ScratchStorage();
|
||||||
|
var AssetType = ScratchStorage.AssetType;
|
||||||
|
storage.addWebSource([AssetType.Project], getProjectUrl);
|
||||||
|
storage.addWebSource([AssetType.ImageVector, AssetType.ImageBitmap, AssetType.Sound], getAssetUrl);
|
||||||
|
vm.attachStorage(storage);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = attachTestStorage;
|
|
@ -1,6 +1,7 @@
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ var sprite = fs.readFileSync(spriteUri, 'utf8');
|
||||||
|
|
||||||
test('complex', function (t) {
|
test('complex', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function (e) {
|
vm.on('playgroundData', function (e) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ var project = extract(uri);
|
||||||
|
|
||||||
test('control', function (t) {
|
test('control', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function (e) {
|
vm.on('playgroundData', function (e) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ var project = extract(uri);
|
||||||
|
|
||||||
test('data', function (t) {
|
test('data', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function () {
|
vm.on('playgroundData', function () {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ var project = extract(uri);
|
||||||
|
|
||||||
test('event', function (t) {
|
test('event', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function (e) {
|
vm.on('playgroundData', function (e) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ var project = extract(projectUri);
|
||||||
|
|
||||||
test('complex', function (t) {
|
test('complex', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function (e) {
|
vm.on('playgroundData', function (e) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
|
|
||||||
var renderedTarget = require('../../src/sprites/rendered-target');
|
var renderedTarget = require('../../src/sprites/rendered-target');
|
||||||
|
@ -18,6 +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();
|
||||||
|
attachTestStorage(rt);
|
||||||
sb2(file, rt);
|
sb2(file, rt);
|
||||||
|
|
||||||
// Test
|
// Test
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ var project = extract(uri);
|
||||||
|
|
||||||
test('looks', function (t) {
|
test('looks', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function (e) {
|
vm.on('playgroundData', function (e) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ var project = extract(uri);
|
||||||
|
|
||||||
test('motion', function (t) {
|
test('motion', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function (e) {
|
vm.on('playgroundData', function (e) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ var project = extract(uri);
|
||||||
|
|
||||||
test('pen', function (t) {
|
test('pen', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function () {
|
vm.on('playgroundData', function () {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ var project = extract(uri);
|
||||||
|
|
||||||
test('procedure', function (t) {
|
test('procedure', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function (e) {
|
vm.on('playgroundData', function (e) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ var project = extract(uri);
|
||||||
|
|
||||||
test('sensing', function (t) {
|
test('sensing', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function (e) {
|
vm.on('playgroundData', function (e) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
|
var attachTestStorage = require('../fixtures/attach-test-storage');
|
||||||
var extract = require('../fixtures/extract');
|
var extract = require('../fixtures/extract');
|
||||||
var VirtualMachine = require('../../src/index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ var project = extract(uri);
|
||||||
|
|
||||||
test('sound', function (t) {
|
test('sound', function (t) {
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
attachTestStorage(vm);
|
||||||
|
|
||||||
// Evaluate playground data and exit
|
// Evaluate playground data and exit
|
||||||
vm.on('playgroundData', function (e) {
|
vm.on('playgroundData', function (e) {
|
||||||
|
|
|
@ -64,10 +64,12 @@ module.exports = [
|
||||||
'highlightjs/highlight.pack.min.js',
|
'highlightjs/highlight.pack.min.js',
|
||||||
// Scratch Blocks
|
// Scratch Blocks
|
||||||
'scratch-blocks/dist/vertical.js',
|
'scratch-blocks/dist/vertical.js',
|
||||||
|
// Audio
|
||||||
|
'scratch-audio',
|
||||||
// Renderer
|
// Renderer
|
||||||
'scratch-render',
|
'scratch-render',
|
||||||
// Audio
|
// Storage
|
||||||
'scratch-audio'
|
'scratch-storage'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
@ -92,13 +94,17 @@ module.exports = [
|
||||||
test: require.resolve('scratch-blocks/dist/vertical.js'),
|
test: require.resolve('scratch-blocks/dist/vertical.js'),
|
||||||
loader: 'expose-loader?Blockly'
|
loader: 'expose-loader?Blockly'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: require.resolve('scratch-audio'),
|
||||||
|
loader: 'expose-loader?AudioEngine'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: require.resolve('scratch-render'),
|
test: require.resolve('scratch-render'),
|
||||||
loader: 'expose-loader?RenderWebGL'
|
loader: 'expose-loader?RenderWebGL'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: require.resolve('scratch-audio'),
|
test: require.resolve('scratch-storage'),
|
||||||
loader: 'expose-loader?AudioEngine'
|
loader: 'expose-loader?Scratch.Storage'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue