Fixed conflicts, ready to merge.
This commit is contained in:
commit
a0c414dbac
9 changed files with 301 additions and 39 deletions
|
@ -8,6 +8,11 @@ Unimplementable Features on iOS: Image effects for whirl, fisheye, mosaic, and p
|
|||
|
||||
More documentation will be added as time permits. Thanks for contributing, and Scratch On!
|
||||
|
||||
## Contributions
|
||||
|
||||
Thank you for your interest in helping out with the Scratch HTML5 Player. [@sclements](https://github.com/sclements/) is the maintainer of the project and reviews all code before pull requests are approved. Though we appreciate all attempts to contribute, there are some contraints that must be met before pull requests can be approved. Here are our top concerns for contributions: matching the behavior and interface of the Flash player, code cleanliness and organization, and robust well tested logic. CSS goes into player.css (not in the html or javascript). Please use [compare.html](https://github.com/LLK/scratch-html5/blob/master/compare.html) to compare your work with the production Flash player.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
Running the HTML5 player on your own website, or locally, you will need to have
|
||||
|
|
9
compare.css
Normal file
9
compare.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
textarea {
|
||||
width: 450px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
object {
|
||||
display: block;
|
||||
}
|
||||
|
44
compare.html
44
compare.html
|
@ -8,8 +8,9 @@ header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
|||
<title>Scratch HTML5 vs. Flash</title>
|
||||
<meta charset="utf-8">
|
||||
<link href="player.css" type="text/css" rel="stylesheet" />
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
|
||||
<link href="compare.css" type="text/css" rel="stylesheet" />
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
|
||||
<script src="js/util/Timer.js"></script>
|
||||
<script src="js/util/OffsetBuffer.js"></script>
|
||||
<script src="js/util/Color.js"></script>
|
||||
|
@ -40,18 +41,16 @@ header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
|||
}
|
||||
</script>
|
||||
<script>
|
||||
var flashLog = null;
|
||||
$(function() {
|
||||
// The flashvars tell flash about the project data (and autostart=true)
|
||||
var flashvars = {
|
||||
server: encodeURIComponent('scratch.mit.edu'),
|
||||
debugOps: true,
|
||||
debugOpCmd: 'debugFlash',
|
||||
project_id: project_id
|
||||
};
|
||||
|
||||
// Pass in the cloud token for the project
|
||||
if (window.getCloudToken) {
|
||||
flashvars.cloud_token = encodeURIComponent(getCloudToken());
|
||||
}
|
||||
|
||||
var params = {
|
||||
allowscriptaccess: 'always',
|
||||
allowfullscreen: 'false',
|
||||
|
@ -62,7 +61,7 @@ header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
|||
var flashPlayer = null;
|
||||
//var swf_url = "http://cdn.scratch.mit.edu/scratchr2/static/Scratch.swf";
|
||||
var swf_url = "http://jiggler.media.mit.edu/shanemc/scratchr2/static/Scratch.swf";
|
||||
swfobject.embedSWF(swf_url, "flashScratch", "480", "400", "10.2.0",
|
||||
swfobject.embedSWF(swf_url, "flashScratch", "482", "402", "10.2.0",
|
||||
"http://cdn.scratch.mit.edu/scratchr2/static/expressInstall.swf",
|
||||
flashvars, params, null, function(e) {
|
||||
$('#flashScratch').css('visibility', 'visible');
|
||||
|
@ -77,11 +76,33 @@ header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
|||
$('#trigger_stop').click(function() {
|
||||
flashPlayer.ASstopRunning();
|
||||
});
|
||||
|
||||
flashLog = $('#flashDebug');
|
||||
var jsLog = $('#jsDebug');
|
||||
interp.debugOps = true;
|
||||
interp.debugFunc = function(opCount, opName, args) {
|
||||
var text = opCount + ': '+opName+'('+args.join(', ')+')';
|
||||
jsLog.val(jsLog.val() + text + "\n");
|
||||
}
|
||||
|
||||
// Setup synchronized scrolling
|
||||
flashLog.scroll(function() {
|
||||
jsLog.scrollTop(flashLog.scrollTop());
|
||||
});
|
||||
|
||||
jsLog.scroll(function() {
|
||||
flashLog.scrollTop(jsLog.scrollTop());
|
||||
});
|
||||
});
|
||||
|
||||
function debugFlash(opCount, opName, args) {
|
||||
var text = opCount + ': '+opName+'('+args.join(', ')+')';
|
||||
flashLog.val(flashLog.val() + text + "\n");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="display: inline-block;">
|
||||
<div class="playerContainer">
|
||||
<div id="up">▲</div>
|
||||
<div id="ui-top">
|
||||
<div id="info">Loading…</div>
|
||||
|
@ -98,15 +119,16 @@ header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
|
|||
<div id="ui-bottom">
|
||||
<span id="address-hint">http://scratch.mit.edu/projects/</span><input type="text" name="project_id" id="project_id" placeholder="10000160" /><button id='go_project'>→</button></div>
|
||||
<div id="down">▼</div>
|
||||
|
||||
<textarea id="jsDebug"></textarea>
|
||||
</div>
|
||||
<div style="display: inline-block;">
|
||||
<div class="playerContainer">
|
||||
<div id="flashScratch" style="text-align:center;visibility:hidden;">
|
||||
<p style="color:#aaa;font-size:22px;margin-top:14px;line-height:28px;">Oh Noes! Scratch project cannot display.<br/>Flash player is disabled, missing, or less than version 10.2.</p>
|
||||
<a href="http://www.adobe.com/go/getflashplayer">
|
||||
<img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" target="_blank" />
|
||||
</a>
|
||||
</div>
|
||||
<textarea id="flashDebug"></textarea>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<meta name="viewport" content="user-scalable=no, width=540" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<link href="player.css" type="text/css" rel="stylesheet" />
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
|
||||
<script src="js/util/Timer.js"></script>
|
||||
<script src="js/util/OffsetBuffer.js"></script>
|
||||
<script src="js/util/Color.js"></script>
|
||||
|
|
49
js/IO.js
49
js/IO.js
|
@ -91,20 +91,45 @@ IO.prototype.makeObjects = function() {
|
|||
runtime.stage.attach(runtime.scene);
|
||||
runtime.stage.attachPenLayer(runtime.scene);
|
||||
runtime.stage.loadSounds();
|
||||
|
||||
// Create the sprites
|
||||
$.each(this.data.children, function(index, obj) {
|
||||
// Create the sprites and watchers
|
||||
function createObj(obj, sprite) {
|
||||
var newSprite;
|
||||
if (!obj.cmd) {
|
||||
newSprite = new Sprite(obj);
|
||||
} else {
|
||||
newSprite = new Reporter(obj);
|
||||
runtime.reporters.push(newSprite);
|
||||
}
|
||||
runtime.sprites.push(newSprite);
|
||||
newSprite.attach(runtime.scene);
|
||||
if (!obj.cmd)
|
||||
function createSprite(obj) {
|
||||
var newSprite = new Sprite(obj);
|
||||
newSprite.loadSounds();
|
||||
return newSprite;
|
||||
}
|
||||
function createReporter(obj, sprite) {
|
||||
var newSprite;
|
||||
if (obj.listName) { // list
|
||||
if (!(sprite===runtime.stage && !runtime.stage.lists[obj.listName])) { // for local lists, only if in sprite
|
||||
newSprite = new List(obj, sprite.objName);
|
||||
runtime.reporters.push(newSprite);
|
||||
}
|
||||
} else {
|
||||
newSprite = new Reporter(obj);
|
||||
runtime.reporters.push(newSprite);
|
||||
}
|
||||
return newSprite;
|
||||
}
|
||||
if (obj.objName) { // sprite
|
||||
newSprite = createSprite(obj);
|
||||
sprite = newSprite;
|
||||
} else {
|
||||
newSprite = createReporter(obj, sprite);
|
||||
}
|
||||
if (newSprite) {
|
||||
runtime.sprites.push(newSprite);
|
||||
newSprite.attach(runtime.scene);
|
||||
}
|
||||
}
|
||||
$.each(this.data.children, function(index, obj) {
|
||||
createObj(obj, runtime.stage); // create children of stage - sprites, watchers, and stage's lists
|
||||
});
|
||||
$.each(runtime.sprites.filter(function(sprite){return sprite instanceof Sprite}), function(index, sprite) { // list of sprites
|
||||
$.each(sprite.lists, function(index, list){
|
||||
createObj(list, sprite); // create local lists
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -54,6 +54,9 @@ var Interpreter = function() {
|
|||
this.yield = false;
|
||||
this.doRedraw = false;
|
||||
this.opCount = 0; // used to benchmark the interpreter
|
||||
this.debugOps = false;
|
||||
this.debugFunc = null;
|
||||
this.opCount2 = 0;
|
||||
};
|
||||
|
||||
// Utilities for building blocks and sequences of blocks
|
||||
|
@ -136,6 +139,14 @@ Interpreter.prototype.stepActiveThread = function() {
|
|||
// Advance the "program counter" to the next block before running the primitive.
|
||||
// Control flow primitives (e.g. if) may change activeThread.nextBlock.
|
||||
this.activeThread.nextBlock = b.nextBlock;
|
||||
if(this.debugOps && this.debugFunc) {
|
||||
var finalArgs = new Array(b.args.length);
|
||||
for(var i=0; i<b.args.length; ++i)
|
||||
finalArgs[i] = this.arg(b, i);
|
||||
|
||||
this.debugFunc(this.opCount2, b.op, finalArgs);
|
||||
++this.opCount2;
|
||||
}
|
||||
b.primFcn(b);
|
||||
if (this.yield) { this.activeThread.nextBlock = b; return; }
|
||||
b = this.activeThread.nextBlock; // refresh local variable b in case primitive did some control flow
|
||||
|
|
|
@ -119,3 +119,81 @@ Reporter.prototype.changeSlider = function() {
|
|||
var variable = $(this).attr('data-var');
|
||||
target.variables[variable] = newValue;
|
||||
};
|
||||
|
||||
var List = function(data, sprite) {
|
||||
this.contents = data.contents;
|
||||
this.listName = data.listName;
|
||||
|
||||
this.height = data.height;
|
||||
this.width = data.width;
|
||||
this.x = data.x;
|
||||
this.y = data.y;
|
||||
this.z = io.getCount();
|
||||
this.visible = data.visible;
|
||||
|
||||
this.target = sprite;
|
||||
|
||||
// this.isPersistent = data.isPersistent;
|
||||
|
||||
this.el = null; // jQuery element for list
|
||||
this.containerEl = null;
|
||||
this.scrollbar = null;
|
||||
};
|
||||
|
||||
List.prototype.attach = function(scene) {
|
||||
this.el = $('<div class="list">');
|
||||
this.el.append('<div class="list-title">'+(this.target==='Stage'?'':this.target+' ')+this.listName);
|
||||
var c = this.containerEl = $('<div style="overflow:hidden;float:left;position:relative">').appendTo(this.el).width(this.width-13).height(this.height-34);
|
||||
var s = this.scrollbar = $('<div class="list-scrollbar-container"><div class="list-scrollbar">').appendTo(this.el);
|
||||
var sb = s.children('.list-scrollbar');
|
||||
sb.mousedown(function(e){
|
||||
if (e.which===1) $(this).data({scrolling:true,startY:e.pageY}); // left button
|
||||
});
|
||||
$('body').mousemove(function(e){
|
||||
if (sb.data('scrolling')) {
|
||||
var offset = parseInt(sb.css('top'))+e.pageY-sb.data('startY');
|
||||
if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
if (offset > c.height()-sb.height()) {
|
||||
offset = c.height()-sb.height();
|
||||
}
|
||||
sb.css('top',offset);
|
||||
c.scrollTop(c.height()/sb.height()*offset);
|
||||
}
|
||||
}).mouseup(function(){
|
||||
if ($.hasData(sb[0],'scrolling')) sb.removeData(['scrolling','startY']);
|
||||
});
|
||||
// this.el.append('<div class="list-add">+'); // disabled because it doesn't do anything even in the original
|
||||
this.el.append('<div class="list-length">length: '+this.contents.length);
|
||||
scene.append(this.el);
|
||||
this.update();
|
||||
this.el.css('left', this.x);
|
||||
this.el.css('top', this.y);
|
||||
this.el.width(this.width);
|
||||
this.el.height(this.height);
|
||||
this.el.css('z-index', this.z);
|
||||
this.el.css('display', this.visible ? 'inline-block' : 'none');
|
||||
};
|
||||
|
||||
List.prototype.update = function(){
|
||||
this.contents = findList(runtime.spriteNamed(this.target),this.listName);
|
||||
|
||||
this.el.css('display', this.visible ? 'inline-block' : 'none');
|
||||
if (!this.visible) return;
|
||||
|
||||
var c = this.containerEl.html(''); // so that it can be used inside the forEach
|
||||
this.contents.forEach(function(val,i){
|
||||
$('<div style="clear:both">').appendTo(c).append('<div class="list-index">'+(i+1),'<div class="list-item">'+val);
|
||||
});
|
||||
c.find('.list-index').width(c.find('.list-index').last().width());
|
||||
c.find('.list-item').width(c.width()-c.find('.list-index').width()-15);
|
||||
var s = this.scrollbar.height(c.height());
|
||||
s.children('.list-scrollbar').height(s.height()/c[0].scrollHeight*s.height()).css('display', s.children('.list-scrollbar').height()===c.height() ? 'none' : 'inline-block');
|
||||
this.el.find('.list-length').text('length: '+this.contents.length);
|
||||
};
|
||||
|
||||
List.prototype.updateLayer = function() {
|
||||
this.el.css('z-index', this.z);
|
||||
};
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ VarListPrims.prototype.addPrimsTo = function(primTable) {
|
|||
primTable['lineCountOfList:'] = this.primListLength;
|
||||
primTable['getLine:ofList:'] = this.primListGetLine;
|
||||
primTable['list:contains:'] = this.primListContains;
|
||||
primTable['hideList:'] = this.primHideList;
|
||||
primTable['showList:'] = this.primShowList;
|
||||
};
|
||||
|
||||
// Variable primitive implementations
|
||||
|
@ -74,9 +76,9 @@ VarListPrims.prototype.primChangeVar = function(b) {
|
|||
};
|
||||
|
||||
VarListPrims.prototype.primHideVar = function(b) {
|
||||
var targetVar = interp.arg(b, 0);
|
||||
var targetVar = interp.arg(b, 0), targetSprite = interp.targetSprite().objName;
|
||||
for (var r = 0; r < runtime.reporters.length; r++) {
|
||||
if (runtime.reporters[r].cmd == 'getVar:' && runtime.reporters[r].param == targetVar) {
|
||||
if (runtime.reporters[r].cmd == 'getVar:' && runtime.reporters[r].param == targetVar && (runtime.reporters[r].target == targetSprite || runtime.reporters[r].target == 'Stage')) {
|
||||
runtime.reporters[r].visible = false;
|
||||
return;
|
||||
}
|
||||
|
@ -84,9 +86,9 @@ VarListPrims.prototype.primHideVar = function(b) {
|
|||
};
|
||||
|
||||
VarListPrims.prototype.primShowVar = function(b) {
|
||||
var targetVar = interp.arg(b, 0);
|
||||
var targetVar = interp.arg(b, 0), targetSprite = interp.targetSprite().objName;
|
||||
for (var r = 0; r < runtime.reporters.length; r++) {
|
||||
if (runtime.reporters[r].cmd == 'getVar:' && runtime.reporters[r].param == targetVar) {
|
||||
if (runtime.reporters[r].cmd == 'getVar:' && runtime.reporters[r].param == targetVar && (runtime.reporters[r].target == targetSprite || runtime.reporters[r].target == 'Stage')) {
|
||||
runtime.reporters[r].visible = true;
|
||||
return;
|
||||
}
|
||||
|
@ -96,7 +98,7 @@ VarListPrims.prototype.primShowVar = function(b) {
|
|||
// List primitive implementations
|
||||
|
||||
// Take a list name and target sprite and return the JS list itself
|
||||
var findList = function(targetSprite, listName) {
|
||||
function findList(targetSprite, listName) {
|
||||
if (targetSprite == null) targetSprite = runtime.stage;
|
||||
if (listName in targetSprite.lists) {
|
||||
return targetSprite.lists[listName].contents;
|
||||
|
@ -104,7 +106,7 @@ var findList = function(targetSprite, listName) {
|
|||
return runtime.stage.lists[listName].contents;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
VarListPrims.prototype.primReadList = function(b) {
|
||||
var list = findList(interp.targetSprite(), interp.arg(b, 0));
|
||||
|
@ -181,3 +183,23 @@ VarListPrims.prototype.primListContains = function(b) {
|
|||
if (parseFloat(searchItem) == searchItem) searchItem = parseFloat(searchItem);
|
||||
return $.inArray(searchItem, list) > -1;
|
||||
};
|
||||
|
||||
VarListPrims.prototype.primHideList = function(b) {
|
||||
var targetList = interp.arg(b, 0), targetSprite = interp.targetSprite().objName;
|
||||
for (var r = 0; r < runtime.reporters.length; r++) {
|
||||
if (runtime.reporters[r] instanceof List && runtime.reporters[r].listName == targetList && (runtime.reporters[r].target == targetSprite || runtime.reporters[r].target == 'Stage')) {
|
||||
runtime.reporters[r].visible = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
VarListPrims.prototype.primShowList = function(b) {
|
||||
var targetList = interp.arg(b, 0), targetSprite = interp.targetSprite().objName;
|
||||
for (var r = 0; r < runtime.reporters.length; r++) {
|
||||
if (runtime.reporters[r] instanceof List && runtime.reporters[r].listName == targetList && (runtime.reporters[r].target == targetSprite || runtime.reporters[r].target == 'Stage')) {
|
||||
runtime.reporters[r].visible = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
106
player.css
106
player.css
|
@ -42,14 +42,9 @@ body {
|
|||
/* Hmm, will this break after the 501st sprite? */
|
||||
margin-left: 30px;
|
||||
}
|
||||
/* The green flag icon. */
|
||||
#greenSlideFg {
|
||||
background-color: #000;
|
||||
opacity: 0.2;
|
||||
width: 278px;
|
||||
height: 218px;
|
||||
padding-top: 142px;
|
||||
padding-left: 202px;
|
||||
#greenSlideFg {
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
padding: 140px 202px;
|
||||
}
|
||||
/* iPad arrow key frame */
|
||||
|
||||
|
@ -108,6 +103,10 @@ body {
|
|||
clear: both;
|
||||
text-align: right;
|
||||
}
|
||||
.playerContainer {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
/* Reporter styles */
|
||||
.reporter-normal {
|
||||
display: inline-block;
|
||||
|
@ -147,6 +146,96 @@ body {
|
|||
color: #fff;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* List watcher styles */
|
||||
.list {
|
||||
float: left;
|
||||
border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
border-style: solid;
|
||||
border-color: gray;
|
||||
border-width: 2px;
|
||||
background-color: #C1C4C7;
|
||||
padding-left: 3px;
|
||||
font: bold 11px sans-serif;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.list .list-title {
|
||||
padding-top: 2px;
|
||||
text-align: center;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.list .list-scrollbar-container {
|
||||
float: right;
|
||||
width: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.list .list-scrollbar {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 10px;
|
||||
background-color: #a8aaad;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.list .list-index {
|
||||
color: rgb(81, 81, 81);
|
||||
float: left;
|
||||
padding: 2px;
|
||||
margin-top: 0px;
|
||||
text-align: right;
|
||||
font: bold 11px;
|
||||
}
|
||||
|
||||
.list .list-item {
|
||||
float: right;
|
||||
height: 16px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0px;
|
||||
margin-right: 2px;
|
||||
padding-top: 2px;
|
||||
padding-left: 5px;
|
||||
border-color: white;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 4px;
|
||||
background-color: #cc5b22;
|
||||
color: white;
|
||||
word-wrap: break-word;
|
||||
font: bolder 11px sans-serif;
|
||||
}
|
||||
|
||||
.list .list-add {
|
||||
clear: both;
|
||||
background-color: #dedede;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 12px;
|
||||
color: #707070;
|
||||
font: bold 10px sans-serif;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
.list .list-length {
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
color: black;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
/* Say/think bubble styles */
|
||||
.bubble-container {
|
||||
position: absolute;
|
||||
|
@ -182,6 +271,7 @@ body {
|
|||
height: 19px;
|
||||
background: url(img/think-bottom.png) transparent no-repeat;
|
||||
}
|
||||
|
||||
#info {
|
||||
float: left;
|
||||
margin-left: 30px;
|
||||
|
|
Reference in a new issue