summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cloud.js616
-rw-r--r--github.js273
-rw-r--r--gui.js721
-rwxr-xr-xsnap.html3
4 files changed, 1462 insertions, 151 deletions
diff --git a/cloud.js b/cloud.js
index 02cc14f..54189d8 100644
--- a/cloud.js
+++ b/cloud.js
@@ -1,11 +1,12 @@
/*
-cloud.js
- a GitHub backend API for SNAP!
+ cloud.js
- written by Gubolin, based on cloud.js by Jens Mönig
+ a backend API for SNAP!
- Copyright (C) 2014 by Jens Mönig, Gubolin
+ written by Jens Mönig
+
+ Copyright (C) 2014 by Jens Mönig
This file is part of Snap!.
@@ -29,73 +30,253 @@ cloud.js
/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop,
localize*/
-modules.cloud = '2014-July-29';
+modules.cloud = '2014-May-26';
// Global stuff
var Cloud;
-var SnapCloud = new Cloud();
+var SnapCloud = new Cloud(
+ 'https://snapcloud.miosoft.com/miocon/app/login?_app=SnapCloud'
+);
// Cloud /////////////////////////////////////////////////////////////
function Cloud(url) {
- this.gh = null;
this.username = null;
- this.password = null; // TODO saved as plain text
+ this.password = null; // hex_sha512 hashed
+ this.url = url;
this.session = null;
this.api = {};
}
Cloud.prototype.clear = function () {
- this.gh = null;
this.username = null;
this.password = null;
this.session = null;
this.api = {};
};
+Cloud.prototype.hasProtocol = function () {
+ return this.url.toLowerCase().indexOf('http') === 0;
+};
+
// Cloud: Snap! API
-Cloud.prototype.getProject = function (
- dict,
+Cloud.prototype.signup = function (
+ username,
+ email,
callBack,
errorCall
) {
- var myself = this;
- var projectName = dict.projectname;
- var userName = dict.username;
+ // both callBack and errorCall are two-argument functions
+ var request = new XMLHttpRequest(),
+ myself = this;
+ try {
+ request.open(
+ "GET",
+ (this.hasProtocol() ? '' : 'http://')
+ + this.url + 'SignUp'
+ + '&Username='
+ + encodeURIComponent(username)
+ + '&Email='
+ + encodeURIComponent(email),
+ true
+ );
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
+ );
+ request.withCredentials = true;
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ if (request.responseText) {
+ if (request.responseText.indexOf('ERROR') === 0) {
+ errorCall.call(
+ this,
+ request.responseText,
+ 'Signup'
+ );
+ } else {
+ callBack.call(
+ null,
+ request.responseText,
+ 'Signup'
+ );
+ }
+ } else {
+ errorCall.call(
+ null,
+ myself.url + 'SignUp',
+ localize('could not connect to:')
+ );
+ }
+ }
+ };
+ request.send(null);
+ } catch (err) {
+ errorCall.call(this, err.toString(), 'Snap!Cloud');
+ }
+};
- if (myself.gh !== null) {
- var repo = myself.gh.getRepo(userName, projectName);
- var branch = repo.getBranch(); // master (default)
- var media, pdata;
- var br;
+Cloud.prototype.getPublicProject = function (
+ id,
+ callBack,
+ errorCall
+) {
+ // id is Username=username&projectName=projectname,
+ // where the values are url-component encoded
+ // callBack is a single argument function, errorCall take two args
+ var request = new XMLHttpRequest(),
+ responseList,
+ myself = this;
+ try {
+ request.open(
+ "GET",
+ (this.hasProtocol() ? '' : 'http://')
+ + this.url + 'Public'
+ + '&'
+ + id,
+ true
+ );
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
+ );
+ request.withCredentials = true;
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ if (request.responseText) {
+ if (request.responseText.indexOf('ERROR') === 0) {
+ errorCall.call(
+ this,
+ request.responseText
+ );
+ } else {
+ responseList = myself.parseResponse(
+ request.responseText
+ );
+ callBack.call(
+ null,
+ responseList[0].SourceCode
+ );
+ }
+ } else {
+ errorCall.call(
+ null,
+ myself.url + 'Public',
+ localize('could not connect to:')
+ );
+ }
+ }
+ };
+ request.send(null);
+ } catch (err) {
+ errorCall.call(this, err.toString(), 'Snap!Cloud');
+ }
+};
- branch.read('snap.xml', false).then(
- function (sourceContent) {
- branch.read('media.xml', false).then( // true for binary
- function (mediaContent) {
+Cloud.prototype.resetPassword = function (
+ username,
+ callBack,
+ errorCall
+) {
+ // both callBack and errorCall are two-argument functions
+ var request = new XMLHttpRequest(),
+ myself = this;
+ try {
+ request.open(
+ "GET",
+ (this.hasProtocol() ? '' : 'http://')
+ + this.url + 'ResetPW'
+ + '&Username='
+ + encodeURIComponent(username),
+ true
+ );
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
+ );
+ request.withCredentials = true;
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ if (request.responseText) {
+ if (request.responseText.indexOf('ERROR') === 0) {
+ errorCall.call(
+ this,
+ request.responseText,
+ 'Reset Password'
+ );
+ } else {
callBack.call(
null,
- sourceContent.content,
- mediaContent.content
+ request.responseText,
+ 'Reset Password'
);
- },
- function (error) {
- errorCall.call(this, error, 'Github');
}
- );
- },
- function (error) {
- errorCall.call(this, error, 'Github');
+ } else {
+ errorCall.call(
+ null,
+ myself.url + 'ResetPW',
+ localize('could not connect to:')
+ );
+ }
}
+ };
+ request.send(null);
+ } catch (err) {
+ errorCall.call(this, err.toString(), 'Snap!Cloud');
+ }
+};
+
+Cloud.prototype.connect = function (
+ callBack,
+ errorCall
+) {
+ // both callBack and errorCall are two-argument functions
+ var request = new XMLHttpRequest(),
+ myself = this;
+ try {
+ request.open(
+ "GET",
+ (this.hasProtocol() ? '' : 'http://') + this.url,
+ true
+ );
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
);
- } else {
- errorCall.call(null, 'Please login', 'Github');
+ request.withCredentials = true;
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ if (request.responseText) {
+ myself.api = myself.parseAPI(request.responseText);
+ myself.session = request.getResponseHeader('MioCracker')
+ .split(';')[0];
+ if (myself.api.login) {
+ callBack.call(null, myself.api, 'Snap!Cloud');
+ } else {
+ errorCall.call(
+ null,
+ 'connection failed'
+ );
+ }
+ } else {
+ errorCall.call(
+ null,
+ myself.url,
+ localize('could not connect to:')
+ );
+ }
+ }
+ };
+ request.send(null);
+ } catch (err) {
+ errorCall.call(this, err.toString(), 'Snap!Cloud');
}
};
+
Cloud.prototype.login = function (
username,
password,
@@ -103,22 +284,61 @@ Cloud.prototype.login = function (
errorCall
) {
var myself = this;
+ this.connect(
+ function () {
+ myself.rawLogin(username, password, callBack, errorCall);
+ myself.disconnect();
+ },
+ errorCall
+ );
+};
- myself.gh = new Octokit({
- username: username,
- password: password
- });
-
- myself.gh.getUser().getInfo().then(
- function(info) {
- myself.username = username;
- myself.password = password;
-
- callBack.call(myself);
+Cloud.prototype.rawLogin = function (
+ username,
+ password,
+ callBack,
+ errorCall
+) {
+ // both callBack and errorCall are two-argument functions
+ var myself = this,
+ pwHash = hex_sha512("miosoft%20miocon,"
+ + this.session.split('=')[1] + ","
+ + encodeURIComponent(username.toLowerCase()) + ","
+ + password // alreadey hex_sha512 hashed
+ );
+ this.callService(
+ 'login',
+ function (response, url) {
+ if (myself.api.logout) {
+ myself.username = username;
+ myself.password = password;
+ callBack.call(null, response, url);
+ } else {
+ errorCall.call(
+ null,
+ 'Service catalog is not available,\nplease retry',
+ 'Connection Error:'
+ );
+ }
},
- function (error) {
- errorCall.call(this, error, 'Github');
- }
+ errorCall,
+ [username, pwHash]
+ );
+};
+
+Cloud.prototype.reconnect = function (
+ callBack,
+ errorCall
+) {
+ if (!(this.username && this.password)) {
+ this.message('You are not logged in');
+ return;
+ }
+ this.login(
+ this.username,
+ this.password,
+ callBack,
+ errorCall
);
};
@@ -126,7 +346,6 @@ Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
var myself = this,
pdata,
media;
- var repoName = ide.projectName.replace(/[^\w-]/g, ''); // FIXME validation of project name
ide.serializer.isCollectingMedia = true;
pdata = ide.serializer.serialize(ide.stage);
@@ -153,94 +372,215 @@ Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
ide.serializer.isCollectingMedia = false;
ide.serializer.flushMedia();
- myself.getProjectList(
- function (projects) {
- var exists = false;
-
- projects.forEach(function (project) {
- if (project.ProjectName.indexOf(repoName) > -1) {
- exists = true;
- return;
- }
- });
-
- if (exists === false){
- myself.gh.getUser().createRepo(repoName, { // these should be discussed
- 'description': 'Snap! Project',
- 'has_wiki': 'false',
- 'has_downloads': 'false',
- 'auto_init': true,
- 'license_template': 'mit' // discuss
- }).then(
- function () {},
- function (error) {
- errorCall.call(this, error, 'Github');
- }
- );
- }
-
- if (myself.gh !== null) {
- var repo = myself.gh.getRepo(myself.username, ide.projectName);
- var branch = repo.getBranch(); // master (default)
- var message = ''; // FIXME optional: specify message
-
- var contents = {
- 'snap.xml': pdata,
- 'media.xml': media // may be binary
- };
-
- branch.writeMany(contents, message).then(
- function () {
- callBack.call()
- },
- function (error) {
- errorCall.call(this, error, 'Github');
- }
- );
- }
- },
- function (error) {
- errorCall.call(null, error, 'Github');
- }
+ myself.reconnect(
+ function () {
+ myself.callService(
+ 'saveProject',
+ function (response, url) {
+ callBack.call(null, response, url);
+ myself.disconnect();
+ ide.hasChangedMedia = false;
+ },
+ errorCall,
+ [
+ ide.projectName,
+ pdata,
+ media,
+ pdata.length,
+ media ? media.length : 0
+ ]
+ );
+ },
+ errorCall
);
};
Cloud.prototype.getProjectList = function (callBack, errorCall) {
var myself = this;
+ this.reconnect(
+ function () {
+ myself.callService(
+ 'getProjectList',
+ function (response, url) {
+ callBack.call(null, response, url);
+ myself.disconnect();
+ },
+ errorCall
+ );
+ },
+ errorCall
+ );
+};
- if (myself.gh !== null) {
- myself.gh.getUser().getRepos().then(
- function (repos) {
- var snapProjects = [];
+Cloud.prototype.changePassword = function (
+ oldPW,
+ newPW,
+ callBack,
+ errorCall
+) {
+ var myself = this;
+ this.reconnect(
+ function () {
+ myself.callService(
+ 'changePassword',
+ function (response, url) {
+ callBack.call(null, response, url);
+ myself.disconnect();
+ },
+ errorCall,
+ [oldPW, newPW]
+ );
+ },
+ errorCall
+ );
+};
- repos.forEach(function (repo) {
- if (repo.description === 'Snap! Project') { // FIXME nicer detection
- var project;
+Cloud.prototype.logout = function (callBack, errorCall) {
+ this.clear();
+ this.callService(
+ 'logout',
+ callBack,
+ errorCall
+ );
+};
- project = {
- 'ProjectName': repo.name
- };
+Cloud.prototype.disconnect = function () {
+ this.callService(
+ 'logout',
+ nop,
+ nop
+ );
+};
- snapProjects.push(project);
- }
- });
+// Cloud: backend communication
- callBack.call(myself, snapProjects);
- },
- function (error) {
- errorCall.call(this, error, 'Github');
- }
+Cloud.prototype.callURL = function (url, callBack, errorCall) {
+ // both callBack and errorCall are optional two-argument functions
+ var request = new XMLHttpRequest(),
+ myself = this;
+ try {
+ request.open('GET', url, true);
+ request.withCredentials = true;
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
);
- } else {
- errorCall.call(myself, 'Please login', 'Github');
+ request.setRequestHeader('MioCracker', this.session);
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ if (request.responseText) {
+ var responseList = myself.parseResponse(
+ request.responseText
+ );
+ callBack.call(null, responseList, url);
+ } else {
+ errorCall.call(
+ null,
+ url,
+ 'no response from:'
+ );
+ }
+ }
+ };
+ request.send(null);
+ } catch (err) {
+ errorCall.call(this, err.toString(), url);
}
};
-Cloud.prototype.logout = function (callBack) {
- this.clear();
+Cloud.prototype.callService = function (
+ serviceName,
+ callBack,
+ errorCall,
+ args
+) {
+ // both callBack and errorCall are optional two-argument functions
+ var request = new XMLHttpRequest(),
+ service = this.api[serviceName],
+ myself = this,
+ postDict;
+
+ if (!this.session) {
+ errorCall.call(null, 'You are not connected', 'Cloud');
+ return;
+ }
+ if (!service) {
+ errorCall.call(
+ null,
+ 'service ' + serviceName + ' is not available',
+ 'API'
+ );
+ return;
+ }
+ if (args && args.length > 0) {
+ postDict = {};
+ service.parameters.forEach(function (parm, idx) {
+ postDict[parm] = args[idx];
+ });
+ }
+ try {
+ request.open(service.method, service.url, true);
+ request.withCredentials = true;
+ request.setRequestHeader(
+ "Content-Type",
+ "application/x-www-form-urlencoded"
+ );
+ request.setRequestHeader('MioCracker', this.session);
+ request.onreadystatechange = function () {
+ if (request.readyState === 4) {
+ var responseList = [];
+ if (request.responseText &&
+ request.responseText.indexOf('ERROR') === 0) {
+ errorCall.call(
+ this,
+ request.responseText,
+ localize('Service:') + ' ' + localize(serviceName)
+ );
+ return;
+ }
+ if (serviceName === 'login') {
+ myself.api = myself.parseAPI(request.responseText);
+ }
+ responseList = myself.parseResponse(
+ request.responseText
+ );
+ callBack.call(null, responseList, service.url);
+ }
+ };
+ request.send(this.encodeDict(postDict));
+ } catch (err) {
+ errorCall.call(this, err.toString(), service.url);
+ }
};
-// Cloud: backend communication
+// Cloud: payload transformation
+
+Cloud.prototype.parseAPI = function (src) {
+ var api = {},
+ services;
+ services = src.split(" ");
+ services.forEach(function (service) {
+ var entries = service.split("&"),
+ serviceDescription = {},
+ parms;
+ entries.forEach(function (entry) {
+ var pair = entry.split("="),
+ key = decodeURIComponent(pair[0]).toLowerCase(),
+ val = decodeURIComponent(pair[1]);
+ if (key === "service") {
+ api[val] = serviceDescription;
+ } else if (key === "parameters") {
+ parms = val.split(",");
+ if (!(parms.length === 1 && !parms[0])) {
+ serviceDescription.parameters = parms;
+ }
+ } else {
+ serviceDescription[key] = val;
+ }
+ });
+ });
+ return api;
+};
Cloud.prototype.parseResponse = function (src) {
var ans = [],
@@ -248,16 +588,16 @@ Cloud.prototype.parseResponse = function (src) {
if (!src) {return ans; }
lines = src.split(" ");
lines.forEach(function (service) {
- var entries = service.split("&"),
- dict = {};
- entries.forEach(function (entry) {
- var pair = entry.split("="),
- key = decodeURIComponent(pair[0]),
- val = decodeURIComponent(pair[1]);
- dict[key] = val;
- });
- ans.push(dict);
+ var entries = service.split("&"),
+ dict = {};
+ entries.forEach(function (entry) {
+ var pair = entry.split("="),
+ key = decodeURIComponent(pair[0]),
+ val = decodeURIComponent(pair[1]);
+ dict[key] = val;
});
+ ans.push(dict);
+ });
return ans;
};
@@ -266,8 +606,8 @@ Cloud.prototype.parseDict = function (src) {
if (!src) {return dict; }
src.split("&").forEach(function (entry) {
var pair = entry.split("="),
- key = decodeURIComponent(pair[0]),
- val = decodeURIComponent(pair[1]);
+ key = decodeURIComponent(pair[0]),
+ val = decodeURIComponent(pair[1]);
dict[key] = val;
});
return dict;
@@ -279,8 +619,10 @@ Cloud.prototype.encodeDict = function (dict) {
key;
if (!dict) {return null; }
for (key in dict) {
- if (dict.hasOwnProperty(key)) {
- pair = encodeURIComponent(key) + '=' + encodeURIComponent(dict[key]);
+ if (dict.hasOwnProperty(key)) {
+ pair = encodeURIComponent(key)
+ + '='
+ + encodeURIComponent(dict[key]);
if (str.length > 0) {
str += '&';
}
@@ -289,3 +631,9 @@ Cloud.prototype.encodeDict = function (dict) {
}
return str;
};
+
+// Cloud: user messages (to be overridden)
+
+Cloud.prototype.message = function (string) {
+ alert(string);
+};
diff --git a/github.js b/github.js
new file mode 100644
index 0000000..a3b3223
--- /dev/null
+++ b/github.js
@@ -0,0 +1,273 @@
+/*
+
+ github.js
+
+ a GitHubBackend backend API for SNAP!
+
+ written by Gubolin, based on cloud.js by Jens Mönig
+
+ Copyright (C) 2014 by Jens Mönig, Gubolin
+
+ This file is part of Snap!.
+
+ Snap! is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+// Global settings /////////////////////////////////////////////////////
+
+/*global modules, IDE_Morph, SnapSerializer, nop,
+localize*/
+
+modules.github = '2014-July-31';
+
+// Global stuff
+
+var GitHubBackend;
+
+var GitHub = new GitHubBackend();
+
+// GitHubBackend /////////////////////////////////////////////////////////////
+
+function GitHubBackend(url) {
+ this.gh = null;
+ this.username = null;
+ this.password = null; // TODO saved as plain text
+}
+
+GitHubBackend.prototype.clear = function () {
+ this.gh = null;
+ this.username = null;
+ this.password = null;
+};
+
+// GitHubBackend: Snap! API
+
+GitHubBackend.prototype.getProject = function (
+ userName,
+ projectName,
+ callBack,
+ errorCall
+) {
+ var myself = this;
+
+ if (myself.gh === null) {
+ myself.gh = new Octokit();
+ }
+
+ var repo = myself.gh.getRepo(userName, projectName);
+ var branch = repo.getBranch(); // master (default)
+ var media, pdata;
+
+ branch.read('snap.xml', false).then(
+ function (sourceContent) {
+ branch.read('media.xml', false).then( // true for binary
+ function (mediaContent) {
+ callBack.call(
+ null,
+ sourceContent.content,
+ mediaContent.content
+ );
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+};
+
+GitHubBackend.prototype.login = function (
+ username,
+ password,
+ validateData,
+ callBack,
+ errorCall
+) {
+ var myself = this;
+ var me;
+
+ myself.gh = new Octokit({
+ username: username,
+ password: password
+ });
+
+ if (validateData === true) {
+ me = myself.gh.getUser();
+ if (me !== null) {
+ me.getInfo().then(
+ function(info) {
+ myself.username = username;
+ myself.password = password;
+
+ callBack.call(myself);
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ } else {
+ errorCall.call(myself, localize('Something went wrong :('), 'GitHub');
+ }
+ } else {
+ myself.username = username;
+ myself.password = password;
+
+ callBack.call(myself);
+ }
+};
+
+GitHubBackend.prototype.saveProject = function (ide, callBack, errorCall) {
+ var myself = this,
+ pdata,
+ media;
+ var repoName = ide.projectName.replace(/[^\w-]/g, ''); // TODO validation of project name
+
+ ide.serializer.isCollectingMedia = true;
+ pdata = ide.serializer.serialize(ide.stage);
+ media = ide.hasChangedMedia ?
+ ide.serializer.mediaXML(ide.projectName) : null;
+ ide.serializer.isCollectingMedia = false;
+ ide.serializer.flushMedia();
+
+ // check if serialized data can be parsed back again
+ try {
+ ide.serializer.parse(pdata);
+ } catch (err) {
+ ide.showMessage('Serialization of program data failed:\n' + err);
+ throw new Error('Serialization of program data failed:\n' + err);
+ }
+ if (media !== null) {
+ try {
+ ide.serializer.parse(media);
+ } catch (err) {
+ ide.showMessage('Serialization of media failed:\n' + err);
+ throw new Error('Serialization of media failed:\n' + err);
+ }
+ }
+ ide.serializer.isCollectingMedia = false;
+ ide.serializer.flushMedia();
+
+ myself.getProjectList(
+ function (projects) {
+ var exists = false;
+
+ projects.forEach(function (project) {
+ if (project.ProjectName.indexOf(repoName) > -1) {
+ exists = true;
+ return;
+ }
+ });
+
+ if (exists === false){
+ myself.gh.getUser().createRepo(repoName, { // these should be discussed
+ 'description': 'Snap! Project - http://snap.berkeley.edu/snapsource/snap.html#github:Username=' + myself.username + '&projectName=' + repoName,
+ 'has_wiki': 'false',
+ 'has_downloads': 'false',
+ 'auto_init': true,
+ 'license_template': 'mit' // discuss
+ }).then(
+ function () {},
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ }
+
+ if (myself.gh !== null) {
+ var repo = myself.gh.getRepo(myself.username, ide.projectName);
+ var branch = repo.getBranch(); // master (default)
+ var message = ''; // TODO optional: specify message
+
+ var contents = {
+ 'snap.xml': pdata,
+ 'media.xml': media, // may be binary
+ 'README.md': ide.projectNotes
+ };
+
+ branch.writeMany(contents, message).then(
+ function () {
+ callBack.call();
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ }
+ },
+ function (error) {
+ errorCall.call(null, error, 'GitHub');
+ }
+ );
+};
+
+GitHubBackend.prototype.getProjectList = function (callBack, errorCall) {
+ var myself = this;
+
+ if (myself.gh !== null) {
+ myself.gh.getUser().getRepos().then(
+ function (repos) {
+ var snapProjects = [];
+
+ var modCallBack = (function () {
+ var called = 0;
+ return function () {
+ if (++called == repos.length) {
+ callBack.call(myself, snapProjects);
+ }
+ };
+ })();
+
+ repos.forEach(function (repo) {
+ if (repo.description.indexOf('Snap! Project') > -1) { // TODO nicer detection
+ var project, ghrepo, branch;
+
+ ghrepo = myself.gh.getRepo(repo.owner.login, repo.name);
+ branch = ghrepo.getBranch(); // master (default)
+
+ branch.read('README.md', false).then(
+ function (notesContent) {
+ project = {
+ 'ProjectName': repo.name,
+ 'Notes': notesContent.content,
+ 'Updated': repo.updated_at.replace(/T/, ' ').replace(/Z/, '') // TODO this could be better
+ };
+
+ snapProjects.push(project);
+ modCallBack();
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ } else {
+ modCallBack();
+ }
+ });
+ },
+ function (error) {
+ errorCall.call(this, error, 'GitHub');
+ }
+ );
+ } else {
+ errorCall.call(myself, localize('Please login'), 'GitHub');
+ }
+};
+
+GitHubBackend.prototype.logout = function (callBack) {
+ this.clear();
+};
diff --git a/gui.js b/gui.js
index a4e1dd5..cbc3353 100644
--- a/gui.js
+++ b/gui.js
@@ -63,13 +63,13 @@ Costume, CostumeEditorMorph, MorphicPreferences, touchScreenSettings,
standardSettings, Sound, BlockMorph, ToggleMorph, InputSlotDialogMorph,
ScriptsMorph, isNil, SymbolMorph, BlockExportDialogMorph,
BlockImportDialogMorph, SnapTranslator, localize, List, InputSlotMorph,
-SnapCloud, Uint8Array, HandleMorph, SVG_Costume, fontHeight, hex_sha512,
+SnapCloud, GitHub, Uint8Array, HandleMorph, SVG_Costume, fontHeight, hex_sha512,
sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio,
SpeechBubbleMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.gui = '2014-July-30';
+modules.gui = '2014-July-31';
// Declarations
@@ -240,6 +240,7 @@ IDE_Morph.prototype.openIn = function (world) {
// get persistent user data, if any
if (localStorage) {
usr = localStorage['-snap-user'];
+ ghusr = localStorage['-snap-ghuser'];
if (usr) {
usr = SnapCloud.parseResponse(usr)[0];
if (usr) {
@@ -247,6 +248,13 @@ IDE_Morph.prototype.openIn = function (world) {
function() {}, myself.cloudError());
}
}
+ if (ghusr) {
+ ghusr = SnapCloud.parseResponse(ghusr)[0];
+ if (ghusr) {
+ GitHub.login(ghusr.username, ghusr.password, false,
+ function() {}, myself.githubError());
+ }
+ }
}
// override SnapCloud's user message with Morphic
@@ -338,11 +346,12 @@ IDE_Morph.prototype.openIn = function (world) {
this.parent.add(this.shield);
myself.showMessage('Fetching project\nfrom the cloud...');
+ // make sure to lowercase the username
dict = SnapCloud.parseDict(location.hash.substr(9));
- dict.Username = dict.Username;
+ dict.Username = dict.Username.toLowerCase();
SnapCloud.getPublicProject(
- dict,
+ SnapCloud.encodeDict(dict),
function (projectData) {
var msg;
myself.nextSteps([
@@ -370,10 +379,51 @@ IDE_Morph.prototype.openIn = function (world) {
},
this.cloudError()
);
+ } else if (location.hash.substr(0, 8) === '#github:') {
+ this.shield = new Morph();
+ this.shield.color = this.color;
+ this.shield.setExtent(this.parent.extent());
+ this.parent.add(this.shield);
+ myself.showMessage('Fetching project\nfrom GitHub...');
+
+ dict = SnapCloud.parseDict(location.hash.substr(8));
+
+ GitHub.getProject(
+ dict.Username,
+ dict.projectName,
+ function (projectData) {
+ var msg;
+ myself.nextSteps([
+ function () {
+ msg = myself.showMessage('Opening GitHub project...');
+ },
+ function () {
+ if (projectData.indexOf('<snapdata') === 0) {
+ myself.rawOpenCloudDataString(projectData);
+ } else if (
+ projectData.indexOf('<project') === 0
+ ) {
+ myself.rawOpenProjectString(projectData);
+ }
+ myself.hasChangedMedia = true;
+ },
+ function () {
+ myself.shield.destroy();
+ myself.shield = null;
+ msg.destroy();
+ myself.toggleAppMode(true);
+ myself.runScripts();
+ }
+ ]);
+ },
+ this.githubError()
+ );
} else if (location.hash.substr(0, 6) === '#lang:') {
urlLanguage = location.hash.substr(6);
this.setLanguage(urlLanguage);
this.loadNewProject = true;
+ } else if (location.hash.substr(0, 7) === '#signup') {
+ this.createCloudAccount();
}
}
@@ -1934,16 +1984,49 @@ IDE_Morph.prototype.cloudMenu = function () {
shiftClicked = (world.currentKey === 16);
menu = new MenuMorph(this);
+ if (shiftClicked) {
+ menu.addItem(
+ 'url...',
+ 'setCloudURL',
+ null,
+ new Color(100, 0, 0)
+ );
+ menu.addLine();
+ }
if (!SnapCloud.username) {
menu.addItem(
'Login...',
'initializeCloud'
);
+ menu.addItem(
+ 'Signup...',
+ 'createCloudAccount'
+ );
+ menu.addItem(
+ 'Reset Password...',
+ 'resetCloudPassword'
+ );
} else {
menu.addItem(
localize('Logout') + ' ' + SnapCloud.username,
'logout'
);
+ menu.addItem(
+ 'Change Password...',
+ 'changeCloudPassword'
+ );
+ }
+
+ if (!GitHub.username) {
+ menu.addItem(
+ 'Login via GitHub...',
+ 'initializeGitHub'
+ );
+ } else {
+ menu.addItem(
+ localize('Logout') + ' ' + GitHub.username + ' ' + localize('from GitHub'),
+ 'logoutGitHub'
+ );
}
if (shiftClicked) {
menu.addLine();
@@ -1989,6 +2072,53 @@ IDE_Morph.prototype.cloudMenu = function () {
null,
new Color(100, 0, 0)
);
+ menu.addLine();
+ menu.addItem(
+ 'open shared project from cloud...',
+ function () {
+ myself.prompt('Author name…', function (usr) {
+ myself.prompt('Project name...', function (prj) {
+ var id = 'Username=' +
+ encodeURIComponent(usr.toLowerCase()) +
+ '&ProjectName=' +
+ encodeURIComponent(prj);
+ myself.showMessage(
+ 'Fetching project\nfrom the cloud...'
+ );
+ SnapCloud.getPublicProject(
+ id,
+ function (projectData) {
+ var msg;
+ if (!Process.prototype.isCatchingErrors) {
+ window.open(
+ 'data:text/xml,' + projectData
+ );
+ }
+ myself.nextSteps([
+ function () {
+ msg = myself.showMessage(
+ 'Opening project...'
+ );
+ },
+ function () {
+ myself.rawOpenCloudDataString(
+ projectData
+ );
+ },
+ function () {
+ msg.destroy();
+ }
+ ]);
+ },
+ myself.cloudError()
+ );
+
+ }, null, 'project');
+ }, null, 'project');
+ },
+ null,
+ new Color(100, 0, 0)
+ );
}
menu.popup(world, pos);
};
@@ -2633,7 +2763,14 @@ IDE_Morph.prototype.editProjectNotes = function () {
};
IDE_Morph.prototype.newProject = function () {
- this.source = SnapCloud.username ? 'cloud' : 'local';
+ if (SnapCloud.username) {
+ this.source = 'cloud';
+ } else if (GitHub.username) {
+ this.source = 'github';
+ } else {
+ this.source = 'local';
+ }
+
if (this.stage) {
this.stage.destroy();
}
@@ -2665,8 +2802,10 @@ IDE_Morph.prototype.save = function () {
if (this.projectName) {
if (this.source === 'local') { // as well as 'examples'
this.saveProject(this.projectName);
- } else { // 'cloud'
+ } else if (this.source === 'cloud') { // 'cloud'
this.saveProjectToCloud(this.projectName);
+ } else { // 'github'
+ this.saveProjectToGitHub(this.projectName);
}
} else {
this.saveProjectsBrowser();
@@ -3554,17 +3693,17 @@ IDE_Morph.prototype.initializeCloud = function () {
new DialogBoxMorph(
null,
function (user) {
- var pw = user.password,
+ var pwh = hex_sha512(user.password),
str;
SnapCloud.login(
user.username,
- pw,
+ pwh,
function () {
if (user.choice) {
str = SnapCloud.encodeDict(
{
username: user.username,
- password: pw
+ password: pwh
}
);
localStorage['-snap-user'] = str;
@@ -3589,6 +3728,160 @@ IDE_Morph.prototype.initializeCloud = function () {
);
};
+IDE_Morph.prototype.initializeGitHub = function () {
+ var myself = this,
+ world = this.world();
+ new DialogBoxMorph(
+ null,
+ function (user) {
+ var pw = user.password,
+ str;
+ GitHub.login(
+ user.username,
+ pw,
+ true,
+ function () {
+ if (user.choice) {
+ str = SnapCloud.encodeDict(
+ {
+ username: user.username,
+ password: pw
+ }
+ );
+ localStorage['-snap-ghuser'] = str;
+ }
+ myself.source = 'github';
+ myself.showMessage('now connected.', 2);
+ },
+ myself.githubError()
+ );
+ }
+ ).withKey('cloudlogin').promptCredentials(
+ 'Sign in with your GitHub account',
+ 'login',
+ null,
+ null,
+ null,
+ null,
+ 'stay signed in on this computer\nuntil logging out',
+ world,
+ myself.cloudIcon(),
+ myself.cloudMsg
+ );
+};
+
+IDE_Morph.prototype.createCloudAccount = function () {
+ var myself = this,
+ world = this.world();
+/*
+ // force-logout, commented out for now:
+ delete localStorage['-snap-user'];
+ SnapCloud.clear();
+*/
+ new DialogBoxMorph(
+ null,
+ function (user) {
+ SnapCloud.signup(
+ user.username,
+ user.email,
+ function (txt, title) {
+ new DialogBoxMorph().inform(
+ title,
+ txt +
+ '.\n\nAn e-mail with your password\n' +
+ 'has been sent to the address provided',
+ world,
+ myself.cloudIcon(null, new Color(0, 180, 0))
+ );
+ },
+ myself.cloudError()
+ );
+ }
+ ).withKey('cloudsignup').promptCredentials(
+ 'Sign up',
+ 'signup',
+ 'http://snap.berkeley.edu/tos.html',
+ 'Terms of Service...',
+ 'http://snap.berkeley.edu/privacy.html',
+ 'Privacy...',
+ 'I have read and agree\nto the Terms of Service',
+ world,
+ myself.cloudIcon(),
+ myself.cloudMsg
+ );
+};
+
+IDE_Morph.prototype.resetCloudPassword = function () {
+ var myself = this,
+ world = this.world();
+/*
+ // force-logout, commented out for now:
+ delete localStorage['-snap-user'];
+ SnapCloud.clear();
+*/
+ new DialogBoxMorph(
+ null,
+ function (user) {
+ SnapCloud.resetPassword(
+ user.username,
+ function (txt, title) {
+ new DialogBoxMorph().inform(
+ title,
+ txt +
+ '.\n\nAn e-mail with a link to\n' +
+ 'reset your password\n' +
+ 'has been sent to the address provided',
+ world,
+ myself.cloudIcon(null, new Color(0, 180, 0))
+ );
+ },
+ myself.cloudError()
+ );
+ }
+ ).withKey('cloudresetpassword').promptCredentials(
+ 'Reset password',
+ 'resetPassword',
+ null,
+ null,
+ null,
+ null,
+ null,
+ world,
+ myself.cloudIcon(),
+ myself.cloudMsg
+ );
+};
+
+IDE_Morph.prototype.changeCloudPassword = function () {
+ var myself = this,
+ world = this.world();
+ new DialogBoxMorph(
+ null,
+ function (user) {
+ SnapCloud.changePassword(
+ user.oldpassword,
+ user.password,
+ function () {
+ myself.logout();
+ myself.showMessage('password has been changed.', 2);
+ },
+ myself.cloudError()
+ );
+ }
+ ).withKey('cloudpassword').promptCredentials(
+ 'Change Password',
+ 'changePassword',
+ null,
+ null,
+ null,
+ null,
+ null,
+ world,
+ myself.cloudIcon(),
+ myself.cloudMsg
+ );
+};
+
IDE_Morph.prototype.logout = function () {
var myself = this;
delete localStorage['-snap-user'];
@@ -3602,6 +3895,19 @@ IDE_Morph.prototype.logout = function () {
);
};
+IDE_Morph.prototype.logoutGitHub = function () {
+ var myself = this;
+ delete localStorage['-snap-ghuser'];
+ GitHub.logout(
+ function () {
+ myself.showMessage('disconnected.', 2);
+ },
+ function () {
+ myself.showMessage('disconnected.', 2);
+ }
+ );
+};
+
IDE_Morph.prototype.saveProjectToCloud = function (name) {
var myself = this;
if (name) {
@@ -3615,6 +3921,19 @@ IDE_Morph.prototype.saveProjectToCloud = function (name) {
}
};
+IDE_Morph.prototype.saveProjectToGitHub = function (name) {
+ var myself = this;
+ if (name) {
+ this.showMessage('Saving project\nto GitHub...');
+ this.setProjectName(name);
+ GitHub.saveProject(
+ this,
+ function () {myself.showMessage('saved.', 2); },
+ this.githubError()
+ );
+ }
+};
+
IDE_Morph.prototype.exportProjectMedia = function (name) {
var menu, media;
this.serializer.isCollectingMedia = true;
@@ -3811,6 +4130,42 @@ IDE_Morph.prototype.cloudError = function () {
};
};
+IDE_Morph.prototype.githubError = function () {
+ var myself = this;
+
+ function getURL(url) {
+ try {
+ var request = new XMLHttpRequest();
+ request.open('GET', url, false);
+ request.send();
+ if (request.status === 200) {
+ return request.responseText;
+ }
+ return null;
+ } catch (err) {
+ return null;
+ }
+ }
+
+ return function (responseText, url) {
+ var response = responseText;
+ if (myself.shield) {
+ myself.shield.destroy();
+ myself.shield = null;
+ }
+ if (response.length > 50) {
+ response = response.substring(0, 50) + '...';
+ }
+ new DialogBoxMorph().inform(
+ 'GitHub',
+ (url ? url + '\n' : '')
+ + response,
+ myself.world(),
+ myself.cloudIcon(null, new Color(180, 0, 0))
+ );
+ };
+};
+
IDE_Morph.prototype.cloudIcon = function (height, color) {
var clr = color || DialogBoxMorph.prototype.titleBarColor,
isFlat = MorphicPreferences.isFlat,
@@ -3827,6 +4182,31 @@ IDE_Morph.prototype.cloudIcon = function (height, color) {
return icon;
};
+IDE_Morph.prototype.setCloudURL = function () {
+ new DialogBoxMorph(
+ null,
+ function (url) {
+ SnapCloud.url = url;
+ }
+ ).withKey('cloudURL').prompt(
+ 'Cloud URL',
+ SnapCloud.url,
+ this.world(),
+ null,
+ {
+ 'Snap!Cloud' :
+ 'https://snapcloud.miosoft.com/miocon/app/' +
+ 'login?_app=SnapCloud',
+ 'local network lab' :
+ '192.168.2.107:8087/miocon/app/login?_app=SnapCloud',
+ 'local network office' :
+ '192.168.186.146:8087/miocon/app/login?_app=SnapCloud',
+ 'localhost dev' :
+ 'localhost/miocon/app/login?_app=SnapCloud'
+ }
+ );
+};
+
// IDE_Morph synchronous Http data fetching
IDE_Morph.prototype.getURL = function (url) {
@@ -3923,7 +4303,7 @@ ProjectDialogMorph.prototype.init = function (ide, task) {
// additional properties:
this.ide = ide;
this.task = task || 'open'; // String describing what do do (open, save)
- this.source = ide.source || 'local'; // or 'cloud' or 'examples'
+ this.source = ide.source || 'local'; // 'or 'cloud' or 'github' or 'examples'
this.projectList = []; // [{name: , thumb: , notes:}]
this.handle = null;
@@ -3934,6 +4314,8 @@ ProjectDialogMorph.prototype.init = function (ide, task) {
this.notesText = null;
this.notesField = null;
this.deleteButton = null;
+ this.shareButton = null;
+ this.unshareButton = null;
// initialize inherited properties:
ProjectDialogMorph.uber.init.call(
@@ -3981,6 +4363,7 @@ ProjectDialogMorph.prototype.buildContents = function () {
}
this.addSourceButton('cloud', localize('Cloud'), 'cloud');
+ this.addSourceButton('github', localize('GitHub'), 'github');
this.addSourceButton('local', localize('Browser'), 'storage');
if (this.task === 'open') {
this.addSourceButton('examples', localize('Examples'), 'poster');
@@ -4073,6 +4456,10 @@ ProjectDialogMorph.prototype.buildContents = function () {
this.addButton('saveProject', 'Save');
this.action = 'saveProject';
}
+ this.shareButton = this.addButton('shareProject', 'Share');
+ this.unshareButton = this.addButton('unshareProject', 'Unshare');
+ this.shareButton.hide();
+ this.unshareButton.hide();
this.deleteButton = this.addButton('deleteProject', 'Delete');
this.addButton('cancel', 'Cancel');
@@ -4225,6 +4612,20 @@ ProjectDialogMorph.prototype.setSource = function (source) {
}
);
return;
+ case 'github':
+ msg = myself.ide.showMessage('Updating\nproject list...');
+ this.projectList = [];
+ GitHub.getProjectList(
+ function (projectList) {
+ myself.installGitHubProjectList(projectList);
+ msg.destroy();
+ },
+ function (err, lbl) {
+ msg.destroy();
+ myself.ide.githubError().call(null, err, lbl);
+ }
+ );
+ return;
case 'examples':
this.projectList = this.getExamplesProjectList();
break;
@@ -4277,7 +4678,7 @@ ProjectDialogMorph.prototype.setSource = function (source) {
}
myself.edit();
};
- } else { // 'examples', 'cloud' is initialized elsewhere
+ } else { // 'examples', 'cloud' and 'github' is initialized elsewhere
this.listField.action = function (item) {
var src, xml;
if (item === undefined) {return; }
@@ -4302,6 +4703,8 @@ ProjectDialogMorph.prototype.setSource = function (source) {
};
}
this.body.add(this.listField);
+ this.shareButton.hide();
+ this.unshareButton.hide();
if (this.source === 'local') {
this.deleteButton.show();
} else { // examples
@@ -4378,6 +4781,83 @@ ProjectDialogMorph.prototype.installCloudProjectList = function (pl) {
function (element) {
return element.ProjectName;
} : null,
+ [ // format: display shared project names bold
+ [
+ 'bold',
+ function (proj) {return proj.Public === 'true'; }
+ ]
+ ],
+ function () {myself.ok(); }
+ );
+ this.fixListFieldItemColors();
+ this.listField.fixLayout = nop;
+ this.listField.edge = InputFieldMorph.prototype.edge;
+ this.listField.fontSize = InputFieldMorph.prototype.fontSize;
+ this.listField.typeInPadding = InputFieldMorph.prototype.typeInPadding;
+ this.listField.contrast = InputFieldMorph.prototype.contrast;
+ this.listField.drawNew = InputFieldMorph.prototype.drawNew;
+ this.listField.drawRectBorder = InputFieldMorph.prototype.drawRectBorder;
+
+ this.listField.action = function (item) {
+ if (item === undefined) {return; }
+ if (myself.nameField) {
+ myself.nameField.setContents(item.ProjectName || '');
+ }
+ if (myself.task === 'open') {
+ myself.notesText.text = item.Notes || '';
+ myself.notesText.drawNew();
+ myself.notesField.contents.adjustBounds();
+ myself.preview.texture = item.Thumbnail || null;
+ myself.preview.cachedTexture = null;
+ myself.preview.drawNew();
+ (new SpeechBubbleMorph(new TextMorph(
+ localize('last changed') + '\n' + item.Updated,
+ null,
+ null,
+ null,
+ null,
+ 'center'
+ ))).popUp(
+ myself.world(),
+ myself.preview.rightCenter().add(new Point(2, 0))
+ );
+ }
+ if (item.Public === 'true') {
+ myself.shareButton.hide();
+ myself.unshareButton.show();
+ } else {
+ myself.unshareButton.hide();
+ myself.shareButton.show();
+ }
+ myself.buttons.fixLayout();
+ myself.fixLayout();
+ myself.edit();
+ };
+ this.body.add(this.listField);
+ this.shareButton.show();
+ this.unshareButton.hide();
+ this.deleteButton.show();
+ this.buttons.fixLayout();
+ this.fixLayout();
+ if (this.task === 'open') {
+ this.clearDetails();
+ }
+};
+
+ProjectDialogMorph.prototype.installGitHubProjectList = function (pl) {
+ var myself = this;
+ this.projectList = pl || [];
+ this.projectList.sort(function (x, y) {
+ return x.ProjectName < y.ProjectName ? -1 : 1;
+ });
+
+ this.listField.destroy();
+ this.listField = new ListMorph(
+ this.projectList,
+ this.projectList.length > 0 ?
+ function (element) {
+ return element.ProjectName;
+ } : null,
[],
function () {myself.ok(); }
);
@@ -4443,6 +4923,8 @@ ProjectDialogMorph.prototype.openProject = function () {
this.ide.source = this.source;
if (this.source === 'cloud') {
this.openCloudProject(proj);
+ } else if (this.source === 'github') {
+ this.openGitHubProject(proj);
} else if (this.source === 'examples') {
src = this.ide.getURL(
'http://snap.berkeley.edu/snapsource/Examples/' +
@@ -4468,20 +4950,58 @@ ProjectDialogMorph.prototype.openCloudProject = function (project) {
]);
};
+ProjectDialogMorph.prototype.openGitHubProject = function (project) {
+ var myself = this;
+ myself.ide.nextSteps([
+ function () {
+ myself.ide.showMessage('Fetching project\nfrom GitHub...');
+ },
+ function () {
+ myself.rawOpenGitHubProject(project);
+ }
+ ]);
+};
+
ProjectDialogMorph.prototype.rawOpenCloudProject = function (proj) {
var myself = this;
- SnapCloud.getProject(
- {'projectname': proj.ProjectName, 'username': SnapCloud.username},
+ SnapCloud.reconnect(
+ function () {
+ SnapCloud.callService(
+ 'getProject',
+ function (response) {
+ SnapCloud.disconnect();
+ myself.ide.source = 'cloud';
+ myself.ide.droppedText(response[0].SourceCode);
+ if (proj.Public === 'true') {
+ location.hash = '#present:Username=' +
+ encodeURIComponent(SnapCloud.username) +
+ '&ProjectName=' +
+ encodeURIComponent(proj.ProjectName);
+ }
+ },
+ myself.ide.cloudError(),
+ [proj.ProjectName]
+ );
+ },
+ myself.ide.cloudError()
+ );
+ this.destroy();
+};
+
+ProjectDialogMorph.prototype.rawOpenGitHubProject = function (proj) {
+ var myself = this;
+ GitHub.getProject(
+ GitHub.username,
+ proj.ProjectName,
function (code, media) {
- myself.ide.source = 'cloud';
+ myself.ide.source = 'github';
myself.ide.droppedText(media);
myself.ide.droppedText(code);
},
- myself.ide.cloudError()
+ myself.ide.githubError()
);
this.destroy();
};
-
ProjectDialogMorph.prototype.saveProject = function () {
var name = this.nameField.contents().text.text,
notes = this.notesText.text,
@@ -4508,6 +5028,25 @@ ProjectDialogMorph.prototype.saveProject = function () {
this.ide.setProjectName(name);
myself.saveCloudProject();
}
+ } else if (this.source === 'github') {
+ if (detect(
+ this.projectList,
+ function (item) {return item.ProjectName === name; }
+ )) {
+ this.ide.confirm(
+ localize(
+ 'Are you sure you want to replace'
+ ) + '\n"' + name + '"?',
+ 'Replace Project',
+ function () {
+ myself.ide.setProjectName(name);
+ myself.saveGitHubProject();
+ }
+ );
+ } else {
+ this.ide.setProjectName(name);
+ myself.saveGitHubProject();
+ }
} else { // 'local'
if (detect(
this.projectList,
@@ -4549,6 +5088,156 @@ ProjectDialogMorph.prototype.saveCloudProject = function () {
this.destroy();
};
+ProjectDialogMorph.prototype.saveGitHubProject = function () {
+ var myself = this;
+ this.ide.showMessage('Committing project\nto GitHub...');
+ GitHub.saveProject(
+ this.ide,
+ function () {
+ myself.ide.source = 'github';
+ myself.ide.showMessage('saved.', 2);
+ },
+ this.ide.githubError()
+ );
+ this.destroy();
+};
+
+ProjectDialogMorph.prototype.deleteProject = function () {
+ var myself = this,
+ proj,
+ idx,
+ name;
+
+ if (this.source === 'github') {
+ // TODO: not implemented
+ } else if (this.source === 'cloud') {
+ proj = this.listField.selected;
+ if (proj) {
+ this.ide.confirm(
+ localize( 'Are you sure you want to delete'
+ ) + '\n"' + proj.ProjectName + '"?',
+ 'Delete Project',
+ function () {
+ SnapCloud.reconnect(
+ function () {
+ SnapCloud.callService(
+ 'deleteProject',
+ function () {
+ SnapCloud.disconnect();
+ myself.ide.hasChangedMedia = true;
+ idx = myself.projectList.indexOf(proj);
+ myself.projectList.splice(idx, 1);
+ myself.installCloudProjectList(
+ myself.projectList
+ ); // refresh list
+ },
+ myself.ide.cloudError(),
+ [proj.ProjectName]
+ );
+ },
+ myself.ide.cloudError()
+ );
+ }
+ );
+ }
+ } else { // 'local, examples'
+ if (this.listField.selected) {
+ name = this.listField.selected.name;
+ this.ide.confirm(
+ localize(
+ 'Are you sure you want to delete'
+ ) + '\n"' + name + '"?',
+ 'Delete Project',
+ function () {
+ delete localStorage['-snap-project-' + name];
+ myself.setSource(myself.source); // refresh list
+ }
+ );
+ }
+ }
+};
+
+ProjectDialogMorph.prototype.shareProject = function () {
+ var myself = this,
+ proj = this.listField.selected,
+ entry = this.listField.active;
+
+ if (proj) {
+ this.ide.confirm(
+ localize(
+ 'Are you sure you want to publish'
+ ) + '\n"' + proj.ProjectName + '"?',
+ 'Share Project',
+ function () {
+ myself.ide.showMessage('sharing\nproject...');
+ SnapCloud.reconnect(
+ function () {
+ SnapCloud.callService(
+ 'publishProject',
+ function () {
+ SnapCloud.disconnect();
+ proj.Public = 'true';
+ myself.unshareButton.show();
+ myself.shareButton.hide();
+ entry.label.isBold = true;
+ entry.label.drawNew();
+ entry.label.changed();
+ myself.buttons.fixLayout();
+ myself.drawNew();
+ myself.ide.showMessage('shared.', 2);
+ },
+ myself.ide.cloudError(),
+ [proj.ProjectName]
+ );
+ },
+ myself.ide.cloudError()
+ );
+ }
+ );
+ }
+};
+
+ProjectDialogMorph.prototype.unshareProject = function () {
+ var myself = this,
+ proj = this.listField.selected,
+ entry = this.listField.active;
+
+
+ if (proj) {
+ this.ide.confirm(
+ localize(
+ 'Are you sure you want to unpublish'
+ ) + '\n"' + proj.ProjectName + '"?',
+ 'Unshare Project',
+ function () {
+ myself.ide.showMessage('unsharing\nproject...');
+ SnapCloud.reconnect(
+ function () {
+ SnapCloud.callService(
+ 'unpublishProject',
+ function () {
+ SnapCloud.disconnect();
+ proj.Public = 'false';
+ myself.shareButton.show();
+ myself.unshareButton.hide();
+ entry.label.isBold = false;
+ entry.label.drawNew();
+ entry.label.changed();
+ myself.buttons.fixLayout();
+ myself.drawNew();
+ myself.ide.showMessage('unshared.', 2);
+ },
+ myself.ide.cloudError(),
+ [proj.ProjectName]
+ );
+ },
+ myself.ide.cloudError()
+ );
+ }
+ );
+ }
+};
+
ProjectDialogMorph.prototype.edit = function () {
if (this.nameField) {
this.nameField.edit();
diff --git a/snap.html b/snap.html
index 26c5124..11e0095 100755
--- a/snap.html
+++ b/snap.html
@@ -16,9 +16,10 @@
<script type="text/javascript" src="xml.js"></script>
<script type="text/javascript" src="store.js"></script>
<script type="text/javascript" src="locale.js"></script>
+ <script type="text/javascript" src="cloud.js"></script>
<script type="text/javascript" src="octokit/promise-1.0.0.js"></script>
<script type="text/javascript" src="octokit/octokit.js"></script>
- <script type="text/javascript" src="cloud.js"></script>
+ <script type="text/javascript" src="github.js"></script>
<script type="text/javascript" src="sha512.js"></script>
<script type="text/javascript">
var world;