diff options
| -rw-r--r-- | .gitmodules | 12 | ||||
| m--------- | Snapin8r | 0 | ||||
| -rw-r--r-- | blocks.js | 36 | ||||
| -rw-r--r-- | github.js | 312 | ||||
| -rw-r--r-- | gui.js | 482 | ||||
| -rwxr-xr-x[-rw-r--r--] | lang-ko.js | 492 | ||||
| -rw-r--r-- | objects.js | 179 | ||||
| m--------- | octokit.js | 0 | ||||
| -rw-r--r-- | promise-1.0.0.js | 684 | ||||
| -rwxr-xr-x | snap.html | 14 | ||||
| -rw-r--r-- | store.js | 4 | ||||
| -rw-r--r-- | threads.js | 60 | ||||
| m--------- | vkBeautify | 0 |
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 @@ -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); +}; @@ -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' + '확인' }; @@ -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 @@ -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(); @@ -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) { @@ -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 |
