summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules12
m---------Snapin8r0
-rw-r--r--blocks.js36
-rw-r--r--github.js312
-rw-r--r--gui.js482
-rwxr-xr-x[-rw-r--r--]lang-ko.js492
-rw-r--r--objects.js179
m---------octokit.js0
-rw-r--r--promise-1.0.0.js684
-rwxr-xr-xsnap.html14
-rw-r--r--store.js4
-rw-r--r--threads.js60
m---------vkBeautify0
13 files changed, 2149 insertions, 126 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..c1a2763
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,12 @@
+[submodule "Snapin8r"]
+ path = Snapin8r
+ url = https://github.com/Hardmath123/Snapin8r
+[submodule "octokit.js"]
+ path = octokit.js
+ url = https://github.com/philschatz/octokit.js
+[submodule "vkBeautify"]
+ path = vkBeautify
+ url = https://github.com/vkiryukhin/vkBeautify
+[submodule "diff-merge"]
+ path = diff-merge
+ url = https://github.com/nighca/diff-merge
diff --git a/Snapin8r b/Snapin8r
new file mode 160000
+Subproject c9cebaa147c2c53520212a221efd4061a5dcb19
diff --git a/blocks.js b/blocks.js
index 35f31a4..6210e22 100644
--- a/blocks.js
+++ b/blocks.js
@@ -992,6 +992,8 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
null,
false,
{
+ 'any key': ['any key'],
+ 'number key': ['number key'],
'up arrow': ['up arrow'],
'down arrow': ['down arrow'],
'right arrow': ['right arrow'],
@@ -7772,6 +7774,8 @@ SymbolMorph.prototype.names = [
'pointRight',
'gears',
'file',
+ 'mutedSounds',
+ 'unmutedSounds',
'fullScreen',
'normalScreen',
'smallStage',
@@ -7896,6 +7900,10 @@ SymbolMorph.prototype.symbolCanvasColored = function (aColor) {
return this.drawSymbolGears(canvas, aColor);
case 'file':
return this.drawSymbolFile(canvas, aColor);
+ case 'mutedSounds':
+ return this.drawSymbolMutedSounds(canvas, aColor);
+ case 'unmutedSounds':
+ return this.drawSymbolUnmutedSounds(canvas, aColor);
case 'fullScreen':
return this.drawSymbolFullScreen(canvas, aColor);
case 'normalScreen':
@@ -8098,6 +8106,34 @@ SymbolMorph.prototype.drawSymbolFile = function (canvas, color) {
return canvas;
};
+SymbolMorph.prototype.drawSymbolMutedSounds = function (canvas, color) {
+ // answer a canvas showing a muted sounds toggling symbol
+ var ctx = canvas.getContext('2d'),
+ w = canvas.width,
+ h = canvas.height,
+ w2 = w / 2,
+ h2 = h / 2;
+
+ ctx.fillStyle = color.darker(40).toString();
+ ctx.fillRect(0, 0, w, h);
+
+ return canvas;
+};
+
+SymbolMorph.prototype.drawSymbolUnmutedSounds = function (canvas, color) {
+ // answer a canvas showing a UNmuted sounds toggling symbol
+ var ctx = canvas.getContext('2d'),
+ w = canvas.width,
+ h = canvas.height,
+ w2 = w / 2,
+ h2 = h / 2;
+
+ ctx.fillStyle = color.darker(60).toString();
+ ctx.fillRect(0, 0, w, h);
+
+ return canvas;
+};
+
SymbolMorph.prototype.drawSymbolFullScreen = function (canvas, color) {
// answer a canvas showing two arrows pointing diagonally outwards
var ctx = canvas.getContext('2d'),
diff --git a/github.js b/github.js
new file mode 100644
index 0000000..5294037
--- /dev/null
+++ b/github.js
@@ -0,0 +1,312 @@
+/*
+
+ github.js
+
+ a GitHubBackend backend API for SNAP!
+
+ written by Gubolin, based on cloud.js by Jens Mönig
+
+ Copyright (C) 2014 by Jens Mönig, Gubolin
+
+ This file is part of Snap!.
+
+ Snap! is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+// Global settings /////////////////////////////////////////////////////
+
+/*global modules, localize*/
+
+modules.github = '2014-July-31';
+
+// Global stuff
+
+var GitHubBackend;
+
+var GitHub = new GitHubBackend();
+
+// GitHubBackend /////////////////////////////////////////////////////////////
+
+function GitHubBackend() {
+ this.gh = null;
+ this.username = null;
+ this.password = null; // TODO saved as plain text
+}
+
+GitHubBackend.prototype.clear = function () {
+ this.gh = null;
+ this.username = null;
+ this.password = null;
+};
+
+// GitHubBackend: Snap! API
+
+GitHubBackend.prototype.getProject = function (
+ userName,
+ projectName,
+ callBack,
+ errorCall,
+ commitSha
+) {
+ var myself = this;
+
+ if (myself.gh === null) {
+ myself.gh = new Octokit();
+ }
+
+ var repo = myself.gh.getRepo(userName, projectName);
+ var branch = repo.getBranch(); // master (default)
+ branch.getCommits({}).then(
+ function (commits) {
+ branch.read('snap.xml', false).then(
+ function (sourceContent) {
+ callBack.call(
+ null,
+ sourceContent.content,
+ commits[0].sha
+ );
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+};
+
+GitHubBackend.prototype.login = function (
+ username,
+ password,
+ validateData,
+ callBack,
+ errorCall
+) {
+ var myself = this;
+ var me;
+
+ myself.gh = new Octokit({
+ username: username,
+ password: password
+ });
+
+ if (validateData === true) {
+ me = myself.gh.getUser();
+ if (me !== null) {
+ me.getInfo().then(
+ function() {
+ myself.username = username;
+ myself.password = password;
+
+ callBack.call(myself);
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ } else {
+ errorCall.call(myself, localize('Something went wrong :('), 'GitHub');
+ }
+ } else {
+ myself.username = username;
+ myself.password = password;
+
+ callBack.call(myself);
+ }
+};
+
+GitHubBackend.prototype.saveProject = function (commitMessage, parentCommitSha, lastCommit, ide, callBack, errorCall) {
+ var myself = this,
+ data;
+ var pdata, media;
+ var repoName = ide.projectName.replace(/[^\w-]/g, ''); // TODO validation of project name
+
+ ide.stage.fireStopAllEvent();
+ ide.serializer.isCollectingMedia = true;
+ pdata = ide.serializer.serialize(ide.stage);
+ media = ide.hasChangedMedia ?
+ ide.serializer.mediaXML(ide.projectName) : null;
+ data = '<snapdata>\n' + pdata + '\n' + media + '\n</snapdata>';
+
+ // check if serialized data can be parsed back again
+ try {
+ ide.serializer.parse(pdata);
+ } catch (err) {
+ ide.showMessage('Serialization of program data failed:\n' + err);
+ throw new Error('Serialization of program data failed:\n' + err);
+ }
+ if (media !== null) {
+ try {
+ ide.serializer.parse(media);
+ } catch (err) {
+ ide.showMessage('Serialization of media failed:\n' + err);
+ throw new Error('Serialization of media failed:\n' + err);
+ }
+ }
+ ide.serializer.isCollectingMedia = false;
+ ide.serializer.flushMedia();
+
+ myself.getProjectList(
+ function (projects) {
+ var exists = false;
+
+ projects.forEach(function (project) {
+ if (project.ProjectName.indexOf(repoName) > -1) {
+ exists = true;
+ return;
+ }
+ });
+
+ pushChanges = function () {
+ if (myself.gh !== null) {
+ var repo = myself.gh.getRepo(myself.username, ide.projectName);
+ var branch = repo.getBranch(); // master (default)
+ var message = commitMessage;
+
+ writeChanges = function (code, pcSha) {
+ if (pcSha !== parentCommitSha && data !== code) {
+ var compareResult = window.diff.compare(lastCommit, data, '\n'); // get diff from last push
+ data = window.diff.merge(code, compareResult); // apply it to the latest code
+ console.log(data);
+ }
+
+ var contents = {
+ 'snap.xml': data,
+ 'README.md': ide.projectNotes
+ };
+
+ branch.writeMany(contents, message, pcSha).then(
+ function () {
+ callBack.call();
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ };
+
+ if (parentCommitSha !== null) { // repo was just created
+ myself.getProject(myself.username, ide.projectName,
+ writeChanges,
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ } else {
+ writeChanges(data, parentCommitSha);
+ }
+ }
+ };
+
+ if (exists === false){
+ myself.gh.getUser().createRepo(repoName, { // these should be discussed
+ 'description': 'Snap! Project - http://gubolin.github.io/snap/index.html#github:Username=' + myself.username + '&projectName=' + repoName,
+ 'has_wiki': 'false',
+ 'has_downloads': 'false',
+ 'auto_init': true,
+ 'license_template': 'mit' // discuss
+ }).then(
+ pushChanges,
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ } else {
+ pushChanges();
+ }
+
+ },
+ function (error) {
+ errorCall.call(null, error, 'GitHub');
+ }
+ );
+};
+
+GitHubBackend.prototype.getProjectList = function (callBack, errorCall) {
+ var myself = this;
+
+ if (myself.gh !== null){
+ var user = myself.gh.getUser();
+
+ if (user === null) {
+ myself.message('You are not logged in');
+ return;
+ }
+
+ user.getRepos().then(
+ function (repos) {
+ var snapProjects = [];
+
+ var modCallBack = (function () {
+ var called = 0;
+ return function () {
+ if (++called == repos.length) {
+ callBack.call(myself, snapProjects);
+ }
+ };
+ })();
+
+ if (repos.length === 0) {
+ callBack.call(myself, snapProjects);
+ }
+
+ repos.forEach(function (repo) {
+ if (repo.description.indexOf('Snap! Project') > -1) { // TODO nicer detection
+ var project, ghrepo, branch;
+
+ ghrepo = myself.gh.getRepo(repo.owner.login, repo.name);
+ branch = ghrepo.getBranch(); // master (default)
+
+ branch.read('README.md', false).then(
+ function (notesContent) {
+ project = {
+ 'ProjectName': repo.name,
+ 'Notes': notesContent.content,
+ 'Updated': repo.updated_at.replace(/T/, ' ').replace(/Z/, '') // TODO this could be better
+ };
+
+ snapProjects.push(project);
+ modCallBack();
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ } else {
+ modCallBack();
+ }
+ });
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ } else {
+ myself.message('You are not logged in');
+ return;
+ }
+};
+
+GitHubBackend.prototype.logout = function (callBack) {
+ this.clear();
+};
+
+// GitHub: user messages (to be overridden)
+
+GitHubBackend.prototype.message = function (string) {
+ alert(string);
+};
diff --git a/gui.js b/gui.js
index 063433a..b60f5e6 100644
--- a/gui.js
+++ b/gui.js
@@ -63,7 +63,7 @@ Costume, CostumeEditorMorph, MorphicPreferences, touchScreenSettings,
standardSettings, Sound, BlockMorph, ToggleMorph, InputSlotDialogMorph,
ScriptsMorph, isNil, SymbolMorph, BlockExportDialogMorph,
BlockImportDialogMorph, SnapTranslator, localize, List, InputSlotMorph,
-SnapCloud, Uint8Array, HandleMorph, SVG_Costume, fontHeight, hex_sha512,
+SnapCloud, GitHub, Uint8Array, HandleMorph, SVG_Costume, fontHeight, hex_sha512,
sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio,
SpeechBubbleMorph*/
@@ -212,6 +212,7 @@ IDE_Morph.prototype.init = function (isAutoFill) {
this.corral = null;
this.isAutoFill = isAutoFill || true;
+ this.isMuted = false;
this.isAppMode = false;
this.isSmallStage = false;
this.filePicker = null;
@@ -221,6 +222,8 @@ IDE_Morph.prototype.init = function (isAutoFill) {
this.stageRatio = 1; // for IDE animations, e.g. when zooming
this.loadNewProject = false; // flag when starting up translated
+ this.parentCommitSha = null; // for GitHub
+ this.lastCommit = null; // for GitHub
this.shield = null;
// initialize inherited properties:
@@ -228,6 +231,8 @@ IDE_Morph.prototype.init = function (isAutoFill) {
// override inherited properites:
this.color = this.backgroundColor;
+
+ setInterval(this.save, 1000 * 60 * 60 * 5); // every 5 minutes
};
IDE_Morph.prototype.openIn = function (world) {
@@ -236,6 +241,7 @@ IDE_Morph.prototype.openIn = function (world) {
// get persistent user data, if any
if (localStorage) {
usr = localStorage['-snap-user'];
+ ghusr = localStorage['-snap-ghuser'];
if (usr) {
usr = SnapCloud.parseResponse(usr)[0];
if (usr) {
@@ -246,6 +252,13 @@ IDE_Morph.prototype.openIn = function (world) {
}
}
}
+ if (ghusr) {
+ ghusr = SnapCloud.parseResponse(ghusr)[0];
+ if (ghusr) {
+ GitHub.login(ghusr.username, ghusr.password, false,
+ function() {}, myself.githubError());
+ }
+ }
}
this.buildPanes();
@@ -263,6 +276,16 @@ IDE_Morph.prototype.openIn = function (world) {
}, 2000);
};
+ GitHub.message = function (string) {
+ var m = new MenuMorph(null, string),
+ intervalHandle;
+ m.popUpCenteredInWorld(world);
+ intervalHandle = setInterval(function () {
+ m.destroy();
+ clearInterval(intervalHandle);
+ }, 2000);
+ };
+
// prevent non-DialogBoxMorphs from being dropped
// onto the World in user-mode
world.reactToDropOf = function (morph) {
@@ -287,7 +310,8 @@ IDE_Morph.prototype.openIn = function (world) {
}
throw new Error('unable to retrieve ' + url);
} catch (err) {
- return;
+ myself.showMessage('unable to retrieve project');
+ return '';
}
}
@@ -374,6 +398,41 @@ IDE_Morph.prototype.openIn = function (world) {
},
this.cloudError()
);
+ } else if (location.hash.substr(0, 8) === '#github:') {
+ this.shield = new Morph();
+ this.shield.color = this.color;
+ this.shield.setExtent(this.parent.extent());
+ this.parent.add(this.shield);
+ myself.showMessage('Fetching project\nfrom GitHub...');
+
+ dict = SnapCloud.parseDict(location.hash.substr(8));
+
+ GitHub.getProject(
+ dict.Username,
+ dict.projectName,
+ function (code, pcSha) {
+ var msg;
+ myself.nextSteps([
+ function () {
+ msg = myself.showMessage('Opening GitHub project...');
+ },
+ function () {
+ myself.parentCommitSha = pcSha;
+ myself.lastCommit = code;
+ myself.rawOpenCloudDataString(code);
+ myself.hasChangedMedia = true;
+ },
+ function () {
+ myself.shield.destroy();
+ myself.shield = null;
+ msg.destroy();
+ myself.toggleAppMode(true);
+ myself.runScripts();
+ }
+ ]);
+ },
+ this.githubError()
+ );
} else if (location.hash.substr(0, 6) === '#lang:') {
urlLanguage = location.hash.substr(6);
this.setLanguage(urlLanguage);
@@ -458,6 +517,7 @@ IDE_Morph.prototype.createControlBar = function () {
stopButton,
pauseButton,
startButton,
+ muteSoundsButton,
projectButton,
settingsButton,
stageSizeButton,
@@ -547,6 +607,38 @@ IDE_Morph.prototype.createControlBar = function () {
this.controlBar.add(appModeButton);
this.controlBar.appModeButton = appModeButton; // for refreshing
+ //muteSoundsButton
+ button = new ToggleButtonMorph(
+ null, //colors,
+ myself, // the IDE is the target
+ 'toggleMuteSounds',
+ [
+ new SymbolMorph('mutedSounds', 14),
+ new SymbolMorph('unmutedSounds', 14)
+ ],
+ function () { // query
+ return myself.isMuted;
+ }
+ );
+
+ button.corner = 12;
+ button.color = colors[0];
+ button.highlightColor = colors[1];
+ button.pressColor = colors[2];
+ button.labelMinExtent = new Point(36, 18);
+ button.padding = 0;
+ button.labelShadowOffset = new Point(-1, -1);
+ button.labelShadowColor = colors[1];
+ button.labelColor = this.buttonLabelColor;
+ button.contrast = this.buttonContrast;
+ button.drawNew();
+ // button.hint = 'sounds\nmuted & unmuted';
+ button.fixLayout();
+ button.refresh();
+ muteSoundsButton = button;
+ this.controlBar.add(muteSoundsButton);
+ this.controlBar.muteSoundsButton = button; // for refreshing
+
// stopButton
button = new PushButtonMorph(
this,
@@ -711,7 +803,7 @@ IDE_Morph.prototype.createControlBar = function () {
myself.right() - StageMorph.prototype.dimensions.x *
(myself.isSmallStage ? myself.stageRatio : 1)
);
- [stageSizeButton, appModeButton].forEach(
+ [stageSizeButton, appModeButton, muteSoundsButton].forEach(
function (button) {
x += padding;
button.setCenter(myself.controlBar.center());
@@ -1571,7 +1663,12 @@ IDE_Morph.prototype.droppedBinary = function (anArrayBuffer, name) {
myself = this,
suffix = name.substring(name.length - 3);
- if (suffix.toLowerCase() !== 'ypr') {return; }
+ if (suffix.toLowerCase() !== 'ypr') {
+ var zip = new JSZip(anArrayBuffer);
+ myself.droppedText(Snapin8r(zip));
+
+ return;
+ }
function loadYPR(buffer, lbl) {
var reader = new sb.Reader(),
@@ -1974,6 +2071,18 @@ IDE_Morph.prototype.cloudMenu = function () {
'changeCloudPassword'
);
}
+
+ if (!GitHub.username) {
+ menu.addItem(
+ 'Login via GitHub...',
+ 'initializeGitHub'
+ );
+ } else {
+ menu.addItem(
+ localize('Logout') + ' ' + GitHub.username + ' ' + localize('from GitHub'),
+ 'logoutGitHub'
+ );
+ }
if (shiftClicked) {
menu.addLine();
menu.addItem(
@@ -2292,6 +2401,9 @@ IDE_Morph.prototype.projectMenu = function () {
menu.addItem('New', 'createNewProject');
menu.addItem('Open...', 'openProjectsBrowser');
menu.addItem('Save', "save");
+ if (GitHub.username) {
+ menu.addItem('Save with commit message', 'commitProjectToGitHub');
+ }
if (shiftClicked) {
menu.addItem(
'Save to disk',
@@ -2742,8 +2854,10 @@ IDE_Morph.prototype.save = function () {
if (this.projectName) {
if (this.source === 'local') { // as well as 'examples'
this.saveProject(this.projectName);
- } else { // 'cloud'
+ } else if (this.source === 'cloud') { // 'cloud'
this.saveProjectToCloud(this.projectName);
+ } else { // 'github'
+ this.saveProjectToGitHub(this.projectName);
}
} else {
this.saveProjectsBrowser();
@@ -3388,6 +3502,27 @@ IDE_Morph.prototype.toggleStageSize = function (isSmall) {
}
};
+IDE_Morph.prototype.toggleMuteSounds = function (isMuted) {
+ this.isMuted = isNil(isMuted) ? !this.isMuted : isMuted;
+ this.controlBar.muteSoundsButton.refresh();
+
+ /* stage.activeSounds holds all active sounds
+ * a sprite's .activeSounds holds just its own
+ * so you have to use the stage to mute
+ * and the sprite to unmute, because the stage's volume
+ * overrides the sprite's one.
+ */
+
+ if (this.isMuted === false) {
+ this.stage.unmuteAllSounds();
+ this.sprites.asArray().forEach(function (sprt) {
+ sprt.unmuteAllSounds();
+ });
+ } else {
+ this.stage.muteAllSounds();
+ }
+};
+
IDE_Morph.prototype.createNewProject = function () {
var myself = this;
this.confirm(
@@ -3449,6 +3584,12 @@ IDE_Morph.prototype.setLanguage = function (lang, callback) {
IDE_Morph.prototype.reflectLanguage = function (lang, callback) {
var projectData;
SnapTranslator.language = lang;
+ this.world().children.forEach(function (morph) {
+ if (morph instanceof BlockEditorMorph) {
+ morph.updateDefinition(); // save custom blocks
+ // otherwise, initBlocks() will reset the definition
+ }
+ });
if (!this.loadNewProject) {
if (Process.prototype.isCatchingErrors) {
try {
@@ -3680,6 +3821,48 @@ IDE_Morph.prototype.initializeCloud = function () {
);
};
+IDE_Morph.prototype.initializeGitHub = function () {
+ var myself = this,
+ world = this.world();
+ new DialogBoxMorph(
+ null,
+ function (user) {
+ var pw = user.password,
+ str;
+ GitHub.login(
+ user.username,
+ pw,
+ true,
+ function () {
+ if (user.choice) {
+ str = SnapCloud.encodeDict(
+ {
+ username: user.username,
+ password: pw
+ }
+ );
+ localStorage['-snap-ghuser'] = str;
+ }
+ myself.source = 'github';
+ myself.showMessage('now connected.', 2);
+ },
+ myself.githubError()
+ );
+ }
+ ).withKey('cloudlogin').promptCredentials(
+ 'Sign in with your GitHub account',
+ 'login',
+ null,
+ null,
+ null,
+ null,
+ 'stay signed in on this computer\nuntil logging out',
+ world,
+ myself.cloudIcon(),
+ myself.cloudMsg
+ );
+};
+
IDE_Morph.prototype.createCloudAccount = function () {
var myself = this,
world = this.world();
@@ -3807,6 +3990,19 @@ IDE_Morph.prototype.logout = function () {
);
};
+IDE_Morph.prototype.logoutGitHub = function () {
+ var myself = this;
+ delete localStorage['-snap-ghuser'];
+ GitHub.logout(
+ function () {
+ myself.showMessage('disconnected.', 2);
+ },
+ function () {
+ myself.showMessage('disconnected.', 2);
+ }
+ );
+};
+
IDE_Morph.prototype.saveProjectToCloud = function (name) {
var myself = this;
if (name) {
@@ -3820,6 +4016,90 @@ IDE_Morph.prototype.saveProjectToCloud = function (name) {
}
};
+IDE_Morph.prototype.saveProjectToGitHub = function (name, commitMessage) {
+ var myself = this;
+ if (name) {
+ this.showMessage('Comitting project\nto GitHub...');
+ this.setProjectName(name);
+ GitHub.saveProject(
+ commitMessage,
+ this.parentCommitSha,
+ this.lastCommit,
+ this,
+ function () {
+ GitHub.getProject(
+ GitHub.username,
+ name,
+ function (code, pcSha) {
+ myself.source = 'github';
+ myself.parentCommitSha = pcSha;
+ myself.lastCommit = code;
+ myself.droppedText(code);
+ },
+ myself.githubError()
+ );
+ },
+ this.githubError()
+ );
+ }
+};
+
+IDE_Morph.prototype.commitProjectToGitHub = function () {
+ var dialog = new DialogBoxMorph().withKey('commitMessage'),
+ frame = new ScrollFrameMorph(),
+ text = new TextMorph(''),
+ ok = dialog.ok,
+ myself = this,
+ size = 120,
+ world = this.world();
+
+ frame.padding = 6;
+ frame.setWidth(size);
+ frame.acceptsDrops = false;
+ frame.contents.acceptsDrops = false;
+
+ text.setWidth(size - frame.padding * 2);
+ text.setPosition(frame.topLeft().add(frame.padding));
+ text.enableSelecting();
+ text.isEditable = true;
+
+ frame.setHeight(size);
+ frame.fixLayout = nop;
+ frame.edge = InputFieldMorph.prototype.edge;
+ frame.fontSize = InputFieldMorph.prototype.fontSize;
+ frame.typeInPadding = InputFieldMorph.prototype.typeInPadding;
+ frame.contrast = InputFieldMorph.prototype.contrast;
+ frame.drawNew = InputFieldMorph.prototype.drawNew;
+ frame.drawRectBorder = InputFieldMorph.prototype.drawRectBorder;
+
+ frame.addContents(text);
+ text.drawNew();
+
+ dialog.ok = function () {
+ myself.saveProjectToGitHub(
+ myself.projectName,
+ text.text
+ );
+
+ ok.call(this);
+ };
+
+ dialog.justDropped = function () {
+ text.edit();
+ };
+
+ dialog.labelString = 'Commit message';
+ dialog.createLabel();
+ dialog.addBody(frame);
+ frame.drawNew();
+ dialog.addButton('ok', 'OK');
+ dialog.addButton('cancel', 'Cancel');
+ dialog.fixLayout();
+ dialog.drawNew();
+ dialog.popUp(world);
+ dialog.setCenter(world.center());
+ text.edit();
+};
IDE_Morph.prototype.exportProjectMedia = function (name) {
var menu, media;
this.serializer.isCollectingMedia = true;
@@ -4016,6 +4296,42 @@ IDE_Morph.prototype.cloudError = function () {
};
};
+IDE_Morph.prototype.githubError = function () {
+ var myself = this;
+
+ function getURL(url) {
+ try {
+ var request = new XMLHttpRequest();
+ request.open('GET', url, false);
+ request.send();
+ if (request.status === 200) {
+ return request.responseText;
+ }
+ return null;
+ } catch (err) {
+ return null;
+ }
+ }
+
+ return function (responseText, url) {
+ var response = responseText;
+ if (myself.shield) {
+ myself.shield.destroy();
+ myself.shield = null;
+ }
+ if (response.length > 50) {
+ response = response.substring(0, 50) + '...';
+ }
+ new DialogBoxMorph().inform(
+ 'GitHub',
+ (url ? url + '\n' : '')
+ + response,
+ myself.world(),
+ myself.cloudIcon(null, new Color(180, 0, 0))
+ );
+ };
+};
+
IDE_Morph.prototype.cloudIcon = function (height, color) {
var clr = color || DialogBoxMorph.prototype.titleBarColor,
isFlat = MorphicPreferences.isFlat,
@@ -4153,7 +4469,7 @@ ProjectDialogMorph.prototype.init = function (ide, task) {
// additional properties:
this.ide = ide;
this.task = task || 'open'; // String describing what do do (open, save)
- this.source = ide.source || 'local'; // or 'cloud' or 'examples'
+ this.source = ide.source || 'local'; // or 'cloud' or 'github' or 'examples'
this.projectList = []; // [{name: , thumb: , notes:}]
this.handle = null;
@@ -4213,6 +4529,7 @@ ProjectDialogMorph.prototype.buildContents = function () {
}
this.addSourceButton('cloud', localize('Cloud'), 'cloud');
+ this.addSourceButton('github', localize('GitHub'), 'github');
this.addSourceButton('local', localize('Browser'), 'storage');
if (this.task === 'open') {
this.addSourceButton('examples', localize('Examples'), 'poster');
@@ -4461,6 +4778,20 @@ ProjectDialogMorph.prototype.setSource = function (source) {
}
);
return;
+ case 'github':
+ msg = myself.ide.showMessage('Updating\nproject list...');
+ this.projectList = [];
+ GitHub.getProjectList(
+ function (projectList) {
+ myself.installGitHubProjectList(projectList);
+ msg.destroy();
+ },
+ function (err, lbl) {
+ msg.destroy();
+ myself.ide.githubError().call(null, err, lbl);
+ }
+ );
+ return;
case 'examples':
this.projectList = this.getExamplesProjectList();
break;
@@ -4513,7 +4844,7 @@ ProjectDialogMorph.prototype.setSource = function (source) {
}
myself.edit();
};
- } else { // 'examples', 'cloud' is initialized elsewhere
+ } else { // 'examples', 'cloud' and 'github' is initialized elsewhere
this.listField.action = function (item) {
var src, xml;
if (item === undefined) {return; }
@@ -4679,6 +5010,69 @@ ProjectDialogMorph.prototype.installCloudProjectList = function (pl) {
}
};
+ProjectDialogMorph.prototype.installGitHubProjectList = function (pl) {
+ var myself = this;
+ this.projectList = pl || [];
+ this.projectList.sort(function (x, y) {
+ return x.ProjectName < y.ProjectName ? -1 : 1;
+ });
+
+ this.listField.destroy();
+ this.listField = new ListMorph(
+ this.projectList,
+ this.projectList.length > 0 ?
+ function (element) {
+ return element.ProjectName;
+ } : null,
+ [],
+ function () {myself.ok(); }
+ );
+ this.fixListFieldItemColors();
+ this.listField.fixLayout = nop;
+ this.listField.edge = InputFieldMorph.prototype.edge;
+ this.listField.fontSize = InputFieldMorph.prototype.fontSize;
+ this.listField.typeInPadding = InputFieldMorph.prototype.typeInPadding;
+ this.listField.contrast = InputFieldMorph.prototype.contrast;
+ this.listField.drawNew = InputFieldMorph.prototype.drawNew;
+ this.listField.drawRectBorder = InputFieldMorph.prototype.drawRectBorder;
+
+ this.listField.action = function (item) {
+ if (item === undefined) {return; }
+ if (myself.nameField) {
+ myself.nameField.setContents(item.ProjectName || '');
+ }
+ if (myself.task === 'open') {
+ myself.notesText.text = item.Notes || '';
+ myself.notesText.drawNew();
+ myself.notesField.contents.adjustBounds();
+ myself.preview.texture = item.Thumbnail || null;
+ myself.preview.cachedTexture = null;
+ myself.preview.drawNew();
+ (new SpeechBubbleMorph(new TextMorph(
+ localize('last changed') + '\n' + item.Updated,
+ null,
+ null,
+ null,
+ null,
+ 'center'
+ ))).popUp(
+ myself.world(),
+ myself.preview.rightCenter().add(new Point(2, 0))
+ );
+ }
+ myself.buttons.fixLayout();
+ myself.fixLayout();
+ myself.edit();
+ };
+ this.body.add(this.listField);
+ this.deleteButton.show();
+ this.buttons.fixLayout();
+ this.fixLayout();
+ if (this.task === 'open') {
+ this.clearDetails();
+ }
+};
+
ProjectDialogMorph.prototype.clearDetails = function () {
this.notesText.text = '';
this.notesText.drawNew();
@@ -4695,6 +5089,8 @@ ProjectDialogMorph.prototype.openProject = function () {
this.ide.source = this.source;
if (this.source === 'cloud') {
this.openCloudProject(proj);
+ } else if (this.source === 'github') {
+ this.openGitHubProject(proj);
} else if (this.source === 'examples') {
src = this.ide.getURL(
'http://snap.berkeley.edu/snapsource/Examples/' +
@@ -4720,6 +5116,23 @@ ProjectDialogMorph.prototype.openCloudProject = function (project) {
]);
};
+ProjectDialogMorph.prototype.openGitHubProject = function (project, user) {
+ var myself = this;
+
+ if (user == null) { // jshint ignore:line
+ user = GitHub.username;
+ }
+
+ myself.ide.nextSteps([
+ function () {
+ myself.ide.showMessage('Fetching project\nfrom GitHub...');
+ },
+ function () {
+ myself.rawOpenGitHubProject(project, user);
+ }
+ ]);
+};
+
ProjectDialogMorph.prototype.rawOpenCloudProject = function (proj) {
var myself = this;
SnapCloud.reconnect(
@@ -4746,6 +5159,21 @@ ProjectDialogMorph.prototype.rawOpenCloudProject = function (proj) {
this.destroy();
};
+ProjectDialogMorph.prototype.rawOpenGitHubProject = function (proj, user) {
+ var myself = this;
+ GitHub.getProject(
+ user,
+ proj.ProjectName,
+ function (code, pcSha) {
+ myself.ide.source = 'github';
+ myself.ide.parentCommitSha = pcSha;
+ myself.ide.lastCommit = code;
+ myself.ide.droppedText(code);
+ },
+ myself.ide.githubError()
+ );
+ this.destroy();
+};
ProjectDialogMorph.prototype.saveProject = function () {
var name = this.nameField.contents().text.text,
notes = this.notesText.text,
@@ -4772,6 +5200,25 @@ ProjectDialogMorph.prototype.saveProject = function () {
this.ide.setProjectName(name);
myself.saveCloudProject();
}
+ } else if (this.source === 'github') {
+ if (detect(
+ this.projectList,
+ function (item) {return item.ProjectName === name; }
+ )) {
+ this.ide.confirm(
+ localize(
+ 'Are you sure you want to replace'
+ ) + '\n"' + name + '"?',
+ 'Replace Project',
+ function () {
+ myself.ide.setProjectName(name);
+ myself.saveGitHubProject();
+ }
+ );
+ } else {
+ this.ide.setProjectName(name);
+ myself.saveGitHubProject();
+ }
} else { // 'local'
if (detect(
this.projectList,
@@ -4813,13 +5260,32 @@ ProjectDialogMorph.prototype.saveCloudProject = function () {
this.destroy();
};
+ProjectDialogMorph.prototype.saveGitHubProject = function () {
+ var myself = this;
+ this.ide.showMessage('Committing project\nto GitHub...');
+ GitHub.saveProject(
+ null,
+ this.ide.parentCommitSha,
+ this.ide.lastCommit,
+ this.ide,
+ function () {
+ myself.ide.source = 'github';
+ myself.ide.showMessage('saved.', 2);
+ },
+ this.ide.githubError()
+ );
+ this.destroy();
+};
+
ProjectDialogMorph.prototype.deleteProject = function () {
var myself = this,
proj,
idx,
name;
- if (this.source === 'cloud') {
+ if (this.source === 'github') {
+ // TODO: not implemented
+ } else if (this.source === 'cloud') {
proj = this.listField.selected;
if (proj) {
this.ide.confirm(
diff --git a/lang-ko.js b/lang-ko.js
index a1e50eb..3241c7f 100644..100755
--- a/lang-ko.js
+++ b/lang-ko.js
@@ -179,13 +179,13 @@ SnapTranslator.dict.ko = {
// translations meta information
'language_name':
- 'Korean', // the name as it should appear in the language menu
+ '한국어', // the name as it should appear in the language menu
'language_translator':
'Yunjae Jang', // your name for the Translators tab
'translator_e-mail':
- 'yunjae.jang@inc.korea.ac.kr', // optional
+ 'janggoons@gmail.com', // optional
'last_changed':
- '2012-11-18', // this, too, will appear in the Translators tab
+ '2014-11-07', // this, too, will appear in the Translators tab
// GUI
// control bar:
@@ -218,7 +218,7 @@ SnapTranslator.dict.ko = {
// editor:
'draggable':
- '드래그 가능?',
+ '마우스로 직접 움직이기',
// tabs:
'Scripts':
@@ -243,8 +243,16 @@ SnapTranslator.dict.ko = {
'왼쪽에서 오른쪽으로만',
// new sprite button:
- 'add a new sprite':
- '새로운 스프라이트 추가',
+ 'add a new Turtle sprite':
+ '새로운 스프라이트 추가하기',
+
+ // new paint sprite button:
+ 'paint a new sprite':
+ '새로운 스프라이트 그리기',
+
+ // new paint costume button:
+ 'Paint a new costume':
+ '새로운 모양 그리기',
// tab help
'costumes tab help':
@@ -300,19 +308,19 @@ SnapTranslator.dict.ko = {
'point towards %dst':
'%dst 쪽 보기',
'go to x: %n y: %n':
- 'x: %n 、y: %n 쪽으로 가기',
+ 'x: %n 、y: %n 쪽으로 이동하기',
'go to %dst':
- '%dst 위치로 가기',
+ '%dst 위치로 이동하기',
'glide %n secs to x: %n y: %n':
- '%n 초 동안 x: %n 、y: %n 쪽으로 움직이기',
+ '%n 초 동안 x: %n 、y: %n 쪽으로 이동하기',
'change x by %n':
'x좌표 %n 만큼 바꾸기',
'set x to %n':
- 'x좌표 %n 로 정하기',
+ 'x좌표 %n (으)로 정하기',
'change y by %n':
'y좌표 %n 만큼 바꾸기',
'set y to %n':
- 'y좌표 %n 로 정하기',
+ 'y좌표 %n (으)로 정하기',
'if on edge, bounce':
'벽에 닿으면 튕기기',
'x position':
@@ -328,13 +336,13 @@ SnapTranslator.dict.ko = {
'next costume':
'다음 모양',
'costume #':
- '모양 #',
+ '모양 번호',
'say %s for %n secs':
- '%s %n 초 동안 말하기',
+ '%s 을(를) %n 초 동안 말하기',
'say %s':
'%s 말하기',
'think %s for %n secs':
- '%s %n 초간 생각하기',
+ '%s 을(를) %n 초 동안 생각하기',
'think %s':
'%s 생각하기',
'Hello!':
@@ -342,15 +350,15 @@ SnapTranslator.dict.ko = {
'Hmm...':
'흠…',
'change %eff effect by %n':
- '%eff 효과 %n 만큼 바꾸기',
+ '%eff 효과를 %n 만큼 바꾸기',
'set %eff effect to %n':
- '%eff 효과 %n 만큼 주기',
+ '%eff 효과를 %n 만큼 정하기',
'clear graphic effects':
'그래픽 효과 지우기',
'change size by %n':
- '크기 %n 만큼 바꾸기',
+ '크기를 %n 만큼 바꾸기',
'set size to %n %':
- '크기 %n % 로 정하기',
+ '크기를 %n % 로 정하기',
'size':
'크기',
'show':
@@ -373,82 +381,96 @@ SnapTranslator.dict.ko = {
'play sound %snd':
'%snd 소리내기',
'play sound %snd until done':
- '끝날때까지 %snd 소리내기',
+ '%snd 을(를) 끝까지 소리내기',
'stop all sounds':
'모든 소리 끄기',
'rest for %n beats':
- '%n 비트 동안 쉬기',
+ '%n 박자 동안 쉬기',
'play note %n for %n beats':
- '%n 음을 %n 비트로 연주하기',
+ '%n 음을 %n 박자로 연주하기',
'change tempo by %n':
- '템포를 %n 만큼 바꾸기',
+ '빠르기를 %n 만큼 바꾸기',
'set tempo to %n bpm':
- '템포를 %n bpm으로 맞추기',
+ '빠르기를 %n bpm으로 정하기',
'tempo':
- '템포',
+ '빠르기',
// pen:
'clear':
- '지우기',
+ '펜 자국 지우기',
'pen down':
'펜 내리기',
'pen up':
'펜 올리기',
'set pen color to %clr':
- '펜의 색 %clr 으로 정하기',
+ '펜 색깔을 %clr 으로 정하기',
'change pen color by %n':
- '펜의 색 %n 만큼 바꾸기',
+ '펜 색깔을 %n 만큼 바꾸기',
'set pen color to %n':
- '펜의 색 %n 으로 정하기',
+ '펜 색깔을 %n (으)로 정하기',
'change pen shade by %n':
- '펜의 그림자 %n 만큼 바꾸기',
+ '펜 음영을 %n 만큼 바꾸기',
'set pen shade to %n':
- '펜의 그림자 %n 으로 정하기',
+ '펜 음영을 %n 으로 정하기',
'change pen size by %n':
- '펜의 크기 %n 만큼 바꾸기',
+ '펜 굵기를 %n 만큼 바꾸기',
'set pen size to %n':
- '펜의 크기 %n 으로 정하기',
+ '펜 굵기를 %n (으)로 정하기',
'stamp':
- '스탬프',
+ '도장찍기',
// control:
'when %greenflag clicked':
- '%greenflag 클릭되었을 때',
+ '%greenflag 클릭했을 때',
'when %keyHat key pressed':
- '%keyHat 키 눌렀을 때',
+ '%keyHat 키를 눌렀을 때',
'when I am clicked':
- '자신이 클릭되었을 때',
+ '이 스프라이트를 클릭했을 때',
'when I receive %msgHat':
- '%msgHat 받을 때',
+ '%msgHat 을(를) 받았을 때',
'broadcast %msg':
'%msg 방송하기',
'broadcast %msg and wait':
'%msg 방송하고 기다리기',
'Message name':
- '메세지 이름',
+ '메시지 이름',
+ 'message':
+ '메시지',
+ 'any message':
+ '어떤 메시지',
'wait %n secs':
'%n 초 기다리기',
'wait until %b':
'%b 까지 기다리기',
'forever %c':
- '무한반복 %c',
+ '무한 반복하기 %c',
'repeat %n %c':
- '반복 %n 회 %c',
+ '%n 번 반복하기 %c',
'repeat until %b %c':
- '반복 %b 계속 확인 %c',
+ '%b 까지 반복하기 %c',
'if %b %c':
'만약 %b 라면 %c',
'if %b %c else %c':
'만약 %b 라면 %c 아니면 %c',
'report %s':
'%s 출력하기',
- 'stop block':
- '블록 멈추기',
- 'stop script':
- '스크립트 멈추기',
- 'stop all %stop':
- '모두 멈추기 %stop',
+ 'stop %stopChoices':
+ '%stopChoices 멈추기',
+ 'all':
+ '모두',
+ 'this script':
+ '이 스크립트',
+ 'this block':
+ '이 블록',
+ 'stop %stopOthersChoices':
+ '%stopOthersChoices 멈추기',
+ 'all but this script':
+ '이 스크립트를 제외한 모두',
+ 'other scripts in sprite':
+ '이 스프라이트에 있는 다른 스크립트',
+ 'pause all %pause':
+ '모두 잠시 멈추기 %pause',
'run %cmdRing %inputs':
'%cmdRing 을(를) %inputs 으로 실행하기',
'launch %cmdRing %inputs':
@@ -461,14 +483,23 @@ SnapTranslator.dict.ko = {
'반복해서 %cmdRing 을(를) 호출하기',
'warp %c':
'워프 %c',
+ 'when I start as a clone':
+ '복제되었을 때',
+ 'create a clone of %cln':
+ '%cln 을(를) 복제하기',
+ 'myself':
+ '나 자신',
+ 'delete this clone':
+ '이 복제본 삭제하기',
+
// sensing:
'touching %col ?':
- '%col 에 닿기?',
+ '%col 에 닿았는가?',
'touching %clr ?':
- '%clr 색에 닿기?',
+ '%clr 색에 닿았는가?',
'color %clr is touching %clr ?':
- '%clr 색이 %clr 색에 닿기?',
+ '%clr 색이 %clr 색에 닿았는가?',
'ask %s and wait':
'%s 을(를) 묻고 기다리기',
'what\'s your name?':
@@ -476,36 +507,59 @@ SnapTranslator.dict.ko = {
'answer':
'대답',
'mouse x':
- '마우스 x좌표',
+ '마우스의 x좌표',
'mouse y':
- '마우스 y좌표',
+ '마우스의 y좌표',
'mouse down?':
- '마우스 클릭하기?',
+ '마우스를 클릭했는가?',
'key %key pressed?':
- '%key 키 클릭하기?',
+ '%key 키를 눌렀는가?',
'distance to %dst':
'%dst 까지 거리',
'reset timer':
'타이머 초기화',
'timer':
'타이머',
+ '%att of %spr':
+ '%att ( %spr 에 대한)',
'http:// %s':
'http:// %s',
-
+ 'turbo mode?':
+ '터보 모드인가?',
+ 'set turbo mode to %b':
+ '터보 모드 %b 으로 설정하기',
'filtered for %clr':
'%clr 색 추출하기',
'stack size':
'스택 크기',
'frames':
'프레임',
+ 'current %dates':
+ '현재 %dates',
+ 'year':
+ '연도',
+ 'month':
+ '월',
+ 'date':
+ '일',
+ 'day of week':
+ '요일(1~7)',
+ 'hour':
+ '시간',
+ 'minute':
+ '분',
+ 'second':
+ '초',
+ 'time in milliseconds':
+ '밀리세컨드초',
// operators:
'%n mod %n':
- '%n 나누기 %n 의 나머지',
+ '( %n / %n ) 의 나머지',
'round %n':
'%n 반올림',
'%fun of %n':
- '%n 의 %fun',
+ '%fun ( %n 에 대한)',
'pick random %n to %n':
'%n 부터 %n 사이의 난수',
'%b and %b':
@@ -520,12 +574,14 @@ SnapTranslator.dict.ko = {
'거짓',
'join %words':
'%words 결합하기',
+ 'split %s by %delim':
+ '%s 를 %delim 기준으로 나누기',
'hello':
'안녕',
'world':
- '세계',
+ '세상',
'letter %n of %s':
- '%n 의 %s 번째 글자',
+ '%n 번째 글자 ( %s 에 대한)',
'length of %s':
'%s 의 길이',
'unicode of %s':
@@ -533,9 +589,11 @@ SnapTranslator.dict.ko = {
'unicode %n as letter':
'유니코드 %n 에 대한 문자',
'is %s a %typ ?':
- '%s 이(가) %typ 인가요?',
+ '%s 이(가) %typ 인가?',
+ 'is %s identical to %s ?':
+ '%s 와(과) %s 가 동일한가?',
'type of %s':
- '%s 타입',
+ '%s 의 타입',
// variables:
'Make a variable':
@@ -543,12 +601,12 @@ SnapTranslator.dict.ko = {
'Variable name':
'변수 이름',
'Delete a variable':
- '변수 삭제',
+ '변수 삭제하기',
'set %var to %s':
- '%var 을(를) %s 로 저장',
+ '변수 %var 에 %s 저장하기',
'change %var by %n':
- '%var 에 %n 씩 누적하기',
+ '변수 %var 을(를) %n 만큼 바꾸기',
'show variable %var':
'변수 %var 보이기',
'hide variable %var':
@@ -560,38 +618,78 @@ SnapTranslator.dict.ko = {
'list %exp':
'리스트 %exp',
'%s in front of %l':
- '%s 을(를) %l 처음에 추가하기 ',
+ '%s 을(를) 리스트 %l 의 맨 앞에 추가하기 ',
'item %idx of %l':
- '%idx 항목 %l',
+ '%idx 번째 항목 (리스트 %l 에 대한)',
'all but first of %l':
- '%l 첫번째 아이템 제외한 모든 아이템',
+ '리스트 %l 에서 첫 번째 항목 제외하기',
'length of %l':
- '%l 의 크기',
+ '리스트 %l 의 항목 갯수',
'%l contains %s':
- '%l 에 %s 포함?',
+ '리스트 %l 에 %s 포함되었는가?',
'thing':
- '아이템',
+ '어떤 것',
'add %s to %l':
- '%s 을(를) %l 마지막에 추가하기 ',
+ '%s 을(를) 리스트 %l 의 마지막에 추가하기 ',
'delete %ida of %l':
- '%ida 을(를) %l 에서 삭제하기 ',
+ '%ida 번째 항목 삭제하기 (리스트 %l 에 대한)',
'insert %s at %idx of %l':
- '%s 을(를) %idx 위치에 추가하기 %l',
+ '%s 을(를) %idx 위치에 추가하기 (리스트 %l 에 대한)',
'replace item %idx of %l with %s':
- '%idx 항목 %l 에 %s 로 교체하기',
+ '%idx 번째 (리스트 %l 에 대한) 를 %s (으)로 바꾸기',
// other
'Make a block':
'블록 만들기',
+ // Paint Editor
+ 'Paint Editor':
+ '그림 편집기',
+ 'undo':
+ '되돌리기',
+ 'grow':
+ '확대',
+ 'shrink':
+ '축소',
+ 'flip ↔':
+ '↔ 반전',
+ 'flip ↕':
+ '↕ 반전',
+ 'Brush size':
+ '펜 크기',
+ 'Constrain proportions of shapes?\n(you can also hold shift)':
+ '도형 크기 비율을 고정하는가?\n(shift 키를 눌러서 사용할 수 있습니다.)',
+ 'Paintbrush tool\n(free draw)':
+ '붓 도구',
+ 'Stroked Rectangle\n(shift: square)':
+ '사각형 그리기 도구\n(shift: 정사각형)',
+ 'Stroked Ellipse\n(shift: circle)':
+ '타원 그리기 도구\n(shift: 원)',
+ 'Eraser tool':
+ '지우개 도구',
+ 'Set the rotation center':
+ '회전축 설정하기',
+ 'Line tool\n(shift: vertical/horizontal)':
+ '선 그리기 도구\n(shift: 수평/수직)',
+ 'Filled Rectangle\n(shift: square)':
+ '채워진 사각형 그리기 도구\n(shift: 정사각형)',
+ 'Filled Ellipse\n(shift: circle)':
+ '채워진 타원 그리기 도구\n(shift: 원)',
+ 'Fill a region':
+ '색 채우기',
+ 'Pipette tool\n(pick a color anywhere)':
+ '스포이드 도구\n(원하는 색 선택하기)',
+
// menus
// snap menu
'About...':
'Snap! 에 대해서...',
+ 'Reference manual':
+ '참고자료 다운로드',
'Snap! website':
'Snap! 웹사이트',
'Download source':
- '소스 다운로드',
+ '소스코드 다운로드',
'Switch back to user mode':
'사용자 모드로 전환',
'disable deep-Morphic\ncontext menus\nand show user-friendly ones':
@@ -609,15 +707,18 @@ SnapTranslator.dict.ko = {
'Open...':
'열기...',
'Save':
- '저장',
+ '저장하기',
+ 'Save to disk':
+ '내 컴퓨터에 저장하기',
+ 'experimental - store this project\nin your downloads folder':
+ '실험적 - 이 프로젝트를\n 다운로드 폴더에 저장합니다.',
'Save As...':
- '다른 이름으로 저장...',
+ '다른 이름으로 저장하기...',
'Import...':
'가져오기...',
'file menu import hint':
'내보낸 프로젝트 파일, 블록 라이브러리\n'
- + '스프라이트 모양 또는 소리를 가져옵니다.\n\n'
- + '일부 웹브라우저에서는 지원되지 않습니다.',
+ + '스프라이트 모양 또는 소리를 가져옵니다.',
'Export project as plain text...':
'프로젝트를 텍스트 파일로 내보내기...',
'Export project...':
@@ -628,58 +729,171 @@ SnapTranslator.dict.ko = {
'블록 내보내기...',
'show global custom block definitions as XML\nin a new browser window':
'새롭게 정의한 전역 블록 데이터를\n새로운 윈도우에 XML 형태로 보여주기',
+ 'Export all scripts as pic...':
+ '모든 스크립트를 그림파일로 내보내기',
+ 'show a picture of all scripts\nand block definitions':
+ '모든 스크립트와 정의된 블록을 그림파일로 보여줍니다.',
+ 'Import tools':
+ '추가 도구 가져오기',
+ 'load the official library of\npowerful blocks':
+ '강력한 기능을 제공하는\n 블록들을 가져옵니다.',
+ 'Libraries...':
+ '라이브러리...',
+ 'Select categories of additional blocks to add to this project.':
+ '추가적인 블록을 선택해서\n 사용할 수 있습니다.',
+ 'Import library':
+ '라이브러리 가져오기',
+
+ // cloud menu
+ 'Login...':
+ '로그인...',
+ 'Signup...':
+ '계정만들기...',
+ 'Reset Password...':
+ '비밀번호 재설정...',
+ 'url...':
+ 'url...',
+ 'export project media only...':
+ 'export project media only...',
+ 'export project without media...':
+ 'export project without media...',
+ 'export project as cloud data...':
+ 'export project as cloud data...',
+ 'open shared project from cloud...':
+ 'open shared project from cloud...',
// settings menu
'Language...':
'언어선택...',
+ 'Zoom blocks...':
+ '블록 크기 설정...',
+ 'Stage size...':
+ '무대 크기 설정...',
+ 'Stage size':
+ '무대 크기',
+ 'Stage width':
+ '가로(너비)',
+ 'Stage height':
+ '세로(높이)',
+ 'Default':
+ '기본설정',
+
'Blurred shadows':
'반투명 그림자',
'uncheck to use solid drop\nshadows and highlights':
'체크해제하면, 그림자와 하이라이트가\n불투명 상태로 됩니다.',
'check to use blurred drop\nshadows and highlights':
'체크하면, 그림자와 하이라이트가\n반투명 상태로 됩니다.',
+
'Zebra coloring':
'중첩 블록 구분하기',
'check to enable alternating\ncolors for nested blocks':
'체크하면, 중첩된 블록을\n다른 색으로 구분할 수 있습니다.',
'uncheck to disable alternating\ncolors for nested block':
'체크해제하면, 중첩된 블록을\n다른 색으로 구분할 수 없습니다.',
+
+ 'Dynamic input labels':
+ 'Dynamic input labels',
+ 'uncheck to disable dynamic\nlabels for variadic inputs':
+ 'uncheck to disable dynamic\nlabels for variadic inputs',
+ 'check to enable dynamic\nlabels for variadic inputs':
+ 'check to enable dynamic\nlabels for variadic inputs',
+
'Prefer empty slot drops':
'빈 슬롯에 입력 가능',
'settings menu prefer empty slots hint':
'설정 메뉴에 빈 슬롯의\n힌트를 사용할 수 있습니다.',
'uncheck to allow dropped\nreporters to kick out others':
'체크해제하면, 기존 리포터 블록에\n새로운 리포터 블록으로 대체할 수 있습니다.',
+
'Long form input dialog':
'긴 형태의 입력 대화창',
'check to always show slot\ntypes in the input dialog':
'체크하면, 입력 대화창에\n항상 슬롯의 형태를 보여줍니다.',
'uncheck to use the input\ndialog in short form':
'체크해제하면, 입력 대화창을\n짧은 형태로 사용합니다.',
+
+ 'Plain prototype labels':
+ '새로 만든 블록 인수 설정',
+ 'uncheck to always show (+) symbols\nin block prototype labels':
+ '체크해제하면, 블록 편집기에서\n 블록 인수 추가 버튼(+)을\n 보입니다.',
+ 'check to hide (+) symbols\nin block prototype labels':
+ '체크하면, 블록 편집기에서\n 블록 인수 추가 버튼(+)을\n 숨깁니다.',
+
'Virtual keyboard':
'가상 키보드',
'uncheck to disable\nvirtual keyboard support\nfor mobile devices':
'체크해제하면, 모바일 기기에서\n가상 키보드를 사용할 수 없습니다.',
'check to enable\nvirtual keyboard support\nfor mobile devices':
'체크하면, 모바일 기기에서\n가상 키보드를 사용할 수 있습니다.',
+
'Input sliders':
'입력창에서 슬라이더 사용',
'uncheck to disable\ninput sliders for\nentry fields':
'체크해제하면, 입력창에서\n슬라이더를 사용할 수 없습니다.',
'check to enable\ninput sliders for\nentry fields':
'체크하면, 입력창에서\n슬라이더를 사용할 수 있습니다.',
+
'Clicking sound':
'블록 클릭시 소리',
'uncheck to turn\nblock clicking\nsound off':
'체크해제하면, 블록 클릭시\n소리가 꺼집니다.',
'check to turn\nblock clicking\nsound on':
'체크하면, 블록 클릭시\n소리가 켜집니다.',
+
+ 'Animations':
+ '애니메이션',
+ 'uncheck to disable\nIDE animations':
+ '체크해제하면, IDE 애니메이션을\n 사용할 수 없습니다.',
+
+ 'Turbo mode':
+ '터보 모드',
+ 'check to prioritize\nscript execution':
+ '체크하면, 스크립트를\n 빠르게 실행합니다.',
+ 'uncheck to run scripts\nat normal speed':
+ '체크해제하면, 스크립트 실행 속도를\n 보통으로 합니다.',
+
+ 'Flat design':
+ '플랫(Flat) 디자인',
+ 'uncheck for default\nGUI design':
+ '체크해제하면,\n 기본 GUI 디자인으로\n 변경합니다.',
+ 'check for alternative\nGUI design':
+ '체크하면, 플랫(Flat)\n 디자인으로 변경합니다.',
+
+ 'Sprite Nesting':
+ 'Sprite Nesting',
+ 'uncheck to disable\nsprite composition':
+ 'uncheck to disable\nsprite composition',
+ 'check to enable\nsprite composition':
+ 'check to enable\nsprite composition',
+
'Thread safe scripts':
'스레드 안전 스크립트',
- 'uncheck to allow\nscript reentrancy':
+ 'uncheck to allow\nscript reentrance':
'체크해제하면, 스크립트\n재진입성을 허락합니다.',
- 'check to disallow\nscript reentrancy':
+ 'check to disallow\nscript reentrance':
'체크하면, 스크립트\n재진입성을 허락하지 않습니다.',
+
+ 'Prefer smooth animations':
+ '자연스러운 애니메이션',
+ 'uncheck for greater speed\nat variable frame rates':
+ '체크해제하면, 프레임\n 전환 비율이 빨라집니다.',
+ 'check for smooth, predictable\nanimations across computers':
+ '체크하면, 애니메이션이\n 자연스러워 집니다.',
+
+ 'Flat line ends':
+ '선 끝을 평평하게 만들기',
+ 'check for flat ends of lines':
+ '체크하면, 선 끝을\n 평평하게 만듭니다.',
+ 'uncheck for round ends of lines':
+ '체크해제하면, 선 끝을\n 둥글게 만듭니다.',
+
+ 'Codification support':
+ '체계화 지원',
+ 'uncheck to disable\nblock to text mapping features':
+ 'uncheck to disable\nblock to text mapping features',
+ 'check for block\nto text mapping features':
+ '체크하면, check for block\nto text mapping features',
// inputs
'with inputs':
@@ -692,35 +906,55 @@ SnapTranslator.dict.ko = {
// context menus:
'help':
'도움말',
+
+ // palette:
+ 'find blocks...':
+ '블록 찾기...',
+ 'hide primitives':
+ '기본 블록 숨기기',
+ 'show primitives':
+ '기본 블록 보이기',
// blocks:
'help...':
'블록 도움말...',
+ 'relabel...':
+ '블록 바꾸기...',
'duplicate':
- '복사',
+ '복사하기',
'make a copy\nand pick it up':
- '복사해서\n그 블록을 들고 있습니다.',
+ '복사해서\n들고 있습니다.',
'delete':
- '삭제',
+ '삭제하기',
'script pic...':
- '스크립트 그림...',
+ '이 스크립트를 그림파일로 내보내기...',
'open a new window\nwith a picture of this script':
'이 스크립트 그림을\n새로운 윈도우에서 엽니다.',
'ringify':
- '형태변환',
+ '블록형태 변환하기',
+ 'unringify':
+ 'unringify',
// custom blocks:
'delete block definition...':
- '블록 삭제',
+ '블록 삭제하기',
'edit...':
'편집…',
// sprites:
'edit':
- '편집',
+ '스크립트 편집하기',
'export...':
'내보내기...',
+ // stage:
+ 'show all':
+ '모든 스프라이트 나타내기',
+ 'pic...':
+ '그림파일로 내보내기...',
+ 'open a new window\nwith a picture of the stage':
+ '새로운 창을 열고\n무대의 화면을\n그림파일로 저장한다.',
+
// scripting area
'clean up':
'스크립트 정리하기',
@@ -728,12 +962,20 @@ SnapTranslator.dict.ko = {
'스크립트를\n수직으로 정렬한다.',
'add comment':
'주석 추가하기',
+ 'undrop':
+ '마지막으로 가져온 블록',
+ 'undo the last\nblock drop\nin this pane':
+ '마지막으로\n 가져온 블록을\n 확인한다.',
+ 'scripts pic...':
+ '모든 스크립트를 그림파일로 내보내기...',
+ 'open a new window\nwith a picture of all scripts':
+ '새로운 창을 열어서\n 모든 스크립트를\n 그림으로 저장한다.',
'make a block...':
'블록 만들기...',
// costumes
'rename':
- '이름수정',
+ '이름 수정하기',
'export':
'내보내기',
@@ -750,7 +992,9 @@ SnapTranslator.dict.ko = {
// dialogs
// buttons
'OK':
- 'OK',
+ '확인',
+ 'Ok':
+ '확인',
'Cancel':
'취소',
'Yes':
@@ -762,6 +1006,31 @@ SnapTranslator.dict.ko = {
'Help':
'도움말',
+ // zoom blocks
+ 'Zoom blocks':
+ '블록 크기 설정',
+ 'build':
+ '만들기',
+ 'your own':
+ '나만의',
+ 'blocks':
+ '블록',
+ 'normal (1x)':
+ '기본 크기 (1x)',
+ 'demo (1.2x)':
+ '데모 크기 (1.2x)',
+ 'presentation (1.4x)':
+ '발표용 크기 (1.4x)',
+ 'big (2x)':
+ '큰 크기 (2x)',
+ 'huge (4x)':
+ '매우 큰 크기(4x)',
+ 'giant (8x)':
+ '정말 큰 크기 (8x)',
+ 'monstrous (10x)':
+ '믿을 수 없는 크기 (10x)',
+
+
// costume editor
'Costume Editor':
'모양 편집기',
@@ -822,7 +1091,7 @@ SnapTranslator.dict.ko = {
// block deletion dialog
'Delete Custom Block':
- '블록 삭제',
+ '블록 삭제하기',
'block deletion dialog text':
'이 블록과 모든 인스턴스를\n 삭제해도 괜찮습니까?',
@@ -911,6 +1180,8 @@ SnapTranslator.dict.ko = {
// coments
'add comment here...':
'여기에 주석 추가…',
+ 'comment pic...':
+ '주석을 그림파일로 내보내기...',
// drow downs
// directions
@@ -933,11 +1204,22 @@ SnapTranslator.dict.ko = {
// costumes
'Turtle':
- '터틀',
+ '화살표',
+ 'Empty':
+ 'Leer',
- // graphical effects
+ // graphical effects
+ 'brightness':
+ '밝기',
'ghost':
'유령',
+ 'negative':
+ '반전',
+ 'comic':
+ '코믹',
+ 'confetti':
+ '색종이',
+
// keys
'space':
@@ -1029,9 +1311,11 @@ SnapTranslator.dict.ko = {
// math functions
'abs':
- '절대값',
+ '절대값(abs)',
'sqrt':
- '제곱근',
+ '제곱근(sqrt)',
+ 'floor':
+ '바닥(floor)',
'sin':
'sin',
'cos':
@@ -1049,11 +1333,23 @@ SnapTranslator.dict.ko = {
'e^':
'e^',
+ // delimiters
+ 'letter':
+ '글자',
+ 'whitespace':
+ '빈칸',
+ 'line':
+ '줄',
+ 'tab':
+ '탭',
+ 'cr':
+ '새줄(cr)',
+
// data types
'number':
'숫자',
'text':
- '텍스트',
+ '문자',
'Boolean':
'불리언',
'list':
@@ -1087,12 +1383,12 @@ SnapTranslator.dict.ko = {
'Are you sure you want to delete':
'정말로 삭제합니까?',
'unringify':
- '형태변환취소',
+ '블록형태변환 취소하기',
'rename...':
'이름수정...',
'(180) down':
'(180) 아래',
'Ok':
- 'OK'
+ '확인'
};
diff --git a/objects.js b/objects.js
index 140ac69..95581c5 100644
--- a/objects.js
+++ b/objects.js
@@ -464,6 +464,23 @@ SpriteMorph.prototype.initBlocks = function () {
category: 'sound',
spec: 'stop all sounds'
},
+ doSetVolume: {
+ type: 'command',
+ category: 'sound',
+ spec: 'set volume to %n %',
+ defaults: [100]
+ },
+ doChangeVolume: {
+ type: 'command',
+ category: 'sound',
+ spec: 'change volume by %n',
+ defaults: [-10]
+ },
+ reportVolume: {
+ type: 'reporter',
+ category: 'sound',
+ spec: 'volume'
+ },
doRest: {
type: 'command',
category: 'sound',
@@ -868,6 +885,11 @@ SpriteMorph.prototype.initBlocks = function () {
category: 'sensing',
spec: 'key %key pressed?'
},
+ getKeysPressed: {
+ type: 'reporter',
+ category: 'sensing',
+ spec: 'keys pressed'
+ },
reportDistanceTo: {
type: 'reporter',
category: 'sensing',
@@ -1376,6 +1398,8 @@ SpriteMorph.prototype.init = function (globals) {
this.version = Date.now(); // for observer optimization
this.isClone = false; // indicate a "temporary" Scratch-style clone
this.cloneOriginName = '';
+ this.volume = 100;
+ this.activeSounds = [];
// sprite nesting properties
this.parts = []; // not serialized, only anchor (name)
@@ -1478,8 +1502,11 @@ SpriteMorph.prototype.appearIn = function (ide) {
// SpriteMorph versioning
SpriteMorph.prototype.setName = function (string) {
- this.name = string || this.name;
- this.version = Date.now();
+ if (string != 'mouse-pointer' && string != 'pen trails'
+ && string != 'edge') { // used by system
+ this.name = string || this.name;
+ this.version = Date.now();
+ }
};
// SpriteMorph rendering
@@ -1851,6 +1878,11 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push(block('doPlaySoundUntilDone'));
blocks.push(block('doStopAllSounds'));
blocks.push('-');
+ blocks.push(block('doSetVolume'));
+ blocks.push(block('doChangeVolume'));
+ blocks.push(watcherToggle('reportVolume'));
+ blocks.push(block('reportVolume'));
+ blocks.push('-');
blocks.push(block('doRest'));
blocks.push('-');
blocks.push(block('doPlayNote'));
@@ -1969,6 +2001,7 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push(block('reportMouseDown'));
blocks.push('-');
blocks.push(block('reportKeyPressed'));
+ blocks.push(block('getKeysPressed'));
blocks.push('-');
blocks.push(block('reportDistanceTo'));
blocks.push('-');
@@ -2687,7 +2720,8 @@ SpriteMorph.prototype.reportCostumes = function () {
// SpriteMorph sound management
SpriteMorph.prototype.addSound = function (audio, name) {
- this.sounds.add(new Sound(audio, name));
+ var volume = this.volume;
+ this.sounds.add(new Sound(audio, name, volume));
};
SpriteMorph.prototype.playSound = function (name) {
@@ -2698,7 +2732,18 @@ SpriteMorph.prototype.playSound = function (name) {
),
active;
if (sound) {
+ sound.volume = this.volume;
active = sound.play();
+
+ if (stage.muted === true) {
+ active.volume = 0;
+ }
+
+ this.activeSounds.push(active);
+ this.activeSounds = this.activeSounds.filter(function (aud) {
+ return !aud.ended && !aud.terminated;
+ });
+
if (stage) {
stage.activeSounds.push(active);
stage.activeSounds = stage.activeSounds.filter(function (aud) {
@@ -2709,6 +2754,34 @@ SpriteMorph.prototype.playSound = function (name) {
}
};
+SpriteMorph.prototype.doSetVolume = function (val) {
+ var myself = this;
+ myself.volume = Math.min(Math.max(0, val), 100);
+
+ if (myself.parentThatIsA(StageMorph).muted === true) {
+ return;
+ }
+
+ myself.activeSounds.forEach(function (snd) {
+ snd.volume = myself.volume / 100; // 'audio' objects
+ });
+};
+
+SpriteMorph.prototype.doChangeVolume = function (val) {
+ this.doSetVolume(this.volume + val);
+};
+
+SpriteMorph.prototype.reportVolume = function () {
+ return this.volume;
+}
+
+SpriteMorph.prototype.unmuteAllSounds = function () {
+ var stage = this.parentThatIsA(StageMorph);
+ stage.muted = false;
+
+ this.doSetVolume(this.volume);
+};
+
SpriteMorph.prototype.reportSounds = function () {
return this.sounds;
};
@@ -3358,9 +3431,7 @@ Morph.prototype.setPosition = function (aPoint, justMe) {
// override the inherited default to make sure my parts follow
// unless it's justMe
var delta = aPoint.subtract(this.topLeft());
- if ((delta.x !== 0) || (delta.y !== 0)) {
- this.moveBy(delta, justMe);
- }
+ this.moveBy(delta, justMe);
};
SpriteMorph.prototype.forward = function (steps) {
@@ -3593,7 +3664,19 @@ SpriteMorph.prototype.allHatBlocksForKey = function (key) {
return this.scripts.children.filter(function (morph) {
if (morph.selector) {
if (morph.selector === 'receiveKey') {
- return morph.inputs()[0].evaluate()[0] === key;
+ var selectedOption = morph.inputs()[0].evaluate()[0];
+
+ if (selectedOption === 'any key') {
+ return true;
+ }
+ if (selectedOption === 'number key' &&
+ (key >= '0' && key <= '9')) {
+ return true;
+ }
+ if (selectedOption === key) {
+ return true;
+ }
+ return false;
}
}
return false;
@@ -3637,6 +3720,16 @@ SpriteMorph.prototype.getTempo = function () {
return 0;
};
+// SpriteMorph last key
+
+SpriteMorph.prototype.getKeysPressed = function () {
+ var stage = this.parentThatIsA(StageMorph);
+ if (stage) {
+ return stage.getKeysPressed();
+ }
+ return '';
+};
+
// SpriteMorph last message
SpriteMorph.prototype.getLastMessage = function () {
@@ -4364,6 +4457,8 @@ StageMorph.prototype.init = function (globals) {
this.version = Date.now(); // for observers
this.isFastTracked = false;
this.cloneCount = 0;
+ this.volume = 100;
+ this.muted = false;
this.timerStart = Date.now();
this.tempo = 60; // bpm
@@ -4652,6 +4747,16 @@ StageMorph.prototype.getTempo = function () {
return +this.tempo;
};
+// StageMorph keys
+
+StageMorph.prototype.getKeysPressed = function () {
+ var keys = [];
+ for (var key in this.keysPressed) {
+ keys.push(key);
+ }
+ return new List(keys);
+};
+
// StageMorph messages
StageMorph.prototype.getLastMessage = function () {
@@ -4705,7 +4810,7 @@ StageMorph.prototype.step = function () {
world.keyboardReceiver = this;
}
if (world.currentKey === null) {
- this.keyPressed = null;
+ this.keysPressed = {};
}
// manage threads
@@ -4893,7 +4998,7 @@ StageMorph.prototype.fireGreenFlagEvent = function () {
StageMorph.prototype.fireStopAllEvent = function () {
var ide = this.parentThatIsA(IDE_Morph);
- this.threads.resumeAll(this.stage);
+ //this.threads.resumeAll(this.stage); // leads to a strange Note bug
this.keysPressed = {};
this.threads.stopAll();
this.stopAllActiveSounds();
@@ -5049,6 +5154,11 @@ StageMorph.prototype.blockTemplates = function (category) {
blocks.push(block('doPlaySoundUntilDone'));
blocks.push(block('doStopAllSounds'));
blocks.push('-');
+ blocks.push(block('doSetVolume'));
+ blocks.push(block('doChangeVolume'));
+ blocks.push(watcherToggle('reportVolume'));
+ blocks.push(block('reportVolume'));
+ blocks.push('-');
blocks.push(block('doRest'));
blocks.push('-');
blocks.push(block('doPlayNote'));
@@ -5147,6 +5257,7 @@ StageMorph.prototype.blockTemplates = function (category) {
blocks.push(block('reportMouseDown'));
blocks.push('-');
blocks.push(block('reportKeyPressed'));
+ blocks.push(block('getKeysPressed'));
blocks.push('-');
blocks.push(block('doResetTimer'));
blocks.push(watcherToggle('getTimer'));
@@ -5600,6 +5711,15 @@ StageMorph.prototype.addSound
StageMorph.prototype.playSound
= SpriteMorph.prototype.playSound;
+StageMorph.prototype.doSetVolume
+ = SpriteMorph.prototype.doSetVolume;
+
+StageMorph.prototype.doChangeVolume
+ = SpriteMorph.prototype.doChangeVolume;
+
+StageMorph.prototype.reportVolume
+ = SpriteMorph.prototype.reportVolume;
+
StageMorph.prototype.stopAllActiveSounds = function () {
this.activeSounds.forEach(function (audio) {
audio.pause();
@@ -5614,11 +5734,29 @@ StageMorph.prototype.pauseAllActiveSounds = function () {
};
StageMorph.prototype.resumeAllActiveSounds = function () {
+ var newSounds = []; // remove Sounds that have been played so they do not resume
+
this.activeSounds.forEach(function (audio) {
- audio.play();
+ if (audio.ended === false) {
+ newSounds.push(audio);
+ audio.play();
+ }
});
+
+ this.activeSounds = newSounds;
};
+StageMorph.prototype.muteAllSounds = function () {
+ this.muted = true;
+
+ this.activeSounds.forEach(function (audio) {
+ audio.volume = 0;
+ });
+};
+
+StageMorph.prototype.unmuteAllSounds
+ = SpriteMorph.prototype.unmuteAllSounds;
+
StageMorph.prototype.reportSounds
= SpriteMorph.prototype.reportSounds;
@@ -6372,9 +6510,10 @@ CostumeEditorMorph.prototype.mouseMove
// Sound instance creation
-function Sound(audio, name) {
+function Sound(audio, name, volume) {
this.audio = audio; // mandatory
this.name = name || "Sound";
+ this.volume = volume || 100;
}
Sound.prototype.play = function () {
@@ -6382,6 +6521,7 @@ Sound.prototype.play = function () {
// externally (i.e. by the stage)
var aud = document.createElement('audio');
aud.src = this.audio.src;
+ aud.volume = Math.min(Math.max(0, this.volume), 100) / 100;
aud.play();
return aud;
};
@@ -6391,7 +6531,8 @@ Sound.prototype.copy = function () {
cpy;
snd.src = this.audio.src;
- cpy = new Sound(snd, this.name ? copy(this.name) : null);
+ snd.volume = this.volume;
+ cpy = new Sound(snd, this.name ? copy(this.name) : null, this.volume ? copy(this.volume) : null);
return cpy;
};
@@ -6405,8 +6546,9 @@ Sound.prototype.toDataURL = function () {
// Note instance creation
-function Note(pitch) {
+function Note(pitch, volume) {
this.pitch = pitch === 0 ? 0 : pitch || 69;
+ this.volume = volume;
this.setupContext();
this.oscillator = null;
}
@@ -6437,12 +6579,13 @@ Note.prototype.setupContext = function () {
}
Note.prototype.audioContext = new AudioContext();
Note.prototype.gainNode = Note.prototype.audioContext.createGain();
- Note.prototype.gainNode.gain.value = 0.25; // reduce volume by 1/4
};
// Note playing
Note.prototype.play = function () {
+ this.gainNode.gain.value = 0.25 * this.volume / 100; // reduce volume by 1/4
+
this.oscillator = this.audioContext.createOscillator();
if (!this.oscillator.start) {
this.oscillator.start = this.oscillator.noteOn;
@@ -6458,6 +6601,12 @@ Note.prototype.play = function () {
this.oscillator.start(0);
};
+Note.prototype.setVolume = function (volume) {
+ this.stop();
+ this.volume = volume;
+ this.play();
+}
+
Note.prototype.stop = function () {
if (this.oscillator) {
this.oscillator.stop(0);
@@ -6886,7 +7035,7 @@ WatcherMorph.prototype.object = function () {
WatcherMorph.prototype.isGlobal = function (selector) {
return contains(
- ['getLastAnswer', 'getLastMessage', 'getTempo', 'getTimer',
+ ['getLastAnswer', 'getKeysPressed', 'getLastMessage', 'getTempo', 'getTimer',
'reportMouseX', 'reportMouseY', 'reportThreadCount'],
selector
);
diff --git a/octokit.js b/octokit.js
new file mode 160000
+Subproject 579cef9893626da93ab8afbd601e4a3cf0d6d27
diff --git a/promise-1.0.0.js b/promise-1.0.0.js
new file mode 100644
index 0000000..5619cfa
--- /dev/null
+++ b/promise-1.0.0.js
@@ -0,0 +1,684 @@
+(function() {
+var define, requireModule, require, requirejs;
+
+(function() {
+ var registry = {}, seen = {};
+
+ define = function(name, deps, callback) {
+ registry[name] = { deps: deps, callback: callback };
+ };
+
+ requirejs = require = requireModule = function(name) {
+ requirejs._eak_seen = registry;
+
+ if (seen[name]) { return seen[name]; }
+ seen[name] = {};
+
+ if (!registry[name]) {
+ throw new Error("Could not find module " + name);
+ }
+
+ var mod = registry[name],
+ deps = mod.deps,
+ callback = mod.callback,
+ reified = [],
+ exports;
+
+ for (var i=0, l=deps.length; i<l; i++) {
+ if (deps[i] === 'exports') {
+ reified.push(exports = {});
+ } else {
+ reified.push(requireModule(resolve(deps[i])));
+ }
+ }
+
+ var value = callback.apply(this, reified);
+ return seen[name] = exports || value;
+
+ function resolve(child) {
+ if (child.charAt(0) !== '.') { return child; }
+ var parts = child.split("/");
+ var parentBase = name.split("/").slice(0, -1);
+
+ for (var i=0, l=parts.length; i<l; i++) {
+ var part = parts[i];
+
+ if (part === '..') { parentBase.pop(); }
+ else if (part === '.') { continue; }
+ else { parentBase.push(part); }
+ }
+
+ return parentBase.join("/");
+ }
+ };
+})();
+
+define("promise/all",
+ ["./utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ /* global toString */
+
+ var isArray = __dependency1__.isArray;
+ var isFunction = __dependency1__.isFunction;
+
+ /**
+ Returns a promise that is fulfilled when all the given promises have been
+ fulfilled, or rejected if any of them become rejected. The return promise
+ is fulfilled with an array that gives all the values in the order they were
+ passed in the `promises` array argument.
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.resolve(2);
+ var promise3 = RSVP.resolve(3);
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.all(promises).then(function(array){
+ // The array here would be [ 1, 2, 3 ];
+ });
+ ```
+
+ If any of the `promises` given to `RSVP.all` are rejected, the first promise
+ that is rejected will be given as an argument to the returned promises's
+ rejection handler. For example:
+
+ Example:
+
+ ```javascript
+ var promise1 = RSVP.resolve(1);
+ var promise2 = RSVP.reject(new Error("2"));
+ var promise3 = RSVP.reject(new Error("3"));
+ var promises = [ promise1, promise2, promise3 ];
+
+ RSVP.all(promises).then(function(array){
+ // Code here never runs because there are rejected promises!
+ }, function(error) {
+ // error.message === "2"
+ });
+ ```
+
+ @method all
+ @for RSVP
+ @param {Array} promises
+ @param {String} label
+ @return {Promise} promise that is fulfilled when all `promises` have been
+ fulfilled, or rejected if any of them become rejected.
+ */
+ function all(promises) {
+ /*jshint validthis:true */
+ var Promise = this;
+
+ if (!isArray(promises)) {
+ throw new TypeError('You must pass an array to all.');
+ }
+
+ return new Promise(function(resolve, reject) {
+ var results = [], remaining = promises.length,
+ promise;
+
+ if (remaining === 0) {
+ resolve([]);
+ }
+
+ function resolver(index) {
+ return function(value) {
+ resolveAll(index, value);
+ };
+ }
+
+ function resolveAll(index, value) {
+ results[index] = value;
+ if (--remaining === 0) {
+ resolve(results);
+ }
+ }
+
+ for (var i = 0; i < promises.length; i++) {
+ promise = promises[i];
+
+ if (promise && isFunction(promise.then)) {
+ promise.then(resolver(i), reject);
+ } else {
+ resolveAll(i, promise);
+ }
+ }
+ });
+ }
+
+ __exports__.all = all;
+ });
+define("promise/asap",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ var browserGlobal = (typeof window !== 'undefined') ? window : {};
+ var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+ var local = (typeof global !== 'undefined') ? global : (this === undefined? window:this);
+
+ // node
+ function useNextTick() {
+ return function() {
+ process.nextTick(flush);
+ };
+ }
+
+ function useMutationObserver() {
+ var iterations = 0;
+ var observer = new BrowserMutationObserver(flush);
+ var node = document.createTextNode('');
+ observer.observe(node, { characterData: true });
+
+ return function() {
+ node.data = (iterations = ++iterations % 2);
+ };
+ }
+
+ function useSetTimeout() {
+ return function() {
+ local.setTimeout(flush, 1);
+ };
+ }
+
+ var queue = [];
+ function flush() {
+ for (var i = 0; i < queue.length; i++) {
+ var tuple = queue[i];
+ var callback = tuple[0], arg = tuple[1];
+ callback(arg);
+ }
+ queue = [];
+ }
+
+ var scheduleFlush;
+
+ // Decide what async method to use to triggering processing of queued callbacks:
+ if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') {
+ scheduleFlush = useNextTick();
+ } else if (BrowserMutationObserver) {
+ scheduleFlush = useMutationObserver();
+ } else {
+ scheduleFlush = useSetTimeout();
+ }
+
+ function asap(callback, arg) {
+ var length = queue.push([callback, arg]);
+ if (length === 1) {
+ // If length is 1, that means that we need to schedule an async flush.
+ // If additional callbacks are queued before the queue is flushed, they
+ // will be processed by this flush that we are scheduling.
+ scheduleFlush();
+ }
+ }
+
+ __exports__.asap = asap;
+ });
+define("promise/config",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ var config = {
+ instrument: false
+ };
+
+ function configure(name, value) {
+ if (arguments.length === 2) {
+ config[name] = value;
+ } else {
+ return config[name];
+ }
+ }
+
+ __exports__.config = config;
+ __exports__.configure = configure;
+ });
+define("promise/polyfill",
+ ["./promise","./utils","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ /*global self*/
+ var RSVPPromise = __dependency1__.Promise;
+ var isFunction = __dependency2__.isFunction;
+
+ function polyfill() {
+ var local;
+
+ if (typeof global !== 'undefined') {
+ local = global;
+ } else if (typeof window !== 'undefined' && window.document) {
+ local = window;
+ } else {
+ local = self;
+ }
+
+ var es6PromiseSupport =
+ "Promise" in local &&
+ // Some of these methods are missing from
+ // Firefox/Chrome experimental implementations
+ "resolve" in local.Promise &&
+ "reject" in local.Promise &&
+ "all" in local.Promise &&
+ "race" in local.Promise &&
+ // Older version of the spec had a resolver object
+ // as the arg rather than a function
+ (function() {
+ var resolve;
+ new local.Promise(function(r) { resolve = r; });
+ return isFunction(resolve);
+ }());
+
+ if (!es6PromiseSupport) {
+ local.Promise = RSVPPromise;
+ }
+ }
+
+ __exports__.polyfill = polyfill;
+ });
+define("promise/promise",
+ ["./config","./utils","./all","./race","./resolve","./reject","./asap","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
+ "use strict";
+ var config = __dependency1__.config;
+ var configure = __dependency1__.configure;
+ var objectOrFunction = __dependency2__.objectOrFunction;
+ var isFunction = __dependency2__.isFunction;
+ var now = __dependency2__.now;
+ var all = __dependency3__.all;
+ var race = __dependency4__.race;
+ var staticResolve = __dependency5__.resolve;
+ var staticReject = __dependency6__.reject;
+ var asap = __dependency7__.asap;
+
+ var counter = 0;
+
+ config.async = asap; // default async is asap;
+
+ function Promise(resolver) {
+ if (!isFunction(resolver)) {
+ throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+ }
+
+ if (!(this instanceof Promise)) {
+ throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+ }
+
+ this._subscribers = [];
+
+ invokeResolver(resolver, this);
+ }
+
+ function invokeResolver(resolver, promise) {
+ function resolvePromise(value) {
+ resolve(promise, value);
+ }
+
+ function rejectPromise(reason) {
+ reject(promise, reason);
+ }
+
+ try {
+ resolver(resolvePromise, rejectPromise);
+ } catch(e) {
+ rejectPromise(e);
+ }
+ }
+
+ function invokeCallback(settled, promise, callback, detail) {
+ var hasCallback = isFunction(callback),
+ value, error, succeeded, failed;
+
+ if (hasCallback) {
+ try {
+ value = callback(detail);
+ succeeded = true;
+ } catch(e) {
+ failed = true;
+ error = e;
+ }
+ } else {
+ value = detail;
+ succeeded = true;
+ }
+
+ if (handleThenable(promise, value)) {
+ return;
+ } else if (hasCallback && succeeded) {
+ resolve(promise, value);
+ } else if (failed) {
+ reject(promise, error);
+ } else if (settled === FULFILLED) {
+ resolve(promise, value);
+ } else if (settled === REJECTED) {
+ reject(promise, value);
+ }
+ }
+
+ var PENDING = void 0;
+ var SEALED = 0;
+ var FULFILLED = 1;
+ var REJECTED = 2;
+
+ function subscribe(parent, child, onFulfillment, onRejection) {
+ var subscribers = parent._subscribers;
+ var length = subscribers.length;
+
+ subscribers[length] = child;
+ subscribers[length + FULFILLED] = onFulfillment;
+ subscribers[length + REJECTED] = onRejection;
+ }
+
+ function publish(promise, settled) {
+ var child, callback, subscribers = promise._subscribers, detail = promise._detail;
+
+ for (var i = 0; i < subscribers.length; i += 3) {
+ child = subscribers[i];
+ callback = subscribers[i + settled];
+
+ invokeCallback(settled, child, callback, detail);
+ }
+
+ promise._subscribers = null;
+ }
+
+ Promise.prototype = {
+ constructor: Promise,
+
+ _state: undefined,
+ _detail: undefined,
+ _subscribers: undefined,
+
+ then: function(onFulfillment, onRejection) {
+ var promise = this;
+
+ var thenPromise = new this.constructor(function() {});
+
+ if (this._state) {
+ var callbacks = arguments;
+ config.async(function invokePromiseCallback() {
+ invokeCallback(promise._state, thenPromise, callbacks[promise._state - 1], promise._detail);
+ });
+ } else {
+ subscribe(this, thenPromise, onFulfillment, onRejection);
+ }
+
+ return thenPromise;
+ },
+
+ 'catch': function(onRejection) {
+ return this.then(null, onRejection);
+ }
+ };
+
+ Promise.all = all;
+ Promise.race = race;
+ Promise.resolve = staticResolve;
+ Promise.reject = staticReject;
+
+ function handleThenable(promise, value) {
+ var then = null,
+ resolved;
+
+ try {
+ if (promise === value) {
+ throw new TypeError("A promises callback cannot return that same promise.");
+ }
+
+ if (objectOrFunction(value)) {
+ then = value.then;
+
+ if (isFunction(then)) {
+ then.call(value, function(val) {
+ if (resolved) { return true; }
+ resolved = true;
+
+ if (value !== val) {
+ resolve(promise, val);
+ } else {
+ fulfill(promise, val);
+ }
+ }, function(val) {
+ if (resolved) { return true; }
+ resolved = true;
+
+ reject(promise, val);
+ });
+
+ return true;
+ }
+ }
+ } catch (error) {
+ if (resolved) { return true; }
+ reject(promise, error);
+ return true;
+ }
+
+ return false;
+ }
+
+ function resolve(promise, value) {
+ if (promise === value) {
+ fulfill(promise, value);
+ } else if (!handleThenable(promise, value)) {
+ fulfill(promise, value);
+ }
+ }
+
+ function fulfill(promise, value) {
+ if (promise._state !== PENDING) { return; }
+ promise._state = SEALED;
+ promise._detail = value;
+
+ config.async(publishFulfillment, promise);
+ }
+
+ function reject(promise, reason) {
+ if (promise._state !== PENDING) { return; }
+ promise._state = SEALED;
+ promise._detail = reason;
+
+ config.async(publishRejection, promise);
+ }
+
+ function publishFulfillment(promise) {
+ publish(promise, promise._state = FULFILLED);
+ }
+
+ function publishRejection(promise) {
+ publish(promise, promise._state = REJECTED);
+ }
+
+ __exports__.Promise = Promise;
+ });
+define("promise/race",
+ ["./utils","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ /* global toString */
+ var isArray = __dependency1__.isArray;
+
+ /**
+ `RSVP.race` allows you to watch a series of promises and act as soon as the
+ first promise given to the `promises` argument fulfills or rejects.
+
+ Example:
+
+ ```javascript
+ var promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 1");
+ }, 200);
+ });
+
+ var promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 2");
+ }, 100);
+ });
+
+ RSVP.race([promise1, promise2]).then(function(result){
+ // result === "promise 2" because it was resolved before promise1
+ // was resolved.
+ });
+ ```
+
+ `RSVP.race` is deterministic in that only the state of the first completed
+ promise matters. For example, even if other promises given to the `promises`
+ array argument are resolved, but the first completed promise has become
+ rejected before the other promises became fulfilled, the returned promise
+ will become rejected:
+
+ ```javascript
+ var promise1 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ resolve("promise 1");
+ }, 200);
+ });
+
+ var promise2 = new RSVP.Promise(function(resolve, reject){
+ setTimeout(function(){
+ reject(new Error("promise 2"));
+ }, 100);
+ });
+
+ RSVP.race([promise1, promise2]).then(function(result){
+ // Code here never runs because there are rejected promises!
+ }, function(reason){
+ // reason.message === "promise2" because promise 2 became rejected before
+ // promise 1 became fulfilled
+ });
+ ```
+
+ @method race
+ @for RSVP
+ @param {Array} promises array of promises to observe
+ @param {String} label optional string for describing the promise returned.
+ Useful for tooling.
+ @return {Promise} a promise that becomes fulfilled with the value the first
+ completed promises is resolved with if the first completed promise was
+ fulfilled, or rejected with the reason that the first completed promise
+ was rejected with.
+ */
+ function race(promises) {
+ /*jshint validthis:true */
+ var Promise = this;
+
+ if (!isArray(promises)) {
+ throw new TypeError('You must pass an array to race.');
+ }
+ return new Promise(function(resolve, reject) {
+ var results = [], promise;
+
+ for (var i = 0; i < promises.length; i++) {
+ promise = promises[i];
+
+ if (promise && typeof promise.then === 'function') {
+ promise.then(resolve, reject);
+ } else {
+ resolve(promise);
+ }
+ }
+ });
+ }
+
+ __exports__.race = race;
+ });
+define("promise/reject",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ /**
+ `RSVP.reject` returns a promise that will become rejected with the passed
+ `reason`. `RSVP.reject` is essentially shorthand for the following:
+
+ ```javascript
+ var promise = new RSVP.Promise(function(resolve, reject){
+ reject(new Error('WHOOPS'));
+ });
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ Instead of writing the above, your code now simply becomes the following:
+
+ ```javascript
+ var promise = RSVP.reject(new Error('WHOOPS'));
+
+ promise.then(function(value){
+ // Code here doesn't run because the promise is rejected!
+ }, function(reason){
+ // reason.message === 'WHOOPS'
+ });
+ ```
+
+ @method reject
+ @for RSVP
+ @param {Any} reason value that the returned promise will be rejected with.
+ @param {String} label optional string for identifying the returned promise.
+ Useful for tooling.
+ @return {Promise} a promise that will become rejected with the given
+ `reason`.
+ */
+ function reject(reason) {
+ /*jshint validthis:true */
+ var Promise = this;
+
+ return new Promise(function (resolve, reject) {
+ reject(reason);
+ });
+ }
+
+ __exports__.reject = reject;
+ });
+define("promise/resolve",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function resolve(value) {
+ /*jshint validthis:true */
+ if (value && typeof value === 'object' && value.constructor === this) {
+ return value;
+ }
+
+ var Promise = this;
+
+ return new Promise(function(resolve) {
+ resolve(value);
+ });
+ }
+
+ __exports__.resolve = resolve;
+ });
+define("promise/utils",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function objectOrFunction(x) {
+ return isFunction(x) || (typeof x === "object" && x !== null);
+ }
+
+ function isFunction(x) {
+ return typeof x === "function";
+ }
+
+ function isArray(x) {
+ return Object.prototype.toString.call(x) === "[object Array]";
+ }
+
+ // Date.now is not available in browsers < IE9
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now#Compatibility
+ var now = Date.now || function() { return new Date().getTime(); };
+
+
+ __exports__.objectOrFunction = objectOrFunction;
+ __exports__.isFunction = isFunction;
+ __exports__.isArray = isArray;
+ __exports__.now = now;
+ });
+requireModule('promise/polyfill').polyfill();
+}()); \ No newline at end of file
diff --git a/snap.html b/snap.html
index e3c54dc..5126b08 100755
--- a/snap.html
+++ b/snap.html
@@ -17,16 +17,30 @@
<script type="text/javascript" src="store.js"></script>
<script type="text/javascript" src="locale.js"></script>
<script type="text/javascript" src="cloud.js"></script>
+ <script type="text/javascript" src="promise-1.0.0.js"></script>
+ <script type="text/javascript" src="octokit.js/octokit.js"></script>
+ <script type="text/javascript" src="github.js"></script>
<script type="text/javascript" src="sha512.js"></script>
+<<<<<<< HEAD
+ <script type="text/javascript" src="Snapin8r/snapin8r.min.js"></script>
+ <script type="text/javascript" src="vkBeautify/vkbeautify.js"></script>
+ <script type="text/javascript" src="diff-merge/dist/diff.js"></script>
+=======
+>>>>>>> mobileapp
<script type="text/javascript">
var world;
window.onload = function () {
world = new WorldMorph(document.getElementById('world'));
world.worldCanvas.focus();
+<<<<<<< HEAD
+ new IDE_Morph().openIn(world);
+ setInterval(loop, 10);
+=======
var ide = new IDE_Morph()
ide.openIn(world);
setInterval(loop, 1);
+>>>>>>> mobileapp
};
function loop() {
world.doOneCycle();
diff --git a/store.js b/store.js
index 8cbc10f..c6449fd 100644
--- a/store.js
+++ b/store.js
@@ -97,7 +97,7 @@ XML_Serializer.prototype.serialize = function (object) {
this.flushMedia();
xml = this.store(object);
this.flush();
- return xml;
+ return vkbeautify.xml(xml);
};
XML_Serializer.prototype.store = function (object, mediaID) {
@@ -135,7 +135,7 @@ XML_Serializer.prototype.mediaXML = function () {
);
xml = xml + str;
});
- return xml + '</media>';
+ return vkbeautify.xml(xml + '</media>');
};
XML_Serializer.prototype.add = function (object) {
diff --git a/threads.js b/threads.js
index 8780de5..9626b89 100644
--- a/threads.js
+++ b/threads.js
@@ -128,6 +128,15 @@ function snapEquals(a, b) {
return x === y;
}
+// stricter alternative to parseFloat
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat/
+function filterFloat(value) {
+ if(/^(\-|\+)?([0-9]+(\.[0-9]+)?|Infinity)$/
+ .test(value))
+ return Number(value);
+ return NaN;
+}
+
// ThreadManager ///////////////////////////////////////////////////////
function ThreadManager() {
@@ -439,11 +448,20 @@ Process.prototype.pause = function () {
if (this.context && this.context.startTime) {
this.pauseOffset = Date.now() - this.context.startTime;
}
+ if (this.context.activeNote) {
+ this.context.activeNote.stop();
+ }
};
Process.prototype.resume = function () {
this.isPaused = false;
this.pauseOffset = null;
+ if (this.context.activeNote) {
+ if (this.context.activeNote.oscillator === null) {
+ // prevents Note from resuming twice
+ this.context.activeNote.play();
+ }
+ }
};
Process.prototype.pauseStep = function () {
@@ -733,6 +751,13 @@ Process.prototype.expectReport = function () {
// Process Exception Handling
+Process.prototype.checkIfList = function (maybeList) {
+ if (!(maybeList instanceof List)) {
+ maybeList = new List();
+ }
+ return maybeList;
+};
+
Process.prototype.handleError = function (error, element) {
var m = element;
this.stop();
@@ -1304,15 +1329,19 @@ Process.prototype.reportCONS = function (car, cdr) {
};
Process.prototype.reportCDR = function (list) {
+ list = this.checkIfList(list);
return list.cdr();
};
Process.prototype.doAddToList = function (element, list) {
+ list = this.checkIfList(list);
list.add(element);
};
Process.prototype.doDeleteFromList = function (index, list) {
var idx = index;
+ list = this.checkIfList(list);
+
if (this.inputOption(index) === 'all') {
return list.clear();
}
@@ -1327,6 +1356,8 @@ Process.prototype.doDeleteFromList = function (index, list) {
Process.prototype.doInsertInList = function (element, index, list) {
var idx = index;
+ list = this.checkIfList(list);
+
if (index === '') {
return null;
}
@@ -1341,6 +1372,8 @@ Process.prototype.doInsertInList = function (element, index, list) {
Process.prototype.doReplaceInList = function (index, list, element) {
var idx = index;
+ list = this.checkIfList(list);
+
if (index === '') {
return null;
}
@@ -1355,6 +1388,8 @@ Process.prototype.doReplaceInList = function (index, list, element) {
Process.prototype.reportListItem = function (index, list) {
var idx = index;
+ list = this.checkIfList(list);
+
if (index === '') {
return '';
}
@@ -1368,10 +1403,12 @@ Process.prototype.reportListItem = function (index, list) {
};
Process.prototype.reportListLength = function (list) {
+ list = this.checkIfList(list);
return list.length();
};
Process.prototype.reportListContainsItem = function (list, element) {
+ list = this.checkIfList(list);
return list.contains(element);
};
@@ -1427,7 +1464,7 @@ Process.prototype.doStopAll = function () {
if (this.homeContext.receiver) {
stage = this.homeContext.receiver.parentThatIsA(StageMorph);
if (stage) {
- stage.threads.resumeAll(stage);
+ //stage.threads.resumeAll(stage); // leads to a strange Note bug
stage.keysPressed = {};
stage.threads.stopAll();
stage.stopAllActiveSounds();
@@ -1630,6 +1667,8 @@ Process.prototype.reportMap = function (reporter, list) {
// documented in each of the variants' code (linked or arrayed) below
var next;
+ list = this.checkIfList(list);
+
if (list.isLinked) {
// this.context.inputs:
// [0] - reporter
@@ -1918,13 +1957,14 @@ Process.prototype.reportIsA = function (thing, typeString) {
Process.prototype.reportTypeOf = function (thing) {
// answer a string denoting the argument's type
var exp;
+
if (thing === null || (thing === undefined)) {
return 'nothing';
}
if (thing === true || (thing === false)) {
return 'Boolean';
}
- if (!isNaN(parseFloat(thing))) {
+ if (!isNaN(filterFloat(thing))) {
return 'number';
}
if (isString(thing)) {
@@ -2991,18 +3031,32 @@ Process.prototype.doPlayNote = function (pitch, beats) {
Process.prototype.doPlayNoteForSecs = function (pitch, secs) {
// interpolated
+ var receiver = this.homeContext.receiver;
+ var volume = receiver.volume;
+ var muted = receiver.parentThatIsA(StageMorph).muted;
+
+ if (muted === true) {
+ volume = 0;
+ }
+
if (!this.context.startTime) {
this.context.startTime = Date.now();
- this.context.activeNote = new Note(pitch);
+ this.context.activeNote = new Note(pitch, volume);
this.context.activeNote.play();
}
+
if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
if (this.context.activeNote) {
this.context.activeNote.stop();
this.context.activeNote = null;
}
return null;
+ } else if (this.context.activeNote) {
+ if (this.context.activeNote.volume !== volume) {
+ this.context.activeNote.setVolume(volume);
+ }
}
+
this.pushContext('doYield');
this.pushContext();
};
diff --git a/vkBeautify b/vkBeautify
new file mode 160000
+Subproject ecfc3b9e2b911ad8ebd49600c4089aca30619f2