summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbinary.js1099
-rwxr-xr-xbinary.sh91
-rw-r--r--blocks.js38
-rw-r--r--config.xml15
-rwxr-xr-xdesktop.sh32
-rw-r--r--gui.js3
-rwxr-xr-xhistory.txt6
-rwxr-xr-xmobile.sh64
-rw-r--r--morphic.js10
-rw-r--r--objects.js112
-rw-r--r--package.json8
-rwxr-xr-xsnap.html4
-rw-r--r--threads.js413
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
diff --git a/blocks.js b/blocks.js
index 2e297ab..9aec7f4 100644
--- a/blocks.js
+++ b/blocks.js
@@ -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/
diff --git a/gui.js b/gui.js
index 6af9137..4c9ee00 100644
--- a/gui.js
+++ b/gui.js
@@ -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
diff --git a/morphic.js b/morphic.js
index 2bbdd4e..efdf139 100644
--- a/morphic.js
+++ b/morphic.js
@@ -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) {
diff --git a/objects.js b/objects.js
index a15aec4..cf7a191 100644
--- a/objects.js
+++ b/objects.js
@@ -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
+ }
+}
diff --git a/snap.html b/snap.html
index 904aba8..d197b08 100755
--- a/snap.html
+++ b/snap.html
@@ -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() {
diff --git a/threads.js b/threads.js
index eda8fc7..8fbd587 100644
--- a/threads.js
+++ b/threads.js
@@ -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
/*