2016-08-31 12:28:44 -04:00
|
|
|
var loadProject = function () {
|
2016-09-12 11:05:16 -04:00
|
|
|
var id = location.hash.substring(1);
|
2016-09-12 12:03:24 -04:00
|
|
|
if (id.length < 1) {
|
|
|
|
id = '119615668';
|
|
|
|
}
|
2016-08-31 12:28:44 -04:00
|
|
|
var url = 'https://projects.scratch.mit.edu/internalapi/project/' +
|
|
|
|
id + '/get/';
|
|
|
|
var r = new XMLHttpRequest();
|
2016-09-12 11:05:16 -04:00
|
|
|
r.onreadystatechange = function() {
|
2016-09-12 12:03:24 -04:00
|
|
|
if (this.readyState === 4) {
|
|
|
|
if (r.status === 200) {
|
|
|
|
window.vm.loadProject(this.responseText);
|
|
|
|
} else {
|
|
|
|
window.vm.createEmptyProject();
|
|
|
|
}
|
2016-09-12 11:05:16 -04:00
|
|
|
}
|
|
|
|
};
|
2016-08-31 12:28:44 -04:00
|
|
|
r.open('GET', url);
|
|
|
|
r.send();
|
|
|
|
};
|
|
|
|
|
2016-06-01 10:04:56 -04:00
|
|
|
window.onload = function() {
|
|
|
|
// Lots of global variables to make debugging easier
|
2016-09-20 15:07:05 -04:00
|
|
|
// Instantiate the VM.
|
|
|
|
var vm = new window.VirtualMachine();
|
2016-06-01 10:04:56 -04:00
|
|
|
window.vm = vm;
|
|
|
|
|
2016-08-31 12:28:44 -04:00
|
|
|
// Loading projects from the server.
|
|
|
|
document.getElementById('projectLoadButton').onclick = function () {
|
|
|
|
document.location = '#' + document.getElementById('projectId').value;
|
|
|
|
location.reload();
|
|
|
|
};
|
2016-09-15 19:02:03 -04:00
|
|
|
document.getElementById('createEmptyProject').addEventListener('click',
|
|
|
|
function() {
|
2016-09-13 22:04:44 -04:00
|
|
|
document.location = '#' + 'createEmptyProject';
|
|
|
|
location.reload();
|
|
|
|
});
|
2016-08-31 12:28:44 -04:00
|
|
|
loadProject();
|
|
|
|
|
2016-09-20 15:07:05 -04:00
|
|
|
// Instantiate the renderer and connect it to the VM.
|
|
|
|
var canvas = document.getElementById('scratch-stage');
|
|
|
|
var renderer = new window.RenderWebGL(canvas);
|
|
|
|
window.renderer = renderer;
|
|
|
|
vm.attachRenderer(renderer);
|
|
|
|
|
2016-09-02 09:52:01 -04:00
|
|
|
// Instantiate scratch-blocks and attach it to the DOM.
|
2016-06-01 10:04:56 -04:00
|
|
|
var toolbox = document.getElementById('toolbox');
|
|
|
|
var workspace = window.Blockly.inject('blocks', {
|
|
|
|
toolbox: toolbox,
|
2016-06-01 14:00:46 -04:00
|
|
|
media: '../node_modules/scratch-blocks/media/',
|
2016-06-07 20:26:58 -04:00
|
|
|
zoom: {
|
|
|
|
controls: true,
|
|
|
|
wheel: true,
|
|
|
|
startScale: 0.75
|
|
|
|
},
|
2016-06-01 14:00:46 -04:00
|
|
|
colours: {
|
|
|
|
workspace: '#334771',
|
|
|
|
flyout: '#283856',
|
|
|
|
scrollbar: '#24324D',
|
|
|
|
scrollbarHover: '#0C111A',
|
|
|
|
insertionMarker: '#FFFFFF',
|
|
|
|
insertionMarkerOpacity: 0.3,
|
|
|
|
fieldShadow: 'rgba(255, 255, 255, 0.3)',
|
|
|
|
dragShadowOpacity: 0.6
|
|
|
|
}
|
2016-06-01 10:04:56 -04:00
|
|
|
});
|
|
|
|
window.workspace = workspace;
|
|
|
|
|
2016-09-02 09:52:01 -04:00
|
|
|
// Attach scratch-blocks events to VM.
|
|
|
|
// @todo: Re-enable flyout listening after fixing GH-69.
|
|
|
|
workspace.addChangeListener(vm.blockListener);
|
|
|
|
|
|
|
|
// Create FPS counter.
|
2016-08-11 22:54:57 -04:00
|
|
|
var stats = new window.Stats();
|
|
|
|
document.getElementById('tab-renderexplorer').appendChild(stats.dom);
|
|
|
|
stats.dom.style.position = 'relative';
|
|
|
|
stats.begin();
|
|
|
|
|
2016-09-02 09:52:01 -04:00
|
|
|
// Playground data tabs.
|
|
|
|
// Block representation tab.
|
2016-06-07 20:44:08 -04:00
|
|
|
var blockexplorer = document.getElementById('blockexplorer');
|
2016-06-21 15:30:36 -04:00
|
|
|
var updateBlockExplorer = function(blocks) {
|
|
|
|
blockexplorer.innerHTML = JSON.stringify(blocks, null, 2);
|
2016-06-07 20:44:08 -04:00
|
|
|
window.hljs.highlightBlock(blockexplorer);
|
2016-06-21 15:30:36 -04:00
|
|
|
};
|
2016-06-01 10:18:08 -04:00
|
|
|
|
2016-09-02 09:52:01 -04:00
|
|
|
// Thread representation tab.
|
2016-06-07 20:44:08 -04:00
|
|
|
var threadexplorer = document.getElementById('threadexplorer');
|
|
|
|
var cachedThreadJSON = '';
|
2016-09-21 16:31:23 -04:00
|
|
|
var updateThreadExplorer = function (newJSON) {
|
2016-06-07 20:44:08 -04:00
|
|
|
if (newJSON != cachedThreadJSON) {
|
|
|
|
cachedThreadJSON = newJSON;
|
|
|
|
threadexplorer.innerHTML = cachedThreadJSON;
|
|
|
|
window.hljs.highlightBlock(threadexplorer);
|
|
|
|
}
|
|
|
|
};
|
2016-06-21 15:30:36 -04:00
|
|
|
|
2016-07-01 13:09:22 -04:00
|
|
|
// Only request data from the VM thread if the appropriate tab is open.
|
|
|
|
window.exploreTabOpen = false;
|
2016-06-21 15:30:36 -04:00
|
|
|
var getPlaygroundData = function () {
|
|
|
|
vm.getPlaygroundData();
|
2016-07-01 13:09:22 -04:00
|
|
|
if (window.exploreTabOpen) {
|
|
|
|
window.requestAnimationFrame(getPlaygroundData);
|
|
|
|
}
|
2016-06-21 15:30:36 -04:00
|
|
|
};
|
|
|
|
|
2016-09-02 09:52:01 -04:00
|
|
|
// VM handlers.
|
|
|
|
// Receipt of new playground data (thread, block representations).
|
2016-06-21 15:30:36 -04:00
|
|
|
vm.on('playgroundData', function(data) {
|
|
|
|
updateThreadExplorer(data.threads);
|
|
|
|
updateBlockExplorer(data.blocks);
|
|
|
|
});
|
2016-06-07 20:44:08 -04:00
|
|
|
|
2016-09-02 09:52:01 -04:00
|
|
|
// Receipt of new block XML for the selected target.
|
2016-08-31 12:28:44 -04:00
|
|
|
vm.on('workspaceUpdate', function (data) {
|
|
|
|
window.Blockly.Events.disable();
|
|
|
|
workspace.clear();
|
|
|
|
var dom = window.Blockly.Xml.textToDom(data.xml);
|
|
|
|
window.Blockly.Xml.domToWorkspace(dom, workspace);
|
|
|
|
window.Blockly.Events.enable();
|
|
|
|
});
|
|
|
|
|
2016-09-02 09:52:01 -04:00
|
|
|
// Receipt of new list of targets, selected target update.
|
2016-08-31 12:28:44 -04:00
|
|
|
var selectedTarget = document.getElementById('selectedTarget');
|
|
|
|
vm.on('targetsUpdate', function (data) {
|
|
|
|
// Clear select box.
|
|
|
|
while (selectedTarget.firstChild) {
|
|
|
|
selectedTarget.removeChild(selectedTarget.firstChild);
|
|
|
|
}
|
|
|
|
// Generate new select box.
|
|
|
|
for (var i = 0; i < data.targetList.length; i++) {
|
|
|
|
var targetOption = document.createElement('option');
|
|
|
|
targetOption.setAttribute('value', data.targetList[i][0]);
|
|
|
|
// If target id matches editingTarget id, select it.
|
|
|
|
if (data.targetList[i][0] == data.editingTarget) {
|
|
|
|
targetOption.setAttribute('selected', 'selected');
|
|
|
|
}
|
|
|
|
targetOption.appendChild(
|
|
|
|
document.createTextNode(data.targetList[i][1])
|
|
|
|
);
|
|
|
|
selectedTarget.appendChild(targetOption);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
selectedTarget.onchange = function () {
|
|
|
|
vm.setEditingTarget(this.value);
|
|
|
|
};
|
|
|
|
|
2016-06-07 20:59:34 -04:00
|
|
|
// Feedback for stacks and blocks running.
|
2016-06-21 15:30:36 -04:00
|
|
|
vm.on('STACK_GLOW_ON', function(data) {
|
|
|
|
workspace.glowStack(data.id, true);
|
2016-06-01 13:54:33 -04:00
|
|
|
});
|
2016-06-21 15:30:36 -04:00
|
|
|
vm.on('STACK_GLOW_OFF', function(data) {
|
|
|
|
workspace.glowStack(data.id, false);
|
2016-06-01 13:54:33 -04:00
|
|
|
});
|
2016-06-21 15:30:36 -04:00
|
|
|
vm.on('BLOCK_GLOW_ON', function(data) {
|
|
|
|
workspace.glowBlock(data.id, true);
|
2016-06-07 20:59:34 -04:00
|
|
|
});
|
2016-06-21 15:30:36 -04:00
|
|
|
vm.on('BLOCK_GLOW_OFF', function(data) {
|
|
|
|
workspace.glowBlock(data.id, false);
|
2016-06-07 20:59:34 -04:00
|
|
|
});
|
2016-07-07 19:42:38 -04:00
|
|
|
vm.on('VISUAL_REPORT', function(data) {
|
|
|
|
workspace.reportValue(data.id, data.value);
|
|
|
|
});
|
2016-06-07 20:59:34 -04:00
|
|
|
|
2016-08-15 21:37:36 -04:00
|
|
|
// Feed mouse events as VM I/O events.
|
|
|
|
document.addEventListener('mousemove', function (e) {
|
|
|
|
var rect = canvas.getBoundingClientRect();
|
|
|
|
var coordinates = {
|
2016-09-12 17:16:10 -04:00
|
|
|
x: e.clientX - rect.left,
|
|
|
|
y: e.clientY - rect.top,
|
|
|
|
canvasWidth: rect.width,
|
|
|
|
canvasHeight: rect.height
|
2016-08-15 21:37:36 -04:00
|
|
|
};
|
|
|
|
window.vm.postIOData('mouse', coordinates);
|
|
|
|
});
|
|
|
|
canvas.addEventListener('mousedown', function (e) {
|
2016-09-12 17:16:10 -04:00
|
|
|
var rect = canvas.getBoundingClientRect();
|
|
|
|
var data = {
|
|
|
|
isDown: true,
|
|
|
|
x: e.clientX - rect.left,
|
|
|
|
y: e.clientY - rect.top,
|
|
|
|
canvasWidth: rect.width,
|
|
|
|
canvasHeight: rect.height
|
|
|
|
};
|
|
|
|
window.vm.postIOData('mouse', data);
|
2016-08-15 21:37:36 -04:00
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
canvas.addEventListener('mouseup', function (e) {
|
2016-09-12 17:16:10 -04:00
|
|
|
var rect = canvas.getBoundingClientRect();
|
|
|
|
var data = {
|
|
|
|
isDown: false,
|
|
|
|
x: e.clientX - rect.left,
|
|
|
|
y: e.clientY - rect.top,
|
|
|
|
canvasWidth: rect.width,
|
|
|
|
canvasHeight: rect.height
|
|
|
|
};
|
|
|
|
window.vm.postIOData('mouse', data);
|
2016-08-15 21:37:36 -04:00
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
|
2016-09-02 11:23:09 -04:00
|
|
|
// Feed keyboard events as VM I/O events.
|
|
|
|
document.addEventListener('keydown', function (e) {
|
|
|
|
// Don't capture keys intended for Blockly inputs.
|
|
|
|
if (e.target != document && e.target != document.body) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
window.vm.postIOData('keyboard', {
|
|
|
|
keyCode: e.keyCode,
|
|
|
|
isDown: true
|
|
|
|
});
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
document.addEventListener('keyup', function(e) {
|
|
|
|
// Always capture up events,
|
|
|
|
// even those that have switched to other targets.
|
|
|
|
window.vm.postIOData('keyboard', {
|
|
|
|
keyCode: e.keyCode,
|
|
|
|
isDown: false
|
|
|
|
});
|
|
|
|
// E.g., prevent scroll.
|
|
|
|
if (e.target != document && e.target != document.body) {
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-06-01 10:04:56 -04:00
|
|
|
// Run threads
|
2016-06-21 15:30:36 -04:00
|
|
|
vm.start();
|
2016-06-01 14:17:18 -04:00
|
|
|
|
2016-07-01 11:52:43 -04:00
|
|
|
// Inform VM of animation frames.
|
|
|
|
var animate = function() {
|
2016-08-11 22:54:57 -04:00
|
|
|
stats.end();
|
|
|
|
stats.begin();
|
2016-07-01 11:52:43 -04:00
|
|
|
window.vm.animationFrame();
|
|
|
|
requestAnimationFrame(animate);
|
|
|
|
};
|
|
|
|
requestAnimationFrame(animate);
|
|
|
|
|
2016-06-01 14:17:18 -04:00
|
|
|
// Handlers for green flag and stop all.
|
|
|
|
document.getElementById('greenflag').addEventListener('click', function() {
|
2016-06-21 15:30:36 -04:00
|
|
|
vm.greenFlag();
|
2016-06-01 14:17:18 -04:00
|
|
|
});
|
|
|
|
document.getElementById('stopall').addEventListener('click', function() {
|
2016-06-21 15:30:36 -04:00
|
|
|
vm.stopAll();
|
2016-06-01 14:17:18 -04:00
|
|
|
});
|
2016-06-07 20:44:08 -04:00
|
|
|
|
|
|
|
var tabBlockExplorer = document.getElementById('tab-blockexplorer');
|
|
|
|
var tabThreadExplorer = document.getElementById('tab-threadexplorer');
|
2016-06-29 20:56:55 -04:00
|
|
|
var tabRenderExplorer = document.getElementById('tab-renderexplorer');
|
2016-09-02 08:27:43 -04:00
|
|
|
var tabImportExport = document.getElementById('tab-importexport');
|
2016-06-07 20:44:08 -04:00
|
|
|
|
|
|
|
// Handlers to show different explorers.
|
|
|
|
document.getElementById('threadexplorer-link').addEventListener('click',
|
|
|
|
function () {
|
2016-07-01 13:09:22 -04:00
|
|
|
window.exploreTabOpen = true;
|
|
|
|
getPlaygroundData();
|
2016-06-07 20:44:08 -04:00
|
|
|
tabBlockExplorer.style.display = 'none';
|
2016-06-29 20:56:55 -04:00
|
|
|
tabRenderExplorer.style.display = 'none';
|
2016-06-07 20:44:08 -04:00
|
|
|
tabThreadExplorer.style.display = 'block';
|
2016-09-02 08:27:43 -04:00
|
|
|
tabImportExport.style.display = 'none';
|
2016-06-07 20:44:08 -04:00
|
|
|
});
|
|
|
|
document.getElementById('blockexplorer-link').addEventListener('click',
|
|
|
|
function () {
|
2016-07-01 13:09:22 -04:00
|
|
|
window.exploreTabOpen = true;
|
|
|
|
getPlaygroundData();
|
2016-06-07 20:44:08 -04:00
|
|
|
tabBlockExplorer.style.display = 'block';
|
2016-06-29 20:56:55 -04:00
|
|
|
tabRenderExplorer.style.display = 'none';
|
|
|
|
tabThreadExplorer.style.display = 'none';
|
2016-09-02 08:27:43 -04:00
|
|
|
tabImportExport.style.display = 'none';
|
2016-06-29 20:56:55 -04:00
|
|
|
});
|
|
|
|
document.getElementById('renderexplorer-link').addEventListener('click',
|
|
|
|
function () {
|
2016-07-01 13:09:22 -04:00
|
|
|
window.exploreTabOpen = false;
|
2016-06-29 20:56:55 -04:00
|
|
|
tabBlockExplorer.style.display = 'none';
|
|
|
|
tabRenderExplorer.style.display = 'block';
|
2016-06-07 20:44:08 -04:00
|
|
|
tabThreadExplorer.style.display = 'none';
|
2016-09-02 08:27:43 -04:00
|
|
|
tabImportExport.style.display = 'none';
|
|
|
|
});
|
|
|
|
document.getElementById('importexport-link').addEventListener('click',
|
|
|
|
function () {
|
|
|
|
window.exploreTabOpen = false;
|
|
|
|
tabBlockExplorer.style.display = 'none';
|
|
|
|
tabRenderExplorer.style.display = 'none';
|
|
|
|
tabThreadExplorer.style.display = 'none';
|
|
|
|
tabImportExport.style.display = 'block';
|
2016-06-07 20:44:08 -04:00
|
|
|
});
|
2016-06-01 10:04:56 -04:00
|
|
|
};
|