8bitworkshop/src/ide/sync.ts

269 lines
9.4 KiB
TypeScript

import DOMPurify from "dompurify";
import { getCookie, getFilenameForPath, getFilenamePrefix, loadScript } from "../common/util";
import { gaEvent } from "./analytics";
import { alertError, alertInfo, setWaitDialog, setWaitProgress } from "./dialogs";
import { createNewPersistentStore } from "./project";
import { GHSession, GithubService, getRepos, parseGithubURL } from "./services";
import { getCurrentMainFilename, getCurrentOutput, getCurrentProject, getPlatformStore, gotoNewLocation, projectWindows, repo_id } from "./ui";
declare var Octokat;
var githubService: GithubService;
export async function getGithubService() {
if (!githubService) {
// load github API client
await loadScript('lib/octokat.js');
// load firebase
await loadScript('https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js');
await loadScript('https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js');
await loadScript('https://8bitworkshop.com/config.js');
// get github API key from cookie
// TODO: move to service?
var ghkey = getCookie('__github_key');
githubService = new GithubService(Octokat, ghkey, getPlatformStore(), getCurrentProject());
console.log("loaded github service");
}
return githubService;
}
export function getBoundGithubURL(): string {
var toks = (repo_id || '').split('/');
if (toks.length != 2) {
alertError("You are not in a GitHub repository. Choose one from the pulldown, or Import or Publish one.");
return null;
}
return 'https://github.com/' + toks[0] + '/' + toks[1];
}
// GITHUB stuff (TODO: move)
export async function importProjectFromGithub(githuburl: string, replaceURL: boolean) {
var sess: GHSession;
var urlparse = parseGithubURL(githuburl);
if (!urlparse) {
alertError('Could not parse Github URL.');
return;
}
// redirect to repo if exists
var existing = getRepos()[urlparse.repopath];
if (existing && !confirm("You've already imported " + urlparse.repopath + " -- do you want to replace all local files?")) {
return;
}
// create new store for imported repository
setWaitDialog(true);
var newstore = createNewPersistentStore(urlparse.repopath);
// import into new store
setWaitProgress(0.25);
var gh = await getGithubService();
return gh.import(githuburl).then((sess1: GHSession) => {
sess = sess1;
setWaitProgress(0.75);
return gh.pull(githuburl, newstore);
}).then((sess2: GHSession) => {
// TODO: only first session has mainPath?
// reload repo
setWaitDialog(false);
gaEvent('sync', 'import', githuburl);
gotoNewLocation(replaceURL, { repo: urlparse.repopath }); // file:sess.mainPath, platform:sess.platform_id};
}).catch((e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not import " + githuburl + "." + e);
});
}
export async function _loginToGithub(e) {
var gh = await getGithubService();
gh.login().then(() => {
alertInfo("You are signed in to Github.");
}).catch((e) => {
alertError("Could not sign in." + e);
});
}
export async function _logoutOfGithub(e) {
var gh = await getGithubService();
gh.logout().then(() => {
alertInfo("You are logged out of Github.");
});
}
export function _importProjectFromGithub(e) {
var modal = $("#importGithubModal");
var btn = $("#importGithubButton");
modal.modal('show');
btn.off('click').on('click', () => {
var githuburl = $("#importGithubURL").val() + "";
modal.modal('hide');
importProjectFromGithub(githuburl, false);
});
}
export function _publishProjectToGithub(e) {
if (repo_id) {
if (!confirm("This project (" + getCurrentProject().mainPath + ") is already bound to a Github repository. Do you want to re-publish to a new repository? (You can instead choose 'Push Changes' to update files in the existing repository.)"))
return;
}
var modal = $("#publishGithubModal");
var btn = $("#publishGithubButton");
$("#githubRepoName").val(getFilenamePrefix(getFilenameForPath(getCurrentProject().mainPath)));
modal.modal('show');
btn.off('click').on('click', async () => {
var name = $("#githubRepoName").val() + "";
var desc = $("#githubRepoDesc").val() + "";
var priv = $("#githubRepoPrivate").val() == 'private';
var license = $("#githubRepoLicense").val() + "";
var sess;
if (!name) {
alertError("You did not enter a project name.");
return;
}
modal.modal('hide');
setWaitDialog(true);
var gh = await getGithubService();
gh.login().then(() => {
setWaitProgress(0.25);
return gh.publish(name, desc, license, priv);
}).then((_sess) => {
sess = _sess;
setWaitProgress(0.5);
//repo_id = qs.repo = sess.repopath;
return pushChangesToGithub('initial import from 8bitworkshop.com');
}).then(() => {
gaEvent('sync', 'publish', priv ? "" : name);
importProjectFromGithub(sess.url, false);
}).catch((e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not publish GitHub repository: " + e);
});
});
}
export function _pushProjectToGithub(e) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
var modal = $("#pushGithubModal");
var btn = $("#pushGithubButton");
modal.modal('show');
btn.off('click').on('click', () => {
var commitMsg = $("#githubCommitMsg").val() + "";
modal.modal('hide');
pushChangesToGithub(commitMsg);
});
}
export function _pullProjectFromGithub(e) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
bootbox.confirm("Pull from repository and replace all local files? Any changes you've made will be overwritten.",
async (ok) => {
if (ok) {
setWaitDialog(true);
var gh = await getGithubService();
gh.pull(ghurl).then((sess: GHSession) => {
setWaitDialog(false);
projectWindows.updateAllOpenWindows(getPlatformStore());
});
}
});
}
function confirmCommit(sess): Promise<GHSession> {
return new Promise((resolve, reject) => {
var files = sess.commit.files;
console.log(files);
// anything changed?
if (files.length == 0) {
setWaitDialog(false);
alertInfo("No files changed.");
return;
}
// build commit confirm message
var msg = "";
for (var f of files) {
msg += DOMPurify.sanitize(f.filename) + ": " + f.status;
if (f.additions || f.deletions || f.changes) {
msg += " (" + f.additions + " additions, " + f.deletions + " deletions, " + f.changes + " changes)";
};
msg += "<br/>";
}
// show dialog, continue when yes
bootbox.confirm(msg, (ok) => {
if (ok) {
resolve(sess);
} else {
setWaitDialog(false);
}
});
});
}
async function pushChangesToGithub(message: string) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
// build file list for push
var files = [];
for (var path in getCurrentProject().filedata) {
var newpath = getCurrentProject().stripLocalPath(path);
var data = getCurrentProject().filedata[path];
if (newpath && data) {
files.push({ path: newpath, data: data });
}
}
// include built ROM file in bin/[mainfile].rom
if (getCurrentOutput() instanceof Uint8Array) {
let binpath = "bin/" + getCurrentMainFilename() + ".rom";
files.push({ path: binpath, data: getCurrentOutput() });
}
// push files
setWaitDialog(true);
var gh = await getGithubService();
return gh.login().then(() => {
setWaitProgress(0.5);
return gh.commit(ghurl, message, files);
}).then((sess) => {
return confirmCommit(sess);
}).then((sess) => {
return gh.push(sess);
}).then((sess) => {
setWaitDialog(false);
alertInfo("Pushed files to " + ghurl);
return sess;
}).catch((e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not push GitHub repository: " + e);
});
}
export function _removeRepository() {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
bootbox.prompt("<p>Are you sure you want to delete this repository (" + DOMPurify.sanitize(ghurl) + ") from browser storage?</p><p>All changes since last commit will be lost.</p><p>Type DELETE to proceed.<p>", (yes) => {
if (yes.trim().toUpperCase() == "DELETE") {
removeRepository();
}
});
}
async function removeRepository() {
var ghurl = getBoundGithubURL();
setWaitDialog(true);
let gh = await getGithubService();
let sess = await gh.getGithubSession(ghurl);
gh.bind(sess, false);
// delete all keys in (repo) storage
await getPlatformStore().keys().then((keys: string[]) => {
return Promise.all(keys.map((key) => {
return getPlatformStore().removeItem(key);
}));
});
setWaitDialog(false);
// leave repository
gotoNewLocation(false, { repo: '/' });
}