diff options
| -rwxr-xr-x | binary.js | 1099 | ||||
| -rwxr-xr-x | binary.sh | 91 | ||||
| -rw-r--r-- | blocks.js | 38 | ||||
| -rw-r--r-- | config.xml | 15 | ||||
| -rwxr-xr-x | desktop.sh | 32 | ||||
| -rw-r--r-- | gui.js | 3 | ||||
| -rwxr-xr-x | history.txt | 6 | ||||
| -rwxr-xr-x | mobile.sh | 64 | ||||
| -rw-r--r-- | morphic.js | 10 | ||||
| -rw-r--r-- | objects.js | 112 | ||||
| -rw-r--r-- | package.json | 8 | ||||
| -rwxr-xr-x | snap.html | 4 | ||||
| -rw-r--r-- | threads.js | 413 |
13 files changed, 1886 insertions, 9 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..6f267d0 --- /dev/null +++ b/binary.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +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 "https://github.com/Gubolin/snap.git" $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 + +# delete everything that is not html/js/logo/config +find . \( \! -name '*.js' \! -name 'snap.html' \! -name 'snap_logo_sm.png' \! -name 'config.xml' \! -name 'package.json' -type f \) -print0 | xargs -0 rm +find . -type d -empty -print0 | xargs -0 rm -r + +# 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 @@ -155,7 +155,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2014-July-30'; +modules.blocks = '2014-August-13'; var SyntaxElementMorph; @@ -396,10 +396,11 @@ SyntaxElementMorph.prototype.allInputs = function () { SyntaxElementMorph.prototype.allEmptySlots = function () { /* answer empty input slots of all children excluding myself, - but omit those in nested rings (lambdas) + but omit those in nested rings (lambdas) and JS-Function primitives */ var empty = []; - if (!(this instanceof RingMorph)) { + if (!(this instanceof RingMorph) && + (this.selector !== 'reportJSFunction')) { this.children.forEach(function (morph) { if (morph.isEmptySlot && morph.isEmptySlot()) { empty.push(morph); @@ -843,6 +844,37 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { ); part.setContents(['date']); break; + case '%fileOrDir': + part = new InputSlotMorph( + null, // text + false, // non-numeric + { + 'file': ['file'], + 'directory': ['directory'] + }, + true // read-only + ); + part.setContents(['file']); + break; + case '%locations': + part = new InputSlotMorph( + null, // text + false, // non-numeric + { + 'all' : ['all'], + 'country' : ['country'], + 'state' : ['state'], + 'state district' : ['state district'], + 'suburb' : ['suburb'], + 'city' : ['city'], + 'road' : ['road'], + 'house number' : ['house number'], + 'licence': ['licence'] + }, + true // read-only + ); + part.setContents(['all']); + break; case '%delim': part = new InputSlotMorph( null, // text 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..8b0e21c --- /dev/null +++ b/desktop.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +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 https://github.com/Gubolin/snap.git $builddir + cd $builddir/ + git checkout mobileapp +else + mv "$2" $builddir + cd $builddir/ +fi + +# remove mobile config +rm config.xml + +nwbuild -p "$1" . + +mv build/* $scriptdir/ @@ -185,7 +185,7 @@ IDE_Morph.prototype.init = function (isAutoFill) { MorphicPreferences.globalFontFamily = 'Helvetica, Arial'; // restore saved user preferences - this.userLanguage = null; // user language preference for startup + this.userLanguage = null; this.applySavedSettings(); // additional properties: @@ -3417,6 +3417,7 @@ IDE_Morph.prototype.setLanguage = function (lang, callback) { if (lang === 'en') { return this.reflectLanguage('en', callback); } + myself.userLanguage = lang; translation = document.createElement('script'); translation.id = 'language'; translation.onload = function () { diff --git a/history.txt b/history.txt index 0a7dfe6..d18452e 100755 --- a/history.txt +++ b/history.txt @@ -2256,3 +2256,9 @@ ______ * Objects: propagate HIDE and SHOW to nested sprite parts * GUI: propagate DELETE to nested sprite parts * Blocks, Threads: export script pic with result bubble (shift-context-menu of reporter scripts) +* updated Portuguese translation, thanks, Manuel! + +140813 +------ +* Threads, Blocks: enable Zombiefication of JS-Functions +* Morphic: Fix #563 (Paste into Chrome), thanks, @Muon, for the hint! diff --git a/mobile.sh b/mobile.sh new file mode 100755 index 0000000..b0bcc5d --- /dev/null +++ b/mobile.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +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 https://github.com/Gubolin/snap.git www + cd www/ + git checkout mobileapp +else + mv "$buildsource" www + cd www +fi + +# remove configs for desktop, it is not needed +rm package.json + +# 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 +cordova plugin add org.apache.cordova.file + +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 @@ -1035,7 +1035,7 @@ /*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio, FileList, getBlurredShadowSupport*/ -var morphicVersion = '2014-July-29'; +var morphicVersion = '2014-August-13'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug @@ -10321,7 +10321,8 @@ WorldMorph.prototype.initEventListeners = function () { } event.preventDefault(); } - if (event.ctrlKey || event.metaKey) { + if ((event.ctrlKey || event.metaKey) && + (event.keyIdentifier !== 'U+0056')) { // allow pasting-in event.preventDefault(); } }, @@ -10777,6 +10778,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', @@ -905,6 +910,71 @@ SpriteMorph.prototype.initBlocks = function () { category: 'sensing', spec: 'current %dates' }, + reportFileOrDirectoryContent: { + type: 'reporter', + category: 'sensing', + spec: 'content of the %fileOrDir %s' + }, + doSetFileContent: { + type: 'command', + category: 'sensing', + spec: 'write %s to the file %s' + }, + doCreateFileOrDirectory: { + type: 'command', + category: 'sensing', + spec: 'create %fileOrDir %s' + }, + reportFileOrDirectoryExists: { + type: 'predicate', + category: 'sensing', + spec: '%fileOrDir %s exists' + }, + doDeleteFileOrDirectory: { + type: 'command', + category: 'sensing', + spec: 'delete the %fileOrDir %s' + }, + reportHomeDirectory: { + type: 'reporter', + category: 'sensing', + spec: 'home directory' + }, + 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', + spec: 'language' + }, + reportLocation: { + type: 'reporter', + category: 'sensing', + spec: 'location %locations' + }, // Operators reifyScript: { @@ -1754,6 +1824,8 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push('-'); blocks.push(block('comeToFront')); blocks.push(block('goBack')); + blocks.push('-'); + blocks.push(block('doNotify')); // for debugging: /////////////// @@ -1915,6 +1987,24 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push(block('doSetFastTracking')); blocks.push('-'); blocks.push(block('reportDate')); + blocks.push('-'); + blocks.push(block('reportFileOrDirectoryContent')); + blocks.push(block('doSetFileContent')); + blocks.push(block('doCreateFileOrDirectory')); + blocks.push(block('reportFileOrDirectoryExists')); + blocks.push(block('doDeleteFileOrDirectory')); + blocks.push(block('reportHomeDirectory')); + 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: /////////////// @@ -4289,6 +4379,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); @@ -4891,6 +4983,8 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push('-'); blocks.push(block('show')); blocks.push(block('hide')); + blocks.push('-'); + blocks.push(block('doNotify')); // for debugging: /////////////// @@ -5030,6 +5124,24 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push(block('doSetFastTracking')); blocks.push('-'); blocks.push(block('reportDate')); + blocks.push('-'); + blocks.push(block('reportFileOrDirectoryContent')); + blocks.push(block('doSetFileContent')); + blocks.push(block('doCreateFileOrDirectory')); + blocks.push(block('reportFileOrDirectoryExists')); + blocks.push(block('doDeleteFileOrDirectory')); + blocks.push(block('reportHomeDirectory')); + 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 + } +} @@ -18,12 +18,14 @@ <script type="text/javascript" src="locale.js"></script> <script type="text/javascript" src="cloud.js"></script> <script type="text/javascript" src="sha512.js"></script> + <script type="text/javascript"> var world; window.onload = function () { world = new WorldMorph(document.getElementById('world')); world.worldCanvas.focus(); - new IDE_Morph().openIn(world); + var ide = new IDE_Morph() + ide.openIn(world); setInterval(loop, 1); }; function loop() { @@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/ // Global stuff //////////////////////////////////////////////////////// -modules.threads = '2014-July-30'; +modules.threads = '2014-August-13'; var ThreadManager; var Process; @@ -774,7 +774,7 @@ Process.prototype.evaluate = function ( if (!context) {return null; } if (context instanceof Function) { return context.apply( - this.homeContext.receiver, + this.blockReceiver(), args.asArray().concat([this]) ); } @@ -2189,6 +2189,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) { @@ -2601,6 +2615,78 @@ Process.prototype.reportTimer = function () { return 0; }; +Process.prototype.reportLanguage = function () { + var ide = this.homeContext.receiver.parentThatIsA(IDE_Morph); + if (ide) { + var lang = ide.userLanguage; + if (lang) { + return lang; + } + } + return 'en'; +}; + +Process.prototype.reportLocation = function (name) { + var myself = this; + + if (!myself.context.jsonpScript) { + if (!navigator.geolocation) { + myself.handleError({name: 'Location', + message: 'navigator.geolocation is not supported'}); + } + window.OSM_JSONP_Callback = function (data) { + myself.context.json = data; + window.OSM_JSONP_Callback = undefined; + }; + myself.context.jsonpScript = document.createElement('script'); + + navigator.geolocation.getCurrentPosition( + function (pos) { + var crd = pos.coords; + + myself.context.jsonpScript.src = + 'https://nominatim.openstreetmap.org/reverse' + + '?format=json&json_callback=OSM_JSONP_Callback' + + '&zoom=18&addressdetails=1' + + '&lat=' + + crd.latitude + + '&lon=' + + crd.longitude; + document.getElementsByTagName('HEAD')[0] + .appendChild(myself.context.jsonpScript); + }, + function (err) { + console.log(err); + myself.handleError({name: 'Location', message: err}); + }, + { enableHightAccuracy: true, timeout: 30000, maximumAge: 0} + ); + } else { + if (myself.context.json) { + switch (myself.inputOption(name)) { + case 'all': + return myself.context.json.display_name; + case 'state district': + return myself.context.json.address.state_district; + case 'house number': + return myself.context.json.address.house_number; + case 'licence': + return myself.context.json.licence; + case 'country': + case 'state': + case 'suburb': + case 'city': + case 'road': + return myself.context. + json.address[myself.inputOption(name)]; + } + } + } + + this.pushContext('doYield'); + this.pushContext(); +}; + // Process Dates and times in Snap // Map block options to built-in functions var dateMap = { @@ -2630,6 +2716,329 @@ Process.prototype.reportDate = function (datefn) { return result; }; +// Process File access +// http://www.html5rocks.com/en/tutorials/file/filesystem/ +Process.prototype.fileErrorHandler = function (e) { + var msg = ''; + + switch (e.code) { + case FileError.QUOTA_EXCEEDED_ERR: + msg = localize('no space left'); + break; + case FileError.NOT_FOUND_ERR: + msg = localize('not found'); + break; + case FileError.SECURITY_ERR: + msg = localize('access denied'); + break; + case FileError.INVALID_MODIFICATION_ERR: + msg = 'INVALID_MODIFICATION_ERR'; + break; + case FileError.INVALID_STATE_ERR: + msg = 'INVALID_STATE_ERR'; + break; + default: + msg = localize('unknown error'); + break; + } + + this.handleError(msg); +}; + +Process.prototype.reportFileOrDirectoryContent = function (typeInput, filepath) { + var myself = this; + var type = myself.inputOption(typeInput); + + if (myself.context.result === null) { + myself.context.result = false; + + if (window.resolveLocalFileSystemURL) { + window.resolveLocalFileSystemURL(filepath, + function (entry) { + if (type === 'file') { + entry.file( + function (file) { + var reader = new FileReader(); + + reader.onloadend = function (e) { + myself.context.result = this.result; + }; + + reader.readAsText(file); + }, + myself.fileErrorHandler + ); + } else { + var dirReader = entry.createReader(); + dirReader.readEntries( + function (results) { + var items = []; + results.forEach( + function (item) { + items.push(item.name); + } + ); + myself.context.result = new List(items); + }, + myself.fileErrorHandler + ); + } + }, + myself.fileErrorHandler + ); + } + } else { + if (myself.context.result !== false) { + return myself.context.result; + } + } + + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.doSetFileContent = function (content, filepath) { + var dirpath = filepath.substring(0, + filepath.lastIndexOf('/')); + var filename = filepath.substring( + filepath.lastIndexOf('/') + 1); + + if (window.resolveLocalFileSystemURL) { + window.resolveLocalFileSystemURL(dirpath, + function (dirEntry) { + dirEntry.getFile(filename, + {create: true, exclusive: false}, + function (fileEntry) { + fileEntry.createWriter( + function (fileWriter) { + var blob; + + try { + blob = new Blob([content], + {type: 'text/plain'}); + // No `new Blob` for Android + } catch (e) { + window.BlobBuilder = + window.BlobBuilder || + window.WebKitBlobBuilder || + window.MozBlobBuilder || + window.MSBlobBuilder; + + if (e.name == 'TypeError' + && window.BlobBuilder) { + var bb = new BlobBuilder(); + bb.append(content); + blob = bb.getBlob('text/plain'); + } + } + + fileWriter.write(blob); + }, + myself.fileErrorHandler + ); + }, + myself.fileErrorHandler + ); + }, + myself.fileErrorHandler + ); + } +}; + +Process.prototype.createFileOrDirectory = function (typeInput, filepath) { + var myself = this; + var type = myself.inputOption(typeInput); + + function createDir(rootDirEntry, folders) { + rootDirEntry.getDirectory(folders[0], {create: true}, + function (dirEntry) { + if (folders.length === 1) { + if (type === 'file') { + dirEntry.getFile(folders.slice(1), + {create: true, exclusive: false}, + function (fileEntry) { + return; + }, + myself.fileErrorHandler + ); + + return; + } + } + + if (folders.length) { + createDir(dirEntry, folders.slice(1)); + } + }, + myself.fileErrorHandler + ); + } + + window.requestFileSystem(window.PERSISTENT, 0, + function (fileSystem) { + createDir(fileSystem.root, path.split('/')); + }, + myself.fileErrorHandler + ); +}; + +Process.prototype.reportFileOrDirectoryExists = function (typeInput, filepath) { + var myself = this; + var type = myself.inputOption(typeInput); + + if (myself.context.result === null) { + myself.context.result = 'wait'; + + if (window.resolveLocalFileSystemURL) { + window.resolveLocalFileSystemURL(filepath, + function (entry) { + if (type === 'file') { + myself.context.result = !entry.isDirectory; + } else { + myself.context.result = entry.isDirectory; + } + }, + myself.fileErrorHandler + ); + } + } else { + if (myself.context.result !== 'wait') { + return myself.context.result; + } + } + + this.pushContext('doYield'); + this.pushContext(); +}; + +Process.prototype.doDeleteFileOrDirectory = function (typeInput, filepath) { + if (window.resolveLocalFileSystemURL) { + window.resolveLocalFileSystemURL(filepath, + function (entry) { + entry.remove(); + }, + myself.fileErrorHandler + ); + } +}; + +Process.prototype.reportHomeDirectory = function () { + if (cordova) { + if (cordova.file) { + return cordova.file.dataDirectory; + } + } + return ''; +}; + +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 /* |
