diff options
| author | Gubolin <gubolin@fantasymail.de> | 2015-01-11 10:32:15 +0100 |
|---|---|---|
| committer | Gubolin <gubolin@fantasymail.de> | 2015-01-11 10:32:15 +0100 |
| commit | ff3abf9d87a502cdf31a5f4abcbbf1826fd64fc9 (patch) | |
| tree | 43e0a2734bc16d5906decf05662d30436cb42313 | |
| parent | 09d04ecd59279513015274a1386d345e3f8a1640 (diff) | |
| parent | 3c03814833a272b305cbac5ad7f70190ec2a8926 (diff) | |
| download | snap-ff3abf9d87a502cdf31a5f4abcbbf1826fd64fc9.tar.gz snap-ff3abf9d87a502cdf31a5f4abcbbf1826fd64fc9.zip | |
merge mobileapp
| -rwxr-xr-x | binary.js | 1099 | ||||
| -rwxr-xr-x | binary.sh | 92 | ||||
| -rw-r--r-- | config.xml | 15 | ||||
| -rwxr-xr-x | desktop.sh | 34 | ||||
| -rwxr-xr-x | mobile.sh | 65 | ||||
| -rw-r--r-- | morphic.js | 5 | ||||
| -rw-r--r-- | objects.js | 52 | ||||
| -rw-r--r-- | package.json | 8 | ||||
| -rwxr-xr-x | snap.html | 10 | ||||
| -rw-r--r-- | threads.js | 126 |
10 files changed, 1502 insertions, 4 deletions
diff --git a/binary.js b/binary.js new file mode 100755 index 0000000..d1a9b21 --- /dev/null +++ b/binary.js @@ -0,0 +1,1099 @@ +/* + + gui.js + + a programming environment + based on morphic.js, blocks.js, threads.js and objects.js + inspired by Scratch + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2014 by Jens Mönig + + 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/>. + + + prerequisites: + -------------- + needs blocks.js, threads.js, objects.js and morphic.js + + + toc + --- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + IDE_Morph + + + credits + ------- + Nathan Dinsmore contributed saving and loading of projects, + ypr-Snap! project conversion and countless bugfixes + Ian Reynolds contributed handling and visualization of sounds + +*/ + +/*global modules, Morph, SpriteMorph, Color, newCanvas, +VariableFrame, Point, MenuMorph, +morphicVersion, DialogBoxMorph, ToggleButtonMorph, contains, +StageMorph, PushButtonMorph, Process, SnapSerializer, MorphicPreferences, +SymbolMorph, +BlockImportDialogMorph, SnapTranslator, localize, List */ + +// Global stuff //////////////////////////////////////////////////////// + +modules.gui = '2014-July-30'; + +// Declarations + +var IDE_Morph; + +// IDE_Morph /////////////////////////////////////////////////////////// + +// I am SNAP's top-level frame, the Editor window + +// IDE_Morph inherits from Morph: + +IDE_Morph.prototype = new Morph(); +IDE_Morph.prototype.constructor = IDE_Morph; +IDE_Morph.uber = Morph.prototype; + +// IDE_Morph preferences settings and skins + +IDE_Morph.prototype.setDefaultDesign = function () { + MorphicPreferences.isFlat = false; + SpriteMorph.prototype.paletteColor = new Color(55, 55, 55); + SpriteMorph.prototype.paletteTextColor = new Color(230, 230, 230); + StageMorph.prototype.paletteTextColor + = SpriteMorph.prototype.paletteTextColor; + StageMorph.prototype.paletteColor = SpriteMorph.prototype.paletteColor; + SpriteMorph.prototype.sliderColor + = SpriteMorph.prototype.paletteColor.lighter(30); + + IDE_Morph.prototype.buttonContrast = 30; + IDE_Morph.prototype.backgroundColor = new Color(40, 40, 40); + IDE_Morph.prototype.frameColor = SpriteMorph.prototype.paletteColor; + + IDE_Morph.prototype.groupColor + = SpriteMorph.prototype.paletteColor.lighter(8); + IDE_Morph.prototype.sliderColor = SpriteMorph.prototype.sliderColor; + IDE_Morph.prototype.buttonLabelColor = new Color(255, 255, 255); + IDE_Morph.prototype.tabColors = [ + IDE_Morph.prototype.groupColor.darker(40), + IDE_Morph.prototype.groupColor.darker(60), + IDE_Morph.prototype.groupColor + ]; + IDE_Morph.prototype.rotationStyleColors = IDE_Morph.prototype.tabColors; + IDE_Morph.prototype.appModeColor = new Color(); + IDE_Morph.prototype.scriptsPaneTexture = 'scriptsPaneTexture.gif'; + IDE_Morph.prototype.padding = 5; +}; + +IDE_Morph.prototype.setFlatDesign = function () { + MorphicPreferences.isFlat = true; + SpriteMorph.prototype.paletteColor = new Color(255, 255, 255); + SpriteMorph.prototype.paletteTextColor = new Color(70, 70, 70); + StageMorph.prototype.paletteTextColor + = SpriteMorph.prototype.paletteTextColor; + StageMorph.prototype.paletteColor = SpriteMorph.prototype.paletteColor; + SpriteMorph.prototype.sliderColor = SpriteMorph.prototype.paletteColor; + + IDE_Morph.prototype.buttonContrast = 30; + IDE_Morph.prototype.backgroundColor = new Color(200, 200, 200); + IDE_Morph.prototype.frameColor = new Color(255, 255, 255); + + IDE_Morph.prototype.groupColor = new Color(230, 230, 230); + IDE_Morph.prototype.sliderColor = SpriteMorph.prototype.sliderColor; + IDE_Morph.prototype.buttonLabelColor = new Color(70, 70, 70); + IDE_Morph.prototype.tabColors = [ + IDE_Morph.prototype.groupColor.lighter(60), + IDE_Morph.prototype.groupColor.darker(10), + IDE_Morph.prototype.groupColor + ]; + IDE_Morph.prototype.rotationStyleColors = [ + IDE_Morph.prototype.groupColor, + IDE_Morph.prototype.groupColor.darker(10), + IDE_Morph.prototype.groupColor.darker(30) + ]; + IDE_Morph.prototype.appModeColor = IDE_Morph.prototype.frameColor; + IDE_Morph.prototype.scriptsPaneTexture = null; + IDE_Morph.prototype.padding = 1; +}; + +IDE_Morph.prototype.setDefaultDesign(); + +// IDE_Morph instance creation: + +function IDE_Morph(isAutoFill) { + this.init(isAutoFill); +} + +IDE_Morph.prototype.init = function (isAutoFill) { + // global font setting + MorphicPreferences.globalFontFamily = 'Helvetica, Arial'; + + // restore saved user preferences + this.userLanguage = null; + + // additional properties: + this.source = 'local'; + this.serializer = new SnapSerializer(); + + this.globalVariables = new VariableFrame(); + this.currentSprite = new SpriteMorph(this.globalVariables); + this.sprites = new List([this.currentSprite]); + this.currentCategory = 'motion'; + this.currentTab = 'scripts'; + this.projectName = ''; + this.projectNotes = ''; + + this.logo = null; + this.controlBar = null; + this.stage = null; + + this.isAutoFill = isAutoFill || true; + + this.filePicker = null; + this.hasChangedMedia = false; + + this.isAnimating = true; + this.stageRatio = 1; // for IDE animations, e.g. when zooming + + this.loadNewProject = false; // flag when starting up translated + this.shield = null; + + // initialize inherited properties: + IDE_Morph.uber.init.call(this); + + // override inherited properites: + this.color = this.appModeColor; +}; + +IDE_Morph.prototype.openIn = function (world) { + var hash; + + this.buildPanes(); + world.add(this); + + this.reactToWorldResize(world.bounds); + + function getURL(url) { + try { + var request = new XMLHttpRequest(); + request.open('GET', url, false); + request.send(); + if (request.status === 200) { + return request.responseText; + } + throw new Error('unable to retrieve ' + url); + } catch (err) { + return; + } + } + + function interpretUrlAnchors() { + if (location.hash.substr(0, 6) === '#open:') { + hash = location.hash.substr(6); + if (hash.charAt(0) === '%' + || hash.search(/\%(?:[0-9a-f]{2})/i) > -1) { + hash = decodeURIComponent(hash); + } + if (contains( + ['project', 'blocks', 'sprites', 'snapdata'].map( + function (each) { + return hash.substr(0, 8).indexOf(each); + } + ), + 1 + )) { + this.droppedText(hash); + } else { + this.droppedText(getURL(hash)); + } + } + } + + if (this.userLanguage) { + this.setLanguage(this.userLanguage, interpretUrlAnchors); + } else { + interpretUrlAnchors.call(this); + } +}; + +// IDE_Morph construction + +IDE_Morph.prototype.buildPanes = function () { + this.createLogo(); + this.createControlBar(); + this.createStage(); +}; + +IDE_Morph.prototype.createLogo = function () { + var myself = this; + + if (this.logo) { + this.logo.destroy(); + } + + this.logo = new Morph(); + this.logo.texture = 'snap_logo_sm.png'; + this.logo.drawNew = function () { + this.image = newCanvas(this.extent()); + var context = this.image.getContext('2d'); + context.fillStyle = this.color; + context.fillRect(0, 0, this.width(), this.height()); + if (this.texture) { + this.drawTexture(this.texture); + } + }; + + this.logo.drawCachedTexture = function () { + var context = this.image.getContext('2d'); + context.drawImage( + this.cachedTexture, + 5, + Math.round((this.height() - this.cachedTexture.height) / 2) + ); + this.changed(); + }; + + this.logo.mouseClickLeft = function () { + myself.snapMenu(); + }; + + this.logo.color = new Color(); + this.logo.setExtent(new Point(200, 28)); // dimensions are fixed + this.add(this.logo); +}; + +IDE_Morph.prototype.createControlBar = function () { + // assumes the logo has already been created + var padding = 5, + button, + stopButton, + pauseButton, + startButton, + x, + colors = [ + this.groupColor, + this.frameColor.darker(50), + this.frameColor.darker(50) + ], + myself = this; + + if (this.controlBar) { + this.controlBar.destroy(); + } + + this.controlBar = new Morph(); + this.controlBar.color = this.appModeColor; + this.controlBar.setHeight(this.logo.height()); // height is fixed + this.controlBar.mouseClickLeft = function () { + this.world().fillPage(); + }; + this.add(this.controlBar); + + // stopButton + button = new PushButtonMorph( + this, + 'stopAllScripts', + new SymbolMorph('octagon', 14) + ); + 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 = new Color(200, 0, 0); + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'stop\nevery-\nthing'; + button.fixLayout(); + stopButton = button; + this.controlBar.add(stopButton); + + //pauseButton + button = new ToggleButtonMorph( + null, //colors, + myself, // the IDE is the target + 'togglePauseResume', + [ + new SymbolMorph('pause', 12), + new SymbolMorph('pointRight', 14) + ], + function () { // query + return myself.isPaused(); + } + ); + + 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 = new Color(255, 220, 0); + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'pause/resume\nall scripts'; + button.fixLayout(); + button.refresh(); + pauseButton = button; + this.controlBar.add(pauseButton); + this.controlBar.pauseButton = pauseButton; // for refreshing + + // startButton + button = new PushButtonMorph( + this, + 'pressStart', + new SymbolMorph('flag', 14) + ); + 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 = new Color(0, 200, 0); + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'start green\nflag scripts'; + button.fixLayout(); + startButton = button; + this.controlBar.add(startButton); + this.controlBar.startButton = startButton; + + this.controlBar.fixLayout = function () { + x = this.right() - padding; + [stopButton, pauseButton, startButton].forEach( + function (button) { + button.setCenter(myself.controlBar.center()); + button.setRight(x); + x -= button.width(); + x -= padding; + } + ); + + x = myself.right() - StageMorph.prototype.dimensions.x; + + this.updateLabel(); + }; + + this.controlBar.updateLabel = function () { + if (this.label) { + this.label.destroy(); + } + }; +}; + +IDE_Morph.prototype.createStage = function () { + // assumes that the logo pane has already been created + if (this.stage) { + this.stage.destroy(); + } + StageMorph.prototype.frameRate = 0; + this.stage = new StageMorph(this.globalVariables); + this.stage.setExtent(this.stage.dimensions); // dimensions are fixed + if (this.currentSprite instanceof SpriteMorph) { + this.currentSprite.setPosition( + this.stage.center().subtract( + this.currentSprite.extent().divideBy(2) + ) + ); + this.stage.add(this.currentSprite); + } + this.add(this.stage); +}; + +IDE_Morph.prototype.createCorral = function () { +}; + +// IDE_Morph resizing + +IDE_Morph.prototype.setExtent = function (point) { + var minExt, + ext; + + // determine the minimum dimensions making sense for the current mode + minExt = StageMorph.prototype.dimensions.add( + this.controlBar.height() + 10 + ); + ext = point.max(minExt); + IDE_Morph.uber.setExtent.call(this, ext); + this.fixLayout(); +}; + +// IDE_Morph events + +IDE_Morph.prototype.reactToWorldResize = function (rect) { + if (this.isAutoFill) { + this.setPosition(rect.origin); + this.setExtent(rect.extent()); + } + if (this.filePicker) { + document.body.removeChild(this.filePicker); + this.filePicker = null; + } +}; + +// IDE_Morph layout + +IDE_Morph.prototype.fixLayout = function () { + var padding = this.padding; + + Morph.prototype.trackChanges = false; + + // controlBar + this.controlBar.setPosition(this.logo.topRight()); + this.controlBar.setWidth(this.right() - this.controlBar.left()); + this.controlBar.fixLayout(); + + // stage + this.stage.setScale(Math.floor(Math.min( + (this.width() - padding * 2) / this.stage.dimensions.x, + (this.height() - this.controlBar.height() * 2 - padding * 2) + / this.stage.dimensions.y + ) * 10) / 10); + this.stage.setCenter(this.center()); + + Morph.prototype.trackChanges = true; + this.changed(); +}; + +IDE_Morph.prototype.droppedText = function (aString, name) { + var lbl = name ? name.split('.')[0] : ''; + if (aString.indexOf('<project') === 0) { + return this.openProjectString(aString); + } + if (aString.indexOf('<snapdata') === 0) { + return this.openCloudDataString(aString); + } + if (aString.indexOf('<blocks') === 0) { + return this.openBlocksString(aString, lbl, true); + } + if (aString.indexOf('<sprites') === 0) { + return this.openSpritesString(aString); + } + if (aString.indexOf('<media') === 0) { + return this.openMediaString(aString); + } +}; + +// IDE_Morph events + +IDE_Morph.prototype.pressStart = function () { + if (this.world().currentKey === 16) { // shiftClicked + this.toggleFastTracking(); + } else { + this.runScripts(); + } +}; + +IDE_Morph.prototype.toggleFastTracking = function () { + if (this.stage.isFastTracked) { + this.stopFastTracking(); + } else { + this.startFastTracking(); + } +}; + +IDE_Morph.prototype.toggleVariableFrameRate = function () { + if (StageMorph.prototype.frameRate) { + StageMorph.prototype.frameRate = 0; + this.stage.fps = 0; + } else { + StageMorph.prototype.frameRate = 30; + this.stage.fps = 30; + } +}; + +IDE_Morph.prototype.startFastTracking = function () { + this.stage.isFastTracked = true; + this.stage.fps = 0; + this.controlBar.startButton.labelString = new SymbolMorph('flash', 14); + this.controlBar.startButton.drawNew(); + this.controlBar.startButton.fixLayout(); +}; + +IDE_Morph.prototype.stopFastTracking = function () { + this.stage.isFastTracked = false; + this.stage.fps = this.stage.frameRate; + this.controlBar.startButton.labelString = new SymbolMorph('flag', 14); + this.controlBar.startButton.drawNew(); + this.controlBar.startButton.fixLayout(); +}; + +IDE_Morph.prototype.runScripts = function () { + this.stage.fireGreenFlagEvent(); +}; + +IDE_Morph.prototype.togglePauseResume = function () { + if (this.stage.threads.isPaused()) { + this.stage.threads.resumeAll(this.stage); + } else { + this.stage.threads.pauseAll(this.stage); + } + this.controlBar.pauseButton.refresh(); +}; + +IDE_Morph.prototype.isPaused = function () { + if (!this.stage) {return false; } + return this.stage.threads.isPaused(); +}; + +IDE_Morph.prototype.stopAllScripts = function () { + this.stage.fireStopAllEvent(); +}; + +IDE_Morph.prototype.selectSprite = function (sprite) { + this.currentSprite = sprite; + this.currentSprite.scripts.fixMultiArgs(); +}; + +// IDE_Morph menus + +IDE_Morph.prototype.snapMenu = function () { + var menu, + world = this.world(); + + menu = new MenuMorph(this); + menu.addItem('About...', 'aboutSnap'); + menu.addLine(); + menu.addItem( + 'Reference manual', + function () { + window.open('help/SnapManual.pdf', 'SnapReferenceManual'); + } + ); + menu.addItem( + 'Snap! website', + function () { + window.open('http://snap.berkeley.edu/', 'SnapWebsite'); + } + ); + menu.addItem( + 'Download source', + function () { + window.open( + 'http://snap.berkeley.edu/snapsource/snap.zip', + 'SnapSource' + ); + } + ); + if (world.isDevMode) { + menu.addLine(); + menu.addItem( + 'Switch back to user mode', + 'switchToUserMode', + 'disable deep-Morphic\ncontext menus' + + '\nand show user-friendly ones', + new Color(0, 100, 0) + ); + } else if (world.currentKey === 16) { // shift-click + menu.addLine(); + menu.addItem( + 'Switch to dev mode', + 'switchToDevMode', + 'enable Morphic\ncontext menus\nand inspectors,' + + '\nnot user-friendly!', + new Color(100, 0, 0) + ); + } + menu.popup(world, this.logo.bottomLeft()); +}; + +// IDE_Morph menu actions + +IDE_Morph.prototype.aboutSnap = function () { + var dlg, aboutTxt, noticeTxt, creditsTxt, versions = '', translations, + module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn, + world = this.world(); + + aboutTxt = 'Snap! 4.0\nBuild Your Own Blocks\n\n--- beta ---\n\n' + + 'Copyright \u24B8 2014 Jens M\u00F6nig and ' + + 'Brian Harvey\n' + + 'jens@moenig.org, bh@cs.berkeley.edu\n\n' + + + 'Snap! is developed by the University of California, Berkeley\n' + + ' with support from the National Science Foundation ' + + 'and MioSoft. \n' + + + 'The design of Snap! is influenced and inspired by Scratch,\n' + + 'from the Lifelong Kindergarten group at the MIT Media Lab\n\n' + + + 'for more information see http://snap.berkeley.edu\n' + + 'and http://scratch.mit.edu'; + + noticeTxt = localize('License') + + '\n\n' + + 'Snap! is free software: you can redistribute it and/or modify\n' + + 'it under the terms of the GNU Affero General Public License as\n' + + 'published by the Free Software Foundation, either version 3 of\n' + + 'the License, or (at your option) any later version.\n\n' + + + 'This program is distributed in the hope that it will be useful,\n' + + 'but WITHOUT ANY WARRANTY; without even the implied warranty of\n' + + 'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n' + + 'GNU Affero General Public License for more details.\n\n' + + + 'You should have received a copy of the\n' + + 'GNU Affero General Public License along with this program.\n' + + 'If not, see http://www.gnu.org/licenses/'; + + creditsTxt = localize('Contributors') + + '\n\nNathan Dinsmore: Saving/Loading, Snap-Logo Design, ' + + 'countless bugfixes' + + '\nKartik Chandra: Paint Editor' + + '\nMichael Ball: Time/Date UI, many bugfixes' + + '\n"Ava" Yuan Yuan: Graphic Effects' + + '\nKyle Hotchkiss: Block search design' + + '\nIan Reynolds: UI Design, Event Bindings, ' + + 'Sound primitives' + + '\nIvan Motyashov: Initial Squeak Porting' + + '\nDavide Della Casa: Morphic Optimizations' + + '\nAchal Dave: Web Audio' + + '\nJoe Otto: Morphic Testing and Debugging'; + + for (module in modules) { + if (Object.prototype.hasOwnProperty.call(modules, module)) { + versions += ('\n' + module + ' (' + + modules[module] + ')'); + } + } + if (versions !== '') { + versions = localize('current module versions:') + ' \n\n' + + 'morphic (' + morphicVersion + ')' + + versions; + } + translations = localize('Translations') + '\n' + SnapTranslator.credits(); + + dlg = new DialogBoxMorph(); + dlg.inform('About Snap', aboutTxt, world); + btn1 = dlg.buttons.children[0]; + translatorsBtn = dlg.addButton( + function () { + dlg.body.text = translations; + dlg.body.drawNew(); + btn1.show(); + btn2.show(); + btn3.hide(); + btn4.hide(); + licenseBtn.hide(); + translatorsBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); + dlg.setCenter(world.center()); + }, + 'Translators...' + ); + btn2 = dlg.addButton( + function () { + dlg.body.text = aboutTxt; + dlg.body.drawNew(); + btn1.show(); + btn2.hide(); + btn3.show(); + btn4.show(); + licenseBtn.show(); + translatorsBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); + dlg.setCenter(world.center()); + }, + 'Back...' + ); + btn2.hide(); + licenseBtn = dlg.addButton( + function () { + dlg.body.text = noticeTxt; + dlg.body.drawNew(); + btn1.show(); + btn2.show(); + btn3.hide(); + btn4.hide(); + licenseBtn.hide(); + translatorsBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); + dlg.setCenter(world.center()); + }, + 'License...' + ); + btn3 = dlg.addButton( + function () { + dlg.body.text = versions; + dlg.body.drawNew(); + btn1.show(); + btn2.show(); + btn3.hide(); + btn4.hide(); + licenseBtn.hide(); + translatorsBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); + dlg.setCenter(world.center()); + }, + 'Modules...' + ); + btn4 = dlg.addButton( + function () { + dlg.body.text = creditsTxt; + dlg.body.drawNew(); + btn1.show(); + btn2.show(); + translatorsBtn.show(); + btn3.hide(); + btn4.hide(); + licenseBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); + dlg.setCenter(world.center()); + }, + 'Credits...' + ); + translatorsBtn.hide(); + dlg.fixLayout(); + dlg.drawNew(); +}; + +IDE_Morph.prototype.openProjectString = function (str) { + var msg, + myself = this; + this.nextSteps([ + function () { + msg = myself.showMessage('Opening project...'); + }, + function () { + myself.rawOpenProjectString(str); + }, + function () { + msg.destroy(); + } + ]); +}; + +IDE_Morph.prototype.rawOpenProjectString = function (str) { + StageMorph.prototype.hiddenPrimitives = {}; + StageMorph.prototype.codeMappings = {}; + StageMorph.prototype.codeHeaders = {}; + StageMorph.prototype.enableCodeMapping = false; + if (Process.prototype.isCatchingErrors) { + try { + this.serializer.openProject(this.serializer.load(str), this); + } catch (err) { + this.showMessage('Load failed: ' + err); + } + } else { + this.serializer.openProject(this.serializer.load(str), this); + } + this.stopFastTracking(); +}; + +IDE_Morph.prototype.openBlocksString = function (str, name, silently) { + var msg, + myself = this; + this.nextSteps([ + function () { + msg = myself.showMessage('Opening blocks...'); + }, + function () { + myself.rawOpenBlocksString(str, name, silently); + }, + function () { + msg.destroy(); + } + ]); +}; + +IDE_Morph.prototype.rawOpenBlocksString = function (str, name, silently) { + // name is optional (string), so is silently (bool) + var blocks, + myself = this; + if (Process.prototype.isCatchingErrors) { + try { + blocks = this.serializer.loadBlocks(str, myself.stage); + } catch (err) { + this.showMessage('Load failed: ' + err); + } + } else { + blocks = this.serializer.loadBlocks(str, myself.stage); + } + if (silently) { + blocks.forEach(function (def) { + def.receiver = myself.stage; + myself.stage.globalBlocks.push(def); + myself.stage.replaceDoubleDefinitionsFor(def); + }); + this.showMessage( + 'Imported Blocks Module' + (name ? ': ' + name : '') + '.', + 2 + ); + } else { + new BlockImportDialogMorph(blocks, this.stage, name).popUp(); + } +}; + +IDE_Morph.prototype.openSpritesString = function (str) { + var msg, + myself = this; + this.nextSteps([ + function () { + msg = myself.showMessage('Opening sprite...'); + }, + function () { + myself.rawOpenSpritesString(str); + }, + function () { + msg.destroy(); + } + ]); +}; + +IDE_Morph.prototype.rawOpenSpritesString = function (str) { + if (Process.prototype.isCatchingErrors) { + try { + this.serializer.loadSprites(str, this); + } catch (err) { + this.showMessage('Load failed: ' + err); + } + } else { + this.serializer.loadSprites(str, this); + } +}; + +IDE_Morph.prototype.openMediaString = function (str) { + if (Process.prototype.isCatchingErrors) { + try { + this.serializer.loadMedia(str); + } catch (err) { + this.showMessage('Load failed: ' + err); + } + } else { + this.serializer.loadMedia(str); + } + this.showMessage('Imported Media Module.', 2); +}; + +IDE_Morph.prototype.openProject = function (name) { + var str; + if (name) { + this.showMessage('opening project\n' + name); + str = localStorage['-snap-project-' + name]; + this.openProjectString(str); + location.hash = '#open:' + str; + } +}; + +IDE_Morph.prototype.switchToUserMode = function () { + var world = this.world(); + + world.isDevMode = false; + Process.prototype.isCatchingErrors = true; + this.controlBar.updateLabel(); + this.isAutoFill = true; + this.isDraggable = false; + this.siblings().forEach(function (morph) { + if (morph instanceof DialogBoxMorph) { + world.add(morph); // bring to front + } else { + morph.destroy(); + } + }); + this.flushBlocksCache(); + // prevent non-DialogBoxMorphs from being dropped + // onto the World in user-mode + world.reactToDropOf = function (morph) { + if (!(morph instanceof DialogBoxMorph)) { + world.hand.grab(morph); + } + }; + this.showMessage('entering user mode', 1); + +}; + +IDE_Morph.prototype.switchToDevMode = function () { + var world = this.world(); + + world.isDevMode = true; + Process.prototype.isCatchingErrors = false; + this.controlBar.updateLabel(); + this.isAutoFill = false; + this.isDraggable = true; + this.setExtent(world.extent().subtract(100)); + this.setPosition(world.position().add(20)); + this.flushBlocksCache(); + // enable non-DialogBoxMorphs to be dropped + // onto the World in dev-mode + delete world.reactToDropOf; + this.showMessage( + 'entering development mode.\n\n' + + 'error catching is turned off,\n' + + 'use the browser\'s web console\n' + + 'to see error messages.' + ); +}; + +IDE_Morph.prototype.flushBlocksCache = function (category) { + // if no category is specified, the whole cache gets flushed + if (category) { + this.stage.blocksCache[category] = null; + this.stage.children.forEach(function (m) { + if (m instanceof SpriteMorph) { + m.blocksCache[category] = null; + } + }); + } else { + this.stage.blocksCache = {}; + this.stage.children.forEach(function (m) { + if (m instanceof SpriteMorph) { + m.blocksCache = {}; + } + }); + } +}; + +// IDE_Morph localization + +IDE_Morph.prototype.setLanguage = function (lang, callback) { + var translation = document.getElementById('language'), + src = 'lang-' + lang + '.js', + myself = this; + SnapTranslator.unload(); + if (translation) { + document.head.removeChild(translation); + } + if (lang === 'en') { + return this.reflectLanguage('en', callback); + } + myself.userLanguage = lang; + translation = document.createElement('script'); + translation.id = 'language'; + translation.onload = function () { + myself.reflectLanguage(lang, callback); + }; + document.head.appendChild(translation); + translation.src = src; +}; + +IDE_Morph.prototype.reflectLanguage = function (lang, callback) { + var projectData; + SnapTranslator.language = lang; + if (!this.loadNewProject) { + if (Process.prototype.isCatchingErrors) { + try { + projectData = this.serializer.serialize(this.stage); + } catch (err) { + this.showMessage('Serialization failed: ' + err); + } + } else { + projectData = this.serializer.serialize(this.stage); + } + } + SpriteMorph.prototype.initBlocks(); + this.fixLayout(); + if (this.loadNewProject) { + this.newProject(); + } else { + this.openProjectString(projectData); + } + this.saveSetting('language', lang); + if (callback) {callback.call(this); } +}; + +// IDE_Morph synchronous Http data fetching + +IDE_Morph.prototype.getURL = function (url) { + var request = new XMLHttpRequest(), + myself = this; + try { + request.open('GET', url, false); + request.send(); + if (request.status === 200) { + return request.responseText; + } + throw new Error('unable to retrieve ' + url); + } catch (err) { + myself.showMessage(err); + return; + } +}; + +IDE_Morph.prototype.getURLsbeOrRelative = function (url) { + var request = new XMLHttpRequest(), + myself = this; + try { + request.open('GET', 'http://snap.berkeley.edu/snapsource/' + + url, false); + request.send(); + if (request.status === 200) { + return request.responseText; + } + return myself.getURL(url); + } catch (err) { + myself.showMessage(err); + return; + } +}; + +// IDE_Morph user dialog shortcuts + +IDE_Morph.prototype.showMessage = function (message, secs) { + var m = new MenuMorph(null, message), + intervalHandle; + m.popUpCenteredInWorld(this.world()); + if (secs) { + intervalHandle = setInterval(function () { + m.destroy(); + clearInterval(intervalHandle); + }, secs * 1000); + } + return m; +}; + +IDE_Morph.prototype.inform = function (title, message) { + new DialogBoxMorph().inform( + title, + localize(message), + this.world() + ); +}; + +IDE_Morph.prototype.confirm = function (message, title, action) { + new DialogBoxMorph(null, action).askYesNo( + title, + localize(message), + this.world() + ); +}; + +IDE_Morph.prototype.prompt = function (message, callback, choices, key) { + (new DialogBoxMorph(null, callback)).withKey(key).prompt( + message, + '', + this.world(), + null, + choices + ); +}; diff --git a/binary.sh b/binary.sh new file mode 100755 index 0000000..212e7cb --- /dev/null +++ b/binary.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +if [[ "$snapsource" == "" ]] +then + export snapsource="https://github.com/Gubolin/snap.git" +fi + +if [[ $# < 2 ]] +then + echo "Usage: binary.sh OPTION PLATFORM [FILE]" + echo "" + echo "OPTIONS:" + echo " -m Mobile" + echo " -d Desktop" + echo "" + echo "PLATFORMS:" + echo " Mobile amazon-fireos android blackberry10 firefoxos ios ubuntu wp8 win8 tizen" + echo " Desktop win osx linux32 linux64" + echo "" + echo "If FILE is given, it will be #open-ed inside Snap\! immediately" + exit 0 +fi + +scriptdir=$(readlink -e ".") + +# Requirements: +# git +# UglifyJS2 (https://github.com/mishoo/UglifyJS2) + +ide=true +platform=$2 + +# presentation mode +if [[ "$3" != "" ]] +then + ide=false +fi + +if [ $ide == false ] +then + if [ -f "$3" ] + then + content=$(cat $3) + else + ide=true + fi +fi + +buildsource=$(mktemp -d) +git clone $snapsource $buildsource +cd "$buildsource" +git checkout mobileapp + +rm -rf .git/ + +if [ $ide == false ] +then + # minimize everything + rm lang* ypr.js paint.js cloud.js gui.js + + sed -i '/paint\.js"/d' snap.html + sed -i '/cloud\.js"/d' snap.html + sed -i 's/gui\.js"/binary\.js"/' snap.html + + # load custom project from file + sed -i '/sha512\.js"/a\ + <script type="text/javascript" src="code.js"></script> ' snap.html + + echo "var code =" > code.js + echo "'$content'" >> code.js + echo ";" >> code.js + + sed -i "/ide\.openIn/a\ + ide.droppedText(code); " snap.html +else + rm binary.js +fi + +# compress all js files +find . -name '*.js' | xargs -I {} uglifyjs {} -o {} -c + +# return to the directory where the script was called from +cd "$scriptdir" + +# run helper scripts for building + +if [[ $1 == "-m" ]] +then + ./mobile.sh "$2" "$buildsource" +else + ./desktop.sh "$2" "$buildsource" +fi diff --git a/config.xml b/config.xml new file mode 100644 index 0000000..36a7bcf --- /dev/null +++ b/config.xml @@ -0,0 +1,15 @@ +<?xml version='4.0' encoding='utf-8'?> +<widget id="edu.berkeley.snap" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"> + <name>Snap!</name> + <description> + Snap! Build Your Own Blocks + A visual, blocks based programming language inspired by Scratch + </description> + <author email="jens@moenig.org" href="http://snap.berkeley.edu"> + Jens Mönig and Brian Harvey + </author> + <content src="snap.html" /> + <access origin="*" /> + <preference name="Fullscreen" value="true" /> + <icon src="www/snap_logo_sm.png" /> +</widget> diff --git a/desktop.sh b/desktop.sh new file mode 100755 index 0000000..72b0786 --- /dev/null +++ b/desktop.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +if [[ "$snapsource" == "" ]] +then + export snapsource="https://github.com/Gubolin/snap.git" +fi + +if [[ $1 = "" ]] +then + echo "Usage: desktop.sh PLATFORM [BUILDSOURCE]" + exit 0 +fi + +scriptdir=$(readlink -e ".") + +# Requirements: +# git, zip, nodejs +# node-webkit (https://github.com/rogerwang/node-webkit), node-webkit-builder (https://github.com/mllrsohn/node-webkit-builder) + +builddir=$(mktemp -d) + +if [[ $2 == "" ]] +then + git clone "$snapsource" $builddir + cd $builddir/ + git checkout mobileapp +else + mv "$2" $builddir + cd $builddir/ +fi + +nwbuild -p "$1" . + +mv build/* $scriptdir/ diff --git a/mobile.sh b/mobile.sh new file mode 100755 index 0000000..6ec8a0e --- /dev/null +++ b/mobile.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +if [[ "$snapsource" == "" ]] +then + export snapsource="https://github.com/Gubolin/snap.git" +fi + +if [[ $1 = "" ]] +then + echo "Usage: mobile.sh PLATFORM [BUILDSOURCE]" + exit 0 +fi + +scriptdir=$(readlink -e ".") + +# Requirements: +# git, nodejs, android SDK / other platform(s) +# cordova (https://cordova.apache.org/) + +if [[ $2 != "" ]] +then + buildsource=$(readlink -e "$2") +fi + +builddir=$(mktemp -d) + +cordova create $builddir edu.berkeley.snap "Snap\!" +cd $builddir +rm -rf www config.xml + +if [[ $2 == "" ]] +then + git clone "$snapsource" www + cd www/ + git checkout mobileapp +else + mv "$buildsource" www + cd www +fi + +# add mobile-specific library; it's made available at runtime +sed -i '/link rel="shortcut icon"/a\ + <script type="text/javascript" src="cordova.js"></script>' snap.html + +# add everything needed and build for $device +cordova platform add "$1" +cordova plugin add org.apache.cordova.plugin.softkeyboard +cordova plugin add org.apache.cordova.vibration +cordova plugin add org.apache.cordova.device-motion +cordova plugin add org.apache.cordova.device-orientation +cordova plugin add org.apache.cordova.geolocation +cordova plugin add de.appplant.cordova.plugin.local-notification + +if [[ $1 == "android" ]] +then + # Remove default icons + cd "$builddir/platforms/android" + find -name '*.png' | xargs rm +fi + +cordova build "$1" + +cd $builddir +# TODO other platforms +find -name '*.apk' | xargs -I {} mv {} $scriptdir @@ -10790,6 +10790,11 @@ WorldMorph.prototype.edit = function (aStringOrTextMorph) { this.virtualKeyboard.style.top = this.cursor.top() + pos.y + "px"; this.virtualKeyboard.style.left = this.cursor.left() + pos.x + "px"; this.virtualKeyboard.focus(); + if (cordova) { + if (cordova.plugins.SoftKeyboard) { // Android only + cordova.plugins.SoftKeyboard.show(); // Issue #81 + } + } } if (MorphicPreferences.useSliderForInput) { @@ -415,6 +415,11 @@ SpriteMorph.prototype.initBlocks = function () { spec: 'go back %n layers', defaults: [1] }, + doNotify: { + type: 'command', + category: 'looks', + spec: 'notification with title %s and content %s' + }, doScreenshot: { type: 'command', category: 'looks', @@ -933,6 +938,31 @@ SpriteMorph.prototype.initBlocks = function () { category: 'sensing', spec: 'current %dates' }, + doVibrate: { + type: 'command', + category: 'sensing', + spec: 'vibrate %n seconds' + }, + reportCompassHeading: { + type: 'reporter', + category: 'sensing', + spec: 'current compass heading' + }, + reportAccelerationX: { + type: 'reporter', + category: 'sensing', + spec: 'current acceleration along the x axes' + }, + reportAccelerationY: { + type: 'reporter', + category: 'sensing', + spec: 'current acceleration along the y axes' + }, + reportAccelerationZ: { + type: 'reporter', + category: 'sensing', + spec: 'current acceleration along the z axes' + }, reportLanguage: { type: 'reporter', category: 'sensing', @@ -1818,6 +1848,8 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push('-'); blocks.push(block('comeToFront')); blocks.push(block('goBack')); + blocks.push('-'); + blocks.push(block('doNotify')); // for debugging: /////////////// @@ -1988,6 +2020,14 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push('-'); blocks.push(block('reportLanguage')); blocks.push(block('reportLocation')); + blocks.push('-'); + blocks.push(block('doVibrate')); + blocks.push('-'); + blocks.push(block('reportCompassHeading')); + blocks.push('-'); + blocks.push(block('reportAccelerationX')); + blocks.push(block('reportAccelerationY')); + blocks.push(block('reportAccelerationZ')); // for debugging: /////////////// @@ -4434,6 +4474,8 @@ StageMorph.prototype.init = function (globals) { this.paletteCache = {}; // not to be serialized (!) this.lastAnswer = ''; // last user input, do not persist this.activeSounds = []; // do not persist + this.acceleration = null; // do not persist + this.compassHeading = null; // do not persist this.streamingCamera = false; this.lastCameraCanvas = null; this.lastCameraMotion = new Point(0, 0); @@ -5082,6 +5124,8 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push('-'); blocks.push(block('show')); blocks.push(block('hide')); + blocks.push('-'); + blocks.push(block('doNotify')); // for debugging: /////////////// @@ -5230,6 +5274,14 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push('-'); blocks.push(block('reportLanguage')); blocks.push(block('reportLocation')); + blocks.push('-'); + blocks.push(block('doVibrate')); + blocks.push('-'); + blocks.push(block('reportCompassHeading')); + blocks.push('-'); + blocks.push(block('reportAccelerationX')); + blocks.push(block('reportAccelerationY')); + blocks.push(block('reportAccelerationZ')); // for debugging: /////////////// diff --git a/package.json b/package.json new file mode 100644 index 0000000..678a69c --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "Snap!", + "version": "4.0", + "main": "snap.html", + "window": { + "toolbar": false + } +} @@ -21,16 +21,26 @@ <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(); @@ -2274,6 +2274,20 @@ Process.prototype.reportTextSplit = function (string, delimiter) { return new List(str.split(del)); }; +// Process notification operations + +Process.prototype.doNotify = function (title, content) { + // TODO webkitNotification + if (window.plugin) { + if (window.plugin.notification) { + window.plugin.notification.local.add({ + message: content, + title: title + }); + } + } +}; + // Process debugging Process.prototype.alert = function (data) { @@ -2794,6 +2808,114 @@ Process.prototype.reportDate = function (datefn) { return result; }; +Process.prototype.getCompassHeading = function () { + var stage = this.homeContext.receiver.parentThatIsA(StageMorph); + + stage.compassHeading = null; + if (navigator.compass) { + navigator.compass.getCurrentHeading( + function (result) { + stage.compassHeading = result; + }, + function () { + stage.compassHeading = + {'magneticHeading': 0, 'trueHeading': 0, + 'headingAccuracy': 0, 'timestamp': 0}; + } + ); + } else { + stage.compassHeading = + {'magneticHeading': 0, 'trueHeading': 0, + 'headingAccuracy': 0, 'timestamp': 0}; + } +}; + +Process.prototype.reportCompassHeading = function () { + var stage = this.homeContext.receiver.parentThatIsA(StageMorph); + + if (this.context.wait) { + if (stage.compassHeading !== null) { + return stage.compassHeading.magneticHeading; + } + } else { + this.context.wait = true; + this.getCompassHeading(); + } + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.getAcceleration = function () { + var stage = this.homeContext.receiver.parentThatIsA(StageMorph); + + stage.acceleration = null; + if (navigator.accelerometer) { + navigator.accelerometer.getCurrentAcceleration( + function (result) { + stage.acceleration = result; + }, + function () { + stage.acceleration = + {'timestamp': 0, 'z': 0, 'y': 0, 'x': 0}; + } + ); + } else { + stage.acceleration = + {'timestamp': 0, 'z': 0, 'y': 0, 'x': 0}; + } +}; + +Process.prototype.reportAccelerationX = function () { + var stage = this.homeContext.receiver.parentThatIsA(StageMorph); + + if (this.context.wait) { + if (stage.acceleration !== null) { + return stage.acceleration.x; + } + } else { + this.context.wait = true; + this.getAcceleration(); + } + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.reportAccelerationY = function () { + var stage = this.homeContext.receiver.parentThatIsA(StageMorph); + + if (this.context.wait) { + if (stage.acceleration !== null) { + return stage.acceleration.y; + } + } else { + this.context.wait = true; + this.getAcceleration(); + } + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.reportAccelerationZ = function () { + var stage = this.homeContext.receiver.parentThatIsA(StageMorph); + + if (this.context.wait) { + if (stage.acceleration !== null) { + return stage.acceleration.z; + } + } else { + this.context.wait = true; + this.getAcceleration(); + } + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.doVibrate = function (seconds) { + if ("vibrate" in navigator) { + window.navigator.vibrate(seconds * 1000); + } +}; + // Process code mapping /* @@ -3203,8 +3325,6 @@ Process.prototype.reportFrameCount = function () { activeAudio audio buffer for interpolated operations, don't persist activeNote audio oscillator for interpolated ops, don't persist activeStream video element for camera streaming, don't persist - isLambda marker for return ops - isImplicitLambda marker for return ops isCustomBlock marker for return ops emptySlots caches the number of empty slots for reification tag string or number to optionally identify the Context, @@ -3232,8 +3352,6 @@ function Context( this.activeAudio = null; this.activeNote = null; this.activeStream = null; - this.isLambda = false; // marks the end of a lambda - this.isImplicitLambda = false; // marks the end of a C-shaped slot this.isCustomBlock = false; // marks the end of a custom block's stack this.emptySlots = 0; // used for block reification this.tag = null; // lexical catch-tag for custom blocks |
