1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-11-12 10:07:00 +00:00
8bitworkshop/src/ui.ts

2038 lines
61 KiB
TypeScript
Raw Normal View History

"use strict";
2016-12-16 01:21:51 +00:00
// 8bitworkshop IDE user interface
2018-07-08 03:10:51 +00:00
import $ = require("jquery");
import * as bootstrap from "bootstrap";
2018-07-08 14:07:19 +00:00
import { CodeProject } from "./project";
import { WorkerResult, WorkerOutput, VerilogOutput, SourceFile, WorkerError, FileData } from "./workertypes";
2018-07-08 03:10:51 +00:00
import { ProjectWindows } from "./windows";
import { Platform, Preset, DebugSymbols, DebugEvalCondition, isDebuggable } from "./baseplatform";
import { PLATFORMS, EmuHalt, Toolbar } from "./emu";
2018-07-08 03:10:51 +00:00
import * as Views from "./views";
import { createNewPersistentStore } from "./store";
import { getFilenameForPath, getFilenamePrefix, highlightDifferences, invertMap, byteArrayToString, compressLZG,
2019-05-17 04:42:52 +00:00
byteArrayToUTF8, isProbablyBinary, getWithBinary, getBasePlatform, getRootBasePlatform, hex } from "./util";
import { StateRecorderImpl } from "./recorder";
2019-05-09 17:22:24 +00:00
import { GHSession, GithubService, getRepos, parseGithubURL } from "./services";
2018-07-08 03:10:51 +00:00
// external libs (TODO)
2019-05-07 19:37:37 +00:00
declare var Tour, GIF, saveAs, JSZip, Mousetrap, Split, firebase;
2019-05-17 19:45:19 +00:00
declare var ga;
// in index.html
declare var exports;
2018-07-08 03:10:51 +00:00
// make sure VCS doesn't start
if (window['Javatari']) window['Javatari'].AUTO_START = false;
2019-05-08 23:15:26 +00:00
var PRESETS : Preset[]; // presets array
2019-05-08 23:15:26 +00:00
export var platform_id : string; // platform ID string (platform)
export var store_id : string; // store ID string (repo || platform)
export var repo_id : string; // repository ID (repo)
export var platform : Platform; // emulator object
2017-01-13 02:21:35 +00:00
var toolbar = $("#controls_top");
var uitoolbar : Toolbar;
export var current_project : CodeProject; // current CodeProject object
2019-02-26 15:56:51 +00:00
export var projectWindows : ProjectWindows; // window manager
var stateRecorder : StateRecorderImpl;
var userPaused : boolean; // did user explicitly pause?
var current_output : WorkerOutput; // current ROM
2019-08-20 23:13:41 +00:00
var current_preset : Preset; // current preset object (if selected)
var store; // persistent store
export var compparams; // received build params from worker
export var lastDebugState; // last debug state (object)
var lastDebugInfo; // last debug info (CPU text)
var debugCategory; // current debug category
var debugTickPaused = false;
var recorderActive = false;
var lastBreakExpr = "c.PC == 0x6000";
// TODO: codemirror multiplex support?
var TOOL_TO_SOURCE_STYLE = {
'dasm': '6502',
'acme': '6502',
'cc65': 'text/x-csrc',
'ca65': '6502',
2019-08-16 01:25:08 +00:00
'nesasm': '6502',
'z80asm': 'z80',
'sdasz80': 'z80',
'sdcc': 'text/x-csrc',
2017-11-11 19:45:32 +00:00
'verilator': 'verilog',
'jsasm': 'z80',
'zmac': 'z80',
'bataribasic': 'bataribasic',
'markdown': 'markdown',
'xasm6809': 'z80'
}
function gaEvent(category:string, action:string, label?:string, value?:string) {
if (ga) ga('send', 'event', category, action, label, value);
}
function alertError(s:string) {
gaEvent('error', platform_id||'error', s);
2019-05-20 19:21:38 +00:00
setWaitDialog(false);
bootbox.alert(s);
}
function alertInfo(s:string) {
2019-05-20 19:21:38 +00:00
setWaitDialog(false);
bootbox.alert(s);
}
export function loadScript(scriptfn:string) : Promise<Event> {
return new Promise( (resolve, reject) => {
var script = document.createElement('script');
script.onload = resolve;
script.onerror = reject;
script.src = scriptfn;
document.getElementsByTagName('head')[0].appendChild(script);
});
}
2018-07-08 14:07:19 +00:00
function newWorker() : Worker {
return new Worker("./src/worker/loader.js");
}
2018-06-28 04:57:06 +00:00
var hasLocalStorage : boolean = function() {
try {
const key = "__some_random_key_you_are_not_going_to_use__";
localStorage.setItem(key, key);
localStorage.removeItem(key);
return true;
} catch (e) {
return false;
}
}();
2018-07-08 14:07:19 +00:00
function getCurrentPresetTitle() : string {
2019-08-20 23:13:41 +00:00
if (!current_preset)
return current_project.mainPath || "ROM";
2016-12-16 01:21:51 +00:00
else
2019-08-20 23:13:41 +00:00
return current_preset.title || current_preset.name || current_project.mainPath || "ROM";
2016-12-16 01:21:51 +00:00
}
2018-07-08 14:07:19 +00:00
function setLastPreset(id:string) {
if (hasLocalStorage) {
if (repo_id && platform_id)
localStorage.setItem("__lastrepo_" + platform_id, repo_id);
else
localStorage.removeItem("__lastrepo_" + platform_id);
localStorage.setItem("__lastplatform", platform_id);
localStorage.setItem("__lastid_" + store_id, id);
}
2016-12-16 01:21:51 +00:00
}
function unsetLastPreset() {
if (hasLocalStorage) {
delete qs['file'];
localStorage.removeItem("__lastid_"+store_id);
}
}
2018-06-29 23:44:04 +00:00
function initProject() {
current_project = new CodeProject(newWorker(), platform_id, platform, store);
projectWindows = new ProjectWindows($("#workspace")[0] as HTMLElement, current_project);
current_project.callbackGetRemote = getWithBinary;
2018-07-26 13:43:49 +00:00
current_project.callbackBuildResult = (result:WorkerResult) => {
setCompileOutput(result);
refreshWindowList();
};
current_project.callbackBuildStatus = (busy:boolean) => {
if (busy) {
toolbar.addClass("is-busy");
} else {
toolbar.removeClass("is-busy");
toolbar.removeClass("has-errors"); // may be added in next callback
2018-07-26 13:43:49 +00:00
projectWindows.setErrors(null);
$("#error_alert").hide();
}
$('#compile_spinner').css('visibility', busy ? 'visible' : 'hidden');
};
2018-06-29 23:44:04 +00:00
}
function refreshWindowList() {
var ul = $("#windowMenuList").empty();
var separate = false;
function addWindowItem(id, name, createfn) {
if (separate) {
ul.append(document.createElement("hr"));
separate = false;
}
var li = document.createElement("li");
var a = document.createElement("a");
a.setAttribute("class", "dropdown-item");
a.setAttribute("href", "#");
2019-08-31 19:36:50 +00:00
a.setAttribute("data-wndid", id);
if (id == projectWindows.getActiveID())
$(a).addClass("dropdown-item-checked");
a.appendChild(document.createTextNode(name));
li.appendChild(a);
ul.append(li);
if (createfn) {
projectWindows.setCreateFunc(id, createfn);
$(a).click( (e) => {
projectWindows.createOrShow(id);
ul.find('a').removeClass("dropdown-item-checked");
ul.find(e.target).addClass("dropdown-item-checked");
});
}
}
2018-07-08 14:07:19 +00:00
function loadEditor(path:string) {
var tool = platform.getToolForFilename(path);
var mode = tool && TOOL_TO_SOURCE_STYLE[tool];
2018-07-08 03:10:51 +00:00
return new Views.SourceEditor(path, mode);
}
2018-12-30 18:57:33 +00:00
function addEditorItem(id:string) {
var data = current_project.getFile(id);
if (typeof data === 'string')
addWindowItem(id, getFilenameForPath(id), loadEditor);
else if (data instanceof Uint8Array)
2018-12-08 00:28:11 +00:00
addWindowItem(id, getFilenameForPath(id), () => { return new Views.BinaryFileView(id, data as Uint8Array); });
}
// add main file editor
addEditorItem(current_project.mainPath);
2018-07-04 01:09:58 +00:00
// add other source files
current_project.iterateFiles( (id, text) => {
if (text && id != current_project.mainPath)
addEditorItem(id);
});
2018-07-04 01:09:58 +00:00
// add listings
// TODO: update listing when recompiling
separate = true;
2018-07-04 01:09:58 +00:00
var listings = current_project.getListings();
if (listings) {
for (var lstfn in listings) {
var lst = listings[lstfn];
2019-02-22 16:43:07 +00:00
// add listing if source/assembly file exists and has text
if ((lst.assemblyfile && lst.assemblyfile.text) || (lst.sourcefile && lst.sourcefile.text)) {
addWindowItem(lstfn, getFilenameForPath(lstfn), (path) => {
return new Views.ListingView(path);
2018-07-04 01:09:58 +00:00
});
}
}
}
// add other tools
separate = true;
2018-07-04 01:09:58 +00:00
if (platform.disassemble) {
addWindowItem("#disasm", "Disassembly", () => {
2018-07-08 03:10:51 +00:00
return new Views.DisassemblerView();
});
}
if (platform.readAddress) {
addWindowItem("#memory", "Memory Browser", () => {
2018-07-08 03:10:51 +00:00
return new Views.MemoryView();
});
}
2019-08-27 16:12:56 +00:00
if (current_project.segments && current_project.segments.length) {
addWindowItem("#memmap", "Memory Map", () => {
2019-02-21 21:47:25 +00:00
return new Views.MemoryMapView();
});
}
2019-08-26 17:44:06 +00:00
if (platform.readVRAMAddress) {
addWindowItem("#memvram", "VRAM Browser", () => {
return new Views.VRAMMemoryView();
});
}
if (platform.startProbing) {
addWindowItem("#memheatmap", "Memory Probe", () => {
return new Views.AddressHeatMapView();
});
// TODO: only if raster
addWindowItem("#crtheatmap", "CRT Probe", () => {
2019-08-25 14:45:36 +00:00
return new Views.RasterPCHeatMapView();
});
}
2019-08-26 17:44:06 +00:00
else if (platform.getRasterScanline && platform.setBreakpoint && platform.getCPUState) { // TODO: use profiler class to determine compat
addWindowItem("#profiler", "Profiler", () => {
return new Views.ProfileView();
});
}
addWindowItem('#asseteditor', 'Asset Editor', () => {
2019-03-18 18:39:02 +00:00
return new Views.AssetEditorView();
});
}
function loadMainWindow(preset_id:string) {
// we need this to build create functions for the editor
refreshWindowList();
// show main file
projectWindows.createOrShow(preset_id);
// build project
current_project.setMainFile(preset_id);
}
2018-07-08 14:07:19 +00:00
function loadProject(preset_id:string) {
// set current file ID
// TODO: this is done twice
current_project.mainPath = preset_id;
setLastPreset(preset_id);
// load files from storage or web URLs
2019-05-23 12:32:53 +00:00
current_project.loadFiles([preset_id]).then((result) => {
measureTimeLoad = new Date(); // for timing calc.
2019-05-23 12:32:53 +00:00
if (result && result.length) {
// file found; continue
loadMainWindow(preset_id);
} else {
getSkeletonFile(preset_id).then((skel) => {
current_project.filedata[preset_id] = skel || "\n";
loadMainWindow(preset_id);
// don't alert if we selected "new file"
if (!qs['newfile']) {
alertInfo("Could not find file \"" + preset_id + "\". Loading default file.");
}
delete qs['newfile'];
replaceURLState();
});
2018-06-29 23:44:04 +00:00
}
});
2016-12-16 01:21:51 +00:00
}
function reloadProject(id:string) {
// leave repository == '/'
if (id == '/') {
qs = {repo:'/'};
2019-05-09 17:22:24 +00:00
} else if (id.indexOf('://') >= 0) {
var urlparse = parseGithubURL(id);
if (urlparse) {
qs = {repo:urlparse.repopath};
}
} else {
qs['platform'] = platform_id;
qs['file'] = id;
}
gotoNewLocation();
2016-12-16 01:21:51 +00:00
}
function getSkeletonFile(fileid:string) : Promise<string> {
2018-06-29 23:44:04 +00:00
var ext = platform.getToolForFilename(fileid);
return $.get( "presets/"+getBasePlatform(platform_id)+"/skeleton."+ext, 'text').catch((e) => {
alertError("Could not load skeleton for " + platform_id + "/" + ext + "; using blank file");
2018-06-29 23:44:04 +00:00
});
}
2019-04-19 14:00:01 +00:00
function checkEnteredFilename(fn : string) : boolean {
if (fn.indexOf(" ") >= 0) {
alertError("No spaces in filenames, please.");
2019-04-19 14:00:01 +00:00
return false;
}
return true;
}
2016-12-16 01:21:51 +00:00
function _createNewFile(e) {
// TODO: support spaces
bootbox.prompt({
title:"Enter the name of your new main source file.",
placeholder:"newfile" + platform.getDefaultExtension(),
callback:(filename) => {
if (filename && filename.trim().length > 0) {
if (!checkEnteredFilename(filename)) return;
if (filename.indexOf(".") < 0) {
filename += platform.getDefaultExtension();
}
var path = filename;
gaEvent('workspace', 'file', 'new');
qs['newfile'] = '1';
reloadProject(path);
}
2016-12-16 01:21:51 +00:00
}
} as any);
2016-12-30 23:51:15 +00:00
return true;
2016-12-16 01:21:51 +00:00
}
2018-06-26 23:57:03 +00:00
function _uploadNewFile(e) {
$("#uploadFileElem").click();
}
2018-07-08 14:07:19 +00:00
function handleFileUpload(files: File[]) {
2018-06-26 23:57:03 +00:00
console.log(files);
var index = 0;
var gotoMainFile = (files.length == 1);
function uploadNextFile() {
2018-06-26 23:57:03 +00:00
var f = files[index++];
if (!f) {
console.log("Done uploading");
2018-12-01 12:19:26 +00:00
if (gotoMainFile) {
gotoNewLocation();
} else {
updateSelector();
alertInfo("Files uploaded.");
gaEvent('workspace', 'file', 'upload');
2018-12-01 12:19:26 +00:00
}
2018-06-26 23:57:03 +00:00
} else {
var path = f.name;
2018-06-26 23:57:03 +00:00
var reader = new FileReader();
reader.onload = function(e) {
var arrbuf = (<any>e.target).result as ArrayBuffer;
var data : FileData = new Uint8Array(arrbuf);
// convert to UTF8, unless it's a binary file
if (isProbablyBinary(path, data)) {
gotoMainFile = false;
} else {
data = byteArrayToUTF8(data).replace('\r\n','\n'); // convert CRLF to LF
}
// store in local forage
// TODO: use projectWindows uploadFile()
2018-06-26 23:57:03 +00:00
store.setItem(path, data, function(err, result) {
if (err)
alertError("Error uploading " + path + ": " + err);
2018-06-26 23:57:03 +00:00
else {
console.log("Uploaded " + path + " " + data.length + " bytes");
if (index == 1) {
2019-05-01 19:28:15 +00:00
qs['file'] = path; // TODO?
}
2018-06-26 23:57:03 +00:00
uploadNextFile();
}
});
}
reader.readAsArrayBuffer(f); // read as binary
2018-06-26 23:57:03 +00:00
}
}
if (files) uploadNextFile();
}
function getCurrentMainFilename() : string {
return getFilenameForPath(current_project.mainPath);
}
function getCurrentEditorFilename() : string {
return getFilenameForPath(projectWindows.getActiveID());
2017-02-02 19:11:52 +00:00
}
// GITHUB stuff (TODO: move)
2019-05-07 19:37:37 +00:00
var githubService : GithubService;
2019-05-18 22:33:38 +00:00
function getCookie(name) : string {
2019-05-07 19:37:37 +00:00
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function getGithubService() {
if (!githubService) {
// get github API key from cookie
2019-05-08 17:42:24 +00:00
// TODO: move to service?
var ghkey = getCookie('__github_key');
2019-05-08 17:42:24 +00:00
githubService = new GithubService(exports['Octokat'], ghkey, store, current_project);
console.log("loaded github service");
2019-05-07 19:37:37 +00:00
}
return githubService;
}
2019-05-08 17:42:24 +00:00
function getBoundGithubURL() : string {
2019-05-08 23:15:26 +00:00
var toks = (repo_id||'').split('/');
if (toks.length != 2) {
2019-05-16 14:08:09 +00:00
alertError("<p>You are not in a GitHub repository.</p><p>Choose one from the pulldown, or Import or Publish one.</p>");
2019-05-08 23:15:26 +00:00
return null;
}
return 'https://github.com/' + toks[0] + '/' + toks[1];
}
2019-05-22 21:03:56 +00:00
function importProjectFromGithub(githuburl:string, replaceURL:boolean) {
2019-05-08 23:15:26 +00:00
var sess : GHSession;
2019-05-09 17:22:24 +00:00
var urlparse = parseGithubURL(githuburl);
2019-05-08 23:15:26 +00:00
if (!urlparse) {
alertError('Could not parse Github URL.');
2019-05-08 23:15:26 +00:00
return;
}
// redirect to repo if exists
var existing = getRepos()[urlparse.repopath];
2019-05-22 21:03:56 +00:00
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);
return getGithubService().import(githuburl).then( (sess1:GHSession) => {
sess = sess1;
setWaitProgress(0.75);
return getGithubService().pull(githuburl, newstore);
}).then( (sess2:GHSession) => {
// TODO: only first session has mainPath?
// reload repo
qs = {repo:sess.repopath}; // file:sess.mainPath, platform:sess.platform_id};
setWaitDialog(false);
gaEvent('sync', 'import', githuburl);
2019-05-22 21:03:56 +00:00
gotoNewLocation(replaceURL);
}).catch( (e) => {
setWaitDialog(false);
console.log(e);
2019-06-03 14:08:29 +00:00
alertError("<p>Could not import " + githuburl + ".</p>" + e);
2016-12-16 01:21:51 +00:00
});
}
function _loginToGithub(e) {
getGithubService().login().then(() => {
alertInfo("You are signed in to Github.");
2019-06-03 14:08:29 +00:00
}).catch( (e) => {
alertError("<p>Could not sign in.</p>" + e);
});
}
function _logoutOfGithub(e) {
getGithubService().logout().then(() => {
alertInfo("You are logged out of Github.");
});
}
2019-05-08 23:15:26 +00:00
function _importProjectFromGithub(e) {
var modal = $("#importGithubModal");
var btn = $("#importGithubButton");
modal.modal('show');
btn.off('click').on('click', () => {
var githuburl = $("#importGithubURL").val()+"";
modal.modal('hide');
2019-05-22 21:03:56 +00:00
importProjectFromGithub(githuburl, false);
2019-05-08 23:15:26 +00:00
});
}
2019-05-07 19:37:37 +00:00
function _publishProjectToGithub(e) {
2019-05-08 23:15:26 +00:00
if (repo_id) {
if (!confirm("This project (" + current_project.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;
}
2019-05-07 19:37:37 +00:00
var modal = $("#publishGithubModal");
var btn = $("#publishGithubButton");
$("#githubRepoName").val(getFilenamePrefix(getFilenameForPath(current_project.mainPath)));
modal.modal('show');
btn.off('click').on('click', () => {
2019-05-07 19:37:37 +00:00
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);
2019-05-08 17:42:24 +00:00
getGithubService().login().then( () => {
setWaitProgress(0.25);
2019-05-08 17:42:24 +00:00
return getGithubService().publish(name, desc, license, priv);
}).then( (_sess) => {
sess = _sess;
setWaitProgress(0.5);
2019-05-08 23:15:26 +00:00
repo_id = qs['repo'] = sess.repopath;
return pushChangesToGithub('initial import from 8bitworkshop.com');
}).then( () => {
gaEvent('sync', 'publish', priv?"":name);
importProjectFromGithub(sess.url, false);
2019-05-07 19:37:37 +00:00
}).catch( (e) => {
setWaitDialog(false);
console.log(e);
alertError("Could not publish GitHub repository: " + e);
2019-05-07 19:37:37 +00:00
});
});
}
2019-05-07 19:37:37 +00:00
function _pushProjectToGithub(e) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
2019-05-07 19:37:37 +00:00
var modal = $("#pushGithubModal");
var btn = $("#pushGithubButton");
modal.modal('show');
btn.off('click').on('click', () => {
var commitMsg = $("#githubCommitMsg").val()+"";
modal.modal('hide');
pushChangesToGithub(commitMsg);
});
}
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.", (ok) => {
2019-05-20 19:21:38 +00:00
if (ok) {
setWaitDialog(true);
getGithubService().pull(ghurl).then( (sess:GHSession) => {
setWaitDialog(false);
projectWindows.updateAllOpenWindows(store);
});
}
});
}
2019-05-12 19:01:33 +00:00
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);
bootbox.alert("No files changed.");
}
// build commit confirm message
var msg = "";
for (var f of files) {
msg += f.filename + ": " + f.status;
if (f.additions || f.deletions || f.changes) {
msg += " (" + f.additions + " additions, " + f.deletions + " deletions, " + f.changes + " changes)";
2019-05-16 14:08:09 +00:00
};
2019-05-12 19:01:33 +00:00
msg += "<br/>";
}
// show dialog, continue when yes
bootbox.confirm(msg, (ok) => {
if (ok) {
resolve(sess);
} else {
setWaitDialog(false);
}
});
});
}
2019-05-07 19:37:37 +00:00
function pushChangesToGithub(message:string) {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
2019-05-07 19:37:37 +00:00
// build file list for push
var files = [];
for (var path in current_project.filedata) {
var newpath = current_project.stripLocalPath(path);
var data = current_project.filedata[path];
if (newpath && data) {
files.push({path:newpath, data:data});
}
2019-05-07 19:37:37 +00:00
}
// include built ROM file in bin/[mainfile].rom
2019-05-21 20:49:48 +00:00
if (current_output instanceof Uint8Array) {
let binpath = "bin/"+getCurrentMainFilename()+".rom";
files.push({path:binpath, data:current_output});
}
2019-05-07 19:37:37 +00:00
// push files
setWaitDialog(true);
2019-05-08 17:42:24 +00:00
return getGithubService().login().then( () => {
setWaitProgress(0.5);
2019-05-12 17:33:21 +00:00
return getGithubService().commit(ghurl, message, files);
2019-05-12 19:01:33 +00:00
}).then( (sess) => {
return confirmCommit(sess);
2019-05-12 17:33:21 +00:00
}).then( (sess) => {
return getGithubService().push(sess);
2019-05-08 17:42:24 +00:00
}).then( (sess) => {
setWaitDialog(false);
alertInfo("Pushed files to " + ghurl);
return sess;
}).catch( (e) => {
2019-05-07 19:37:37 +00:00
setWaitDialog(false);
console.log(e);
alertError("Could not push GitHub repository: " + e);
});
}
2019-05-16 14:08:09 +00:00
function _deleteRepository() {
var ghurl = getBoundGithubURL();
if (!ghurl) return;
2019-07-22 01:28:44 +00:00
bootbox.prompt("<p>Are you sure you want to delete this repository (" + 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") {
2019-05-16 14:08:09 +00:00
deleteRepository();
}
});
}
function deleteRepository() {
var ghurl = getBoundGithubURL();
var gh;
setWaitDialog(true);
// delete all keys in storage
store.keys().then((keys:string[]) => {
return Promise.all(keys.map((key) => {
return store.removeItem(key);
}));
}).then(() => {
gh = getGithubService();
return gh.getGithubSession(ghurl);
}).then((sess) => {
// un-bind repo from list
gh.bind(sess, false);
}).then(() => {
setWaitDialog(false);
// leave repository
qs = {repo:'/'};
gotoNewLocation();
});
}
function _shareEmbedLink(e) {
if (current_output == null) { // TODO
alertError("Please fix errors before sharing.");
return true;
}
if (!(current_output instanceof Uint8Array)) {
alertError("Can't share a Verilog executable yet. (It's not actually a ROM...)");
return true;
}
loadClipboardLibrary();
loadScript('lib/liblzg.js').then( () => {
// TODO: Module is bad var name (conflicts with MAME)
var lzgrom = compressLZG( window['Module'], Array.from(<Uint8Array>current_output) );
window['Module'] = null; // so we load it again next time
var lzgb64 = btoa(byteArrayToString(lzgrom));
var embed = {
p: platform_id,
//n: current_project.mainPath,
r: lzgb64
};
var linkqs = $.param(embed);
var fulllink = get8bitworkshopLink(linkqs, 'embed.html');
var iframelink = '<iframe width=640 height=600 src="' + fulllink + '">';
$("#embedLinkTextarea").text(fulllink);
$("#embedIframeTextarea").text(iframelink);
$("#embedLinkModal").modal('show');
$("#embedAdviceWarnAll").hide();
$("#embedAdviceWarnIE").hide();
if (fulllink.length >= 65536) $("#embedAdviceWarnAll").show();
else if (fulllink.length >= 5120) $("#embedAdviceWarnIE").show();
});
2016-12-30 23:51:15 +00:00
return true;
2016-12-16 01:21:51 +00:00
}
function loadClipboardLibrary() {
loadScript('lib/clipboard.min.js').then( () => {
var ClipboardJS = exports['ClipboardJS'];
new ClipboardJS(".btn");
});
}
function get8bitworkshopLink(linkqs : string, fn : string) {
console.log(linkqs);
var loc = window.location;
var prefix = loc.pathname.replace('index.html','');
var protocol = (loc.host == '8bitworkshop.com') ? 'https:' : loc.protocol;
var fulllink = protocol+'//'+loc.host+prefix+fn+'?' + linkqs;
return fulllink;
}
2018-09-25 23:46:24 +00:00
function _downloadCassetteFile(e) {
if (current_output == null) {
alertError("Please fix errors before exporting.");
2018-09-25 23:46:24 +00:00
return true;
}
var addr = compparams && compparams.code_start;
if (addr === undefined) {
alertError("Cassette export is not supported on this platform.");
2018-09-25 23:46:24 +00:00
return true;
}
loadScript('lib/c2t.js').then( () => {
2018-09-25 23:46:24 +00:00
var stdout = '';
var print_fn = function(s) { stdout += s + "\n"; }
var c2t = window['c2t']({
noInitialRun:true,
print:print_fn,
printErr:print_fn
});
var FS = c2t['FS'];
var rompath = getCurrentMainFilename() + ".bin";
var audpath = getCurrentMainFilename() + ".wav";
FS.writeFile(rompath, current_output, {encoding:'binary'});
var args = ["-2bc", rompath+','+addr.toString(16), audpath];
c2t.callMain(args);
var audout = FS.readFile(audpath, {'encoding':'binary'});
if (audout) {
var blob = new Blob([audout], {type: "audio/wav"});
saveAs(blob, audpath);
2018-09-26 01:16:01 +00:00
stdout += "Then connect your audio output to the cassette input, turn up the volume, and play the audio file.";
alertInfo(stdout);
2018-09-25 23:46:24 +00:00
}
});
}
2018-08-21 14:16:47 +00:00
function _revertFile(e) {
var wnd = projectWindows.getActive();
if (wnd && wnd.setText) {
var fn = projectWindows.getActiveID();
$.get( "presets/"+getBasePlatform(platform_id)+"/"+fn, (text) => {
bootbox.confirm("Reset '" + fn + "' to default?", (ok) => {
if (ok) {
wnd.setText(text);
}
});
}, 'text')
.fail(() => {
if (repo_id) alertError("Can only revert built-in examples. If you want to revert all files, You can pull from the repository.");
else alertError("Can only revert built-in examples.");
});
} else {
alertError("Cannot revert the active window. Please choose a text file.");
}
2016-12-16 01:21:51 +00:00
}
2018-12-08 00:28:11 +00:00
function _deleteFile(e) {
var wnd = projectWindows.getActive();
if (wnd && wnd.getPath) {
var fn = projectWindows.getActiveID();
bootbox.confirm("Delete '" + fn + "'?", (ok) => {
if (ok) {
store.removeItem(fn).then( () => {
// if we delete what is selected
if (qs['file'] == fn) {
unsetLastPreset();
gotoNewLocation();
} else {
updateSelector();
alertInfo("Deleted " + fn);
}
});
}
});
2018-12-08 00:28:11 +00:00
} else {
alertError("Cannot delete the active window.");
2018-12-08 00:28:11 +00:00
}
}
function _renameFile(e) {
var wnd = projectWindows.getActive();
if (wnd && wnd.getPath && current_project.getFile(wnd.getPath())) {
var fn = projectWindows.getActiveID();
2019-05-23 03:23:55 +00:00
bootbox.prompt({
title: "Rename '" + fn + "' to?",
value: fn,
callback: (newfn) => {
var data = current_project.getFile(wnd.getPath());
if (newfn && newfn != fn && data) {
if (!checkEnteredFilename(newfn)) return;
store.removeItem(fn).then( () => {
return store.setItem(newfn, data);
}).then( () => {
updateSelector();
alert("Renamed " + fn + " to " + newfn); // need alert() so it pauses
if (fn == current_project.mainPath) {
reloadProject(newfn);
}
});
}
2019-05-23 03:23:55 +00:00
}
});
2018-12-08 00:28:11 +00:00
} else {
alertError("Cannot rename the active window.");
2018-12-08 00:28:11 +00:00
}
}
2017-02-02 19:11:52 +00:00
function _downloadROMImage(e) {
if (current_output == null) {
alertError("Please finish compiling with no errors before downloading ROM.");
2017-02-02 19:11:52 +00:00
return true;
}
if (current_output instanceof Uint8Array) {
var blob = new Blob([current_output], {type: "application/octet-stream"});
saveAs(blob, getCurrentMainFilename()+".rom");
} else {
var blob = new Blob([(<VerilogOutput>current_output).code], {type: "text/plain"});
saveAs(blob, getCurrentMainFilename()+".js");
}
2017-02-02 19:11:52 +00:00
}
function _downloadSourceFile(e) {
var text = projectWindows.getCurrentText();
if (!text) return false;
2018-12-08 12:35:48 +00:00
var blob = new Blob([text], {type:"text/plain;charset=utf-8"});
saveAs(blob, getCurrentEditorFilename(), {autoBom:false});
}
2018-08-25 18:29:51 +00:00
function _downloadProjectZipFile(e) {
loadScript('lib/jszip.min.js').then( () => {
2018-08-25 18:29:51 +00:00
var zip = new JSZip();
2019-03-18 18:39:02 +00:00
current_project.iterateFiles( (id, data) => {
if (data) {
zip.file(getFilenameForPath(id), data);
}
2018-08-25 18:29:51 +00:00
});
zip.generateAsync({type:"blob"}).then( (content) => {
saveAs(content, getCurrentMainFilename() + ".zip");
});
});
}
function _downloadAllFilesZipFile(e) {
loadScript('lib/jszip.min.js').then( () => {
var zip = new JSZip();
store.keys( (err, keys : string[]) => {
return Promise.all(keys.map( (path) => {
return store.getItem(path).then( (text) => {
if (text) {
zip.file(path, text);
}
});
})).then(() => {
return zip.generateAsync({type:"blob"});
}).then( (content) => {
return saveAs(content, platform_id + "-all.zip");
});
});
});
}
2016-12-16 01:21:51 +00:00
function populateExamples(sel) {
var files = {};
sel.append($("<option />").text("--------- Examples ---------").attr('disabled','true'));
for (var i=0; i<PRESETS.length; i++) {
var preset = PRESETS[i];
var name = preset.chapter ? (preset.chapter + ". " + preset.name) : preset.name;
2019-08-20 23:13:41 +00:00
var isCurrentPreset = preset.id==current_project.mainPath;
sel.append($("<option />").val(preset.id).text(name).attr('selected',isCurrentPreset?'selected':null));
if (isCurrentPreset) current_preset = preset;
files[preset.id] = name;
}
return files;
2016-12-16 01:21:51 +00:00
}
2019-05-09 17:22:24 +00:00
function populateRepos(sel) {
if (hasLocalStorage) {
var n = 0;
var repos = getRepos();
if (repos) {
for (let repopath in repos) {
var repo = repos[repopath];
if (repo.platform_id && getBasePlatform(repo.platform_id) == getBasePlatform(platform_id)) {
2019-05-09 17:22:24 +00:00
if (n++ == 0)
sel.append($("<option />").text("------ Repositories ------").attr('disabled','true'));
sel.append($("<option />").val(repo.url).text(repo.url.substring(repo.url.indexOf('/'))));
}
}
}
}
}
function populateFiles(sel:JQuery, category:string, prefix:string, foundFiles:{}, callback:() => void) {
2019-05-12 17:33:21 +00:00
store.keys().then( (keys:string[]) => {
2018-06-26 06:56:36 +00:00
var numFound = 0;
if (!keys) keys = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key.startsWith(prefix) && !foundFiles[key]) {
2018-06-26 06:56:36 +00:00
if (numFound++ == 0)
2018-07-08 03:10:51 +00:00
sel.append($("<option />").text("------- " + category + " -------").attr('disabled','true'));
2018-06-26 06:56:36 +00:00
var name = key.substring(prefix.length);
sel.append($("<option />").val(key).text(name).attr('selected',(key==current_project.mainPath)?'selected':null));
2018-06-26 06:56:36 +00:00
}
}
if (callback) { callback(); }
2018-06-26 06:56:36 +00:00
});
2016-12-16 01:21:51 +00:00
}
function finishSelector(sel) {
sel.css('visibility','visible');
// create option if not selected
var main = current_project.mainPath;
if (sel.val() != main) {
sel.append($("<option />").val(main).text(main).attr('selected','selected'));
}
}
2016-12-16 01:21:51 +00:00
function updateSelector() {
var sel = $("#preset_select").empty();
2019-05-08 23:15:26 +00:00
if (!repo_id) {
// normal: populate repos, examples, and local files
2019-05-12 17:33:21 +00:00
populateRepos(sel);
var foundFiles = populateExamples(sel);
populateFiles(sel, "Local Files", "", foundFiles, () => {
finishSelector(sel);
2019-05-08 23:15:26 +00:00
});
} else {
sel.append($("<option />").val('/').text('Leave Repository'));
2019-05-08 23:15:26 +00:00
// repo: populate all files
populateFiles(sel, repo_id, "", {}, () => {
finishSelector(sel);
});
2019-05-08 23:15:26 +00:00
}
2016-12-16 01:21:51 +00:00
// set click handlers
sel.off('change').change(function(e) {
2019-05-08 23:15:26 +00:00
reloadProject($(this).val().toString());
2016-12-16 01:21:51 +00:00
});
}
function showErrorAlert(errors : WorkerError[]) {
var div = $("#error_alert_msg").empty();
for (var err of errors.slice(0,10)) {
var s = '';
if (err.path) s += err.path + ":";
if (err.line) s += err.line + ":";
s += err.msg;
div.append($("<p>").text(s));
}
$("#error_alert").show();
}
var measureTimeStart : Date = new Date();
var measureTimeLoad : Date;
function measureBuildTime() {
if (ga && measureTimeLoad) {
var measureTimeBuild = new Date();
ga('send', 'timing', 'load', platform_id, (measureTimeLoad.getTime() - measureTimeStart.getTime()));
ga('send', 'timing', 'build', platform_id, (measureTimeBuild.getTime() - measureTimeLoad.getTime()));
measureTimeLoad = null; // only measure once
}
gaEvent('build', platform_id);
}
2018-07-26 13:43:49 +00:00
function setCompileOutput(data: WorkerResult) {
// errors? mark them in editor
2018-06-25 23:47:40 +00:00
if (data.errors && data.errors.length > 0) {
2018-07-03 04:21:08 +00:00
toolbar.addClass("has-errors");
projectWindows.setErrors(data.errors);
showErrorAlert(data.errors);
2016-12-16 01:21:51 +00:00
} else {
// process symbol map
2018-09-17 20:09:09 +00:00
platform.debugSymbols = new DebugSymbols(data.symbolmap);
compparams = data.params;
2016-12-16 01:21:51 +00:00
// load ROM
var rom = data.output;
if (rom) { // TODO instanceof Uint8Array) {
2016-12-16 01:21:51 +00:00
try {
clearBreakpoint(); // so we can replace memory (TODO: change toolbar btn)
2018-08-23 22:52:56 +00:00
_resetRecording();
2016-12-31 16:05:22 +00:00
platform.loadROM(getCurrentPresetTitle(), rom);
2016-12-16 01:21:51 +00:00
current_output = rom;
if (!userPaused) _resume();
measureBuildTime();
2018-08-02 17:08:37 +00:00
// TODO: reset profiler etc? (Tell views?)
2016-12-16 01:21:51 +00:00
} catch (e) {
console.log(e);
toolbar.addClass("has-errors");
showErrorAlert([{msg:e+"",line:0}]);
2016-12-16 01:21:51 +00:00
current_output = null;
return;
2016-12-16 01:21:51 +00:00
}
}
// update all windows (listings)
2018-08-02 17:08:37 +00:00
projectWindows.refresh(false);
2016-12-16 01:21:51 +00:00
}
}
function loadBIOSFromProject() {
if (platform.loadBIOS) {
var biospath = platform_id + '.rom';
store.getItem(biospath).then( (biosdata) => {
2019-05-26 23:22:51 +00:00
if (biosdata instanceof Uint8Array) {
console.log('loading BIOS')
platform.loadBIOS('BIOS', biosdata);
}
});
}
}
2018-07-29 20:26:05 +00:00
function showDebugInfo(state?) {
if (!isDebuggable(platform)) return;
2018-07-29 19:53:50 +00:00
var meminfo = $("#mem_info");
var allcats = platform.getDebugCategories();
2018-07-29 20:26:05 +00:00
if (allcats && !debugCategory)
debugCategory = allcats[0];
var s = state && platform.getDebugInfo(debugCategory, state);
if (s) {
2016-12-16 01:21:51 +00:00
var hs = lastDebugInfo ? highlightDifferences(lastDebugInfo, s) : s;
2018-07-29 19:53:50 +00:00
meminfo.show().html(hs);
var catspan = $('<div class="mem_info_links">');
2018-07-29 20:26:05 +00:00
var addCategoryLink = (cat:string) => {
var catlink = $('<a>'+cat+'</a>');
if (cat == debugCategory)
catlink.addClass('selected');
catlink.click((e) => {
debugCategory = cat;
lastDebugInfo = null;
showDebugInfo(lastDebugState);
});
catspan.append(catlink);
catspan.append('<span> </span>');
}
for (var cat of allcats) {
addCategoryLink(cat);
}
meminfo.append('<br>');
meminfo.append(catspan);
2016-12-16 01:21:51 +00:00
lastDebugInfo = s;
} else {
2018-07-29 19:53:50 +00:00
meminfo.hide();
2016-12-16 01:21:51 +00:00
lastDebugInfo = null;
}
}
2018-07-08 14:07:19 +00:00
function setDebugButtonState(btnid:string, btnstate:string) {
$("#debug_bar").find("button").removeClass("btn_active").removeClass("btn_stopped");
$("#dbg_"+btnid).addClass("btn_"+btnstate);
}
2018-11-24 21:51:29 +00:00
function checkRunReady() {
if (current_output == null) {
alertError("Can't do this until build successfully completes.");
2018-11-24 21:51:29 +00:00
return false;
} else
return true;
}
function uiDebugCallback(state) {
lastDebugState = state;
showDebugInfo(state);
projectWindows.refresh(true);
2019-04-07 01:47:42 +00:00
debugTickPaused = true;
}
2018-09-15 19:27:12 +00:00
function setupDebugCallback(btnid? : string) {
if (platform.setupDebug) platform.setupDebug((state) => {
uiDebugCallback(state);
2018-09-15 19:27:12 +00:00
setDebugButtonState(btnid||"pause", "stopped");
2016-12-31 16:05:22 +00:00
});
2018-09-15 19:27:12 +00:00
}
function setupBreakpoint(btnid? : string) {
2018-11-24 21:51:29 +00:00
if (!checkRunReady()) return;
2018-09-15 19:27:12 +00:00
_disableRecording();
setupDebugCallback(btnid);
if (btnid) setDebugButtonState(btnid, "active");
2016-12-16 01:21:51 +00:00
}
2017-11-16 17:08:06 +00:00
function _pause() {
2019-01-17 18:04:42 +00:00
if (platform && platform.isRunning()) {
2016-12-31 16:05:22 +00:00
platform.pause();
console.log("Paused");
2016-12-16 01:21:51 +00:00
}
setDebugButtonState("pause", "stopped");
2016-12-16 01:21:51 +00:00
}
2017-11-16 17:08:06 +00:00
function pause() {
2016-12-16 01:21:51 +00:00
clearBreakpoint();
2017-11-16 17:08:06 +00:00
_pause();
userPaused = true;
2017-11-16 17:08:06 +00:00
}
function _resume() {
2018-11-24 21:51:29 +00:00
if (!checkRunReady()) return;
if (!platform.isRunning()) {
2016-12-31 16:05:22 +00:00
platform.resume();
console.log("Resumed");
2016-12-16 01:21:51 +00:00
}
setDebugButtonState("go", "active");
2017-11-16 17:08:06 +00:00
}
function resume() {
clearBreakpoint();
2018-06-24 17:39:08 +00:00
if (! platform.isRunning() ) {
2018-08-02 17:08:37 +00:00
projectWindows.refresh(false);
2018-06-24 17:39:08 +00:00
}
2017-11-16 17:08:06 +00:00
_resume();
userPaused = false;
2016-12-16 01:21:51 +00:00
}
function togglePause() {
if (platform.isRunning())
pause();
else
resume();
}
2016-12-16 01:21:51 +00:00
function singleStep() {
if (!checkRunReady()) return;
setupBreakpoint("step");
2016-12-31 16:05:22 +00:00
platform.step();
2016-12-16 01:21:51 +00:00
}
2017-11-24 19:14:22 +00:00
function singleFrameStep() {
if (!checkRunReady()) return;
setupBreakpoint("tovsync");
2017-11-24 19:14:22 +00:00
platform.runToVsync();
}
2018-07-08 14:07:19 +00:00
function getEditorPC() : number {
var wnd = projectWindows.getActive();
return wnd && wnd.getCursorPC && wnd.getCursorPC();
2017-02-05 04:19:54 +00:00
}
function runToCursor() {
2018-11-24 21:51:29 +00:00
if (!checkRunReady()) return;
setupBreakpoint("toline");
var pc = getEditorPC();
if (pc >= 0) {
2017-02-05 04:19:54 +00:00
console.log("Run to", pc.toString(16));
if (platform.runToPC) {
platform.runToPC(pc);
} else {
platform.runEval((c) => {
return c.PC == pc;
});
}
2016-12-16 01:21:51 +00:00
}
}
function runUntilReturn() {
if (!checkRunReady()) return;
setupBreakpoint("stepout");
platform.runUntilReturn();
}
function runStepBackwards() {
if (!checkRunReady()) return;
setupBreakpoint("stepback");
platform.stepBack();
}
2016-12-16 01:21:51 +00:00
function clearBreakpoint() {
2017-01-06 14:49:07 +00:00
lastDebugState = null;
2017-11-11 19:45:32 +00:00
if (platform.clearDebug) platform.clearDebug();
2018-09-15 19:27:12 +00:00
setupDebugCallback(); // in case of BRK/trap
2018-07-29 20:26:05 +00:00
showDebugInfo();
2016-12-16 01:21:51 +00:00
}
function resetAndDebug() {
if (!checkRunReady()) return;
2018-08-23 22:52:56 +00:00
_disableRecording();
if (platform.setupDebug && platform.readAddress) { // TODO??
2017-11-11 19:45:32 +00:00
clearBreakpoint();
2017-11-16 17:08:06 +00:00
_resume();
2017-11-11 19:45:32 +00:00
platform.reset();
setupBreakpoint("reset");
2018-06-22 06:24:52 +00:00
if (platform.runEval)
platform.runEval((c) => { return true; }); // break immediately
2018-06-22 06:24:52 +00:00
else
; // TODO???
2017-11-11 19:45:32 +00:00
} else {
platform.reset();
}
2017-01-03 01:42:15 +00:00
}
2017-01-06 16:57:28 +00:00
function _breakExpression() {
2019-05-14 13:38:11 +00:00
var modal = $("#debugExprModal");
var btn = $("#debugExprSubmit");
$("#debugExprInput").val(lastBreakExpr);
$("#debugExprExamples").text(getDebugExprExamples());
modal.modal('show');
btn.off('click').on('click', () => {
var exprs = $("#debugExprInput").val()+"";
modal.modal('hide');
breakExpression(exprs);
});
}
function getDebugExprExamples() : string {
var state = platform.saveState && platform.saveState();
var cpu = state.c;
2019-05-14 13:38:11 +00:00
console.log(cpu, state);
var s = '';
if (cpu.PC) s += "c.PC == 0x" + hex(cpu.PC) + "\n";
if (cpu.SP) s += "c.SP < 0x" + hex(cpu.SP) + "\n";
2019-06-03 14:08:29 +00:00
if (cpu['HL']) s += "c.HL == 0x4000\n";
2019-05-14 13:38:11 +00:00
if (platform.readAddress) s += "this.readAddress(0x1234) == 0x0\n";
if (platform.readVRAMAddress) s += "this.readVRAMAddress(0x1234) != 0x80\n";
if (platform['getRasterScanline']) s += "this.getRasterScanline() > 222\n";
2019-05-14 13:38:11 +00:00
return s;
}
function breakExpression(exprs : string) {
var fn = new Function('c', 'return (' + exprs + ');').bind(platform);
setupBreakpoint();
platform.runEval(fn as DebugEvalCondition);
lastBreakExpr = exprs;
}
2018-07-08 14:07:19 +00:00
function getSymbolAtAddress(a : number) {
2018-09-17 20:09:09 +00:00
var addr2symbol = platform.debugSymbols && platform.debugSymbols.addr2symbol;
if (addr2symbol) {
if (addr2symbol[a]) return addr2symbol[a];
var i=0;
while (--a >= 0) {
i++;
if (addr2symbol[a]) return addr2symbol[a] + '+' + i;
}
}
return '';
}
2017-04-19 01:18:53 +00:00
function updateDebugWindows() {
if (platform.isRunning()) {
projectWindows.tick();
debugTickPaused = false;
} else if (!debugTickPaused) { // final tick after pausing
projectWindows.tick();
debugTickPaused = true;
2017-04-19 01:18:53 +00:00
}
setTimeout(updateDebugWindows, 100);
2017-04-19 01:18:53 +00:00
}
2019-05-01 19:28:15 +00:00
function setWaitDialog(b : boolean) {
if (b) {
setWaitProgress(0);
2019-05-01 19:28:15 +00:00
$("#pleaseWaitModal").modal('show');
} else {
setWaitProgress(1);
2019-05-01 19:28:15 +00:00
$("#pleaseWaitModal").modal('hide');
}
}
2019-05-07 19:37:37 +00:00
function setWaitProgress(prog : number) {
$("#pleaseWaitProgressBar").css('width', (prog*100)+'%').show();
}
2019-05-01 20:47:04 +00:00
var recordingVideo = false;
2017-05-20 19:13:23 +00:00
function _recordVideo() {
2019-05-01 20:47:04 +00:00
if (recordingVideo) return;
loadScript("gif.js/dist/gif.js").then( () => {
var canvas = $("#emulator").find("canvas")[0] as HTMLElement;
2017-05-20 19:13:23 +00:00
if (!canvas) {
alertError("Could not find canvas element to record video!");
2017-05-20 19:13:23 +00:00
return;
}
var rotate = 0;
if (canvas.style && canvas.style.transform) {
if (canvas.style.transform.indexOf("rotate(-90deg)") >= 0)
rotate = -1;
else if (canvas.style.transform.indexOf("rotate(90deg)") >= 0)
rotate = 1;
}
var gif = new GIF({
workerScript: 'gif.js/dist/gif.worker.js',
workers: 4,
quality: 10,
rotate: rotate
});
2017-05-20 19:13:23 +00:00
var img = $('#videoPreviewImage');
2019-05-07 19:37:37 +00:00
gif.on('progress', (prog) => {
setWaitProgress(prog);
});
gif.on('finished', (blob) => {
2017-05-20 19:13:23 +00:00
img.attr('src', URL.createObjectURL(blob));
2019-05-01 19:28:15 +00:00
setWaitDialog(false);
2017-11-16 17:08:06 +00:00
_resume();
2017-05-20 19:13:23 +00:00
$("#videoPreviewModal").modal('show');
});
2019-05-01 20:47:04 +00:00
var intervalMsec = 33;
var maxFrames = 200;
2017-05-20 19:13:23 +00:00
var nframes = 0;
console.log("Recording video", canvas);
2019-05-01 20:47:04 +00:00
$("#emulator").css('backgroundColor', '#cc3333');
var f = () => {
2017-05-20 19:13:23 +00:00
if (nframes++ > maxFrames) {
console.log("Rendering video");
2019-05-01 20:47:04 +00:00
$("#emulator").css('backgroundColor', 'inherit');
2019-05-01 19:28:15 +00:00
setWaitDialog(true);
2017-11-16 17:08:06 +00:00
_pause();
2017-05-20 19:13:23 +00:00
gif.render();
2019-05-01 20:47:04 +00:00
recordingVideo = false;
2017-05-20 19:13:23 +00:00
} else {
gif.addFrame(canvas, {delay: intervalMsec, copy: true});
2017-05-20 19:13:23 +00:00
setTimeout(f, intervalMsec);
2019-05-01 20:47:04 +00:00
recordingVideo = true;
2017-05-20 19:13:23 +00:00
}
};
f();
});
2017-05-20 19:13:23 +00:00
}
export function setFrameRateUI(fps:number) {
2018-02-26 23:18:23 +00:00
platform.setFrameRate(fps);
if (fps > 0.01)
$("#fps_label").text(fps.toFixed(2));
else
$("#fps_label").text("1/"+Math.round(1/fps));
}
function _slowerFrameRate() {
var fps = platform.getFrameRate();
fps = fps/2;
if (fps > 0.00001) setFrameRateUI(fps);
}
function _fasterFrameRate() {
var fps = platform.getFrameRate();
fps = Math.min(60, fps*2);
setFrameRateUI(fps);
}
function _slowestFrameRate() {
setFrameRateUI(60/65536);
}
function _fastestFrameRate() {
_resume();
setFrameRateUI(60);
}
2018-07-04 15:36:32 +00:00
function traceTiming() {
2018-08-02 17:08:37 +00:00
projectWindows.refresh(false);
2018-07-04 15:36:32 +00:00
var wnd = projectWindows.getActive();
if (wnd.getSourceFile && wnd.setTimingResult) { // is editor active?
var analyzer = platform.newCodeAnalyzer();
analyzer.showLoopTimingForPC(0);
wnd.setTimingResult(analyzer);
2018-07-04 15:36:32 +00:00
}
2018-06-30 02:06:14 +00:00
}
function _disableRecording() {
if (recorderActive) {
platform.setRecorder(null);
$("#dbg_record").removeClass("btn_recording");
$("#replaydiv").hide();
recorderActive = false;
}
}
2018-08-23 22:52:56 +00:00
function _resetRecording() {
if (recorderActive) {
stateRecorder.reset();
}
}
function _enableRecording() {
stateRecorder.reset();
platform.setRecorder(stateRecorder);
$("#dbg_record").addClass("btn_recording");
$("#replaydiv").show();
recorderActive = true;
}
function _toggleRecording() {
if (recorderActive) {
_disableRecording();
} else {
_enableRecording();
}
}
function _lookupHelp() {
2018-11-22 16:22:54 +00:00
if (platform.showHelp) {
let tool = platform.getToolForFilename(current_project.mainPath);
2018-11-22 16:22:54 +00:00
platform.showHelp(tool); // TODO: tool, identifier
}
}
function addFileToProject(type, ext, linefn) {
var wnd = projectWindows.getActive();
if (wnd && wnd.insertText) {
2019-05-23 03:23:55 +00:00
bootbox.prompt({
title:"Add "+type+" File to Project",
value:"filename"+ext,
callback:(filename:string) => {
if (filename && filename.trim().length > 0) {
if (!checkEnteredFilename(filename)) return;
var path = filename;
var newline = "\n" + linefn(filename) + "\n";
2019-05-23 12:32:53 +00:00
current_project.loadFiles([path]).then((result) => {
2019-05-23 03:23:55 +00:00
if (result && result.length) {
alertError(filename + " already exists; including anyway");
} else {
current_project.updateFile(path, "\n");
}
wnd.insertText(newline);
refreshWindowList();
});
}
2019-05-23 03:23:55 +00:00
}
});
} else {
alertError("Can't insert text in this window -- switch back to main file");
}
}
function _addIncludeFile() {
var fn = getCurrentMainFilename();
var tool = platform.getToolForFilename(fn);
if (fn.endsWith(".c") || tool == 'sdcc' || tool == 'cc65')
addFileToProject("Header", ".h", (s) => { return '#include "'+s+'"' });
else if (tool == 'dasm' || tool == 'zmac')
addFileToProject("Include File", ".inc", (s) => { return '\tinclude "'+s+'"' });
else if (tool == 'ca65' || tool == 'sdasz80')
addFileToProject("Include File", ".inc", (s) => { return '\t.include "'+s+'"' });
else if (tool == 'verilator')
addFileToProject("Verilog File", ".v", (s) => { return '`include "'+s+'"' });
else
alertError("Can't add include file to this project type (" + tool + ")");
}
function _addLinkFile() {
var fn = getCurrentMainFilename();
var tool = platform.getToolForFilename(fn);
if (fn.endsWith(".c") || tool == 'sdcc' || tool == 'cc65')
addFileToProject("Linked C (or .s)", ".c", (s) => { return '//#link "'+s+'"' });
2019-05-23 03:23:55 +00:00
else if (fn.endsWith("asm") || fn.endsWith(".s") || tool == 'ca65')
addFileToProject("Linked ASM", ".inc", (s) => { return ';#link "'+s+'"' });
else
alertError("Can't add linked file to this project type (" + tool + ")");
}
function setupDebugControls() {
// create toolbar buttons
uitoolbar = new Toolbar($("#toolbar")[0], null);
uitoolbar.grp.prop('id','debug_bar');
2019-08-05 15:15:26 +00:00
uitoolbar.add('ctrl+alt+r', 'Reset', 'glyphicon-refresh', resetAndDebug).prop('id','dbg_reset');
uitoolbar.add('ctrl+alt+,', 'Pause', 'glyphicon-pause', pause).prop('id','dbg_pause');
uitoolbar.add('ctrl+alt+.', 'Resume', 'glyphicon-play', resume).prop('id','dbg_go');
if (platform.step) {
uitoolbar.add('ctrl+alt+s', 'Single Step', 'glyphicon-step-forward', singleStep).prop('id','dbg_step');
}
if (platform.runToVsync) {
2019-08-05 15:15:26 +00:00
uitoolbar.add('ctrl+alt+n', 'Next Frame/Interrupt', 'glyphicon-forward', singleFrameStep).prop('id','dbg_tovsync');
}
2018-11-18 17:30:41 +00:00
if ((platform.runEval || platform.runToPC) && !platform_id.startsWith('verilog')) {
uitoolbar.add('ctrl+alt+l', 'Run To Line', 'glyphicon-save', runToCursor).prop('id','dbg_toline');
}
if (platform.runUntilReturn) {
uitoolbar.add('ctrl+alt+o', 'Step Out of Subroutine', 'glyphicon-hand-up', runUntilReturn).prop('id','dbg_stepout');
}
if (platform.stepBack) {
uitoolbar.add('ctrl+alt+b', 'Step Backwards', 'glyphicon-step-backward', runStepBackwards).prop('id','dbg_stepback');
}
uitoolbar.newGroup();
if (platform.newCodeAnalyzer) {
uitoolbar.add(null, 'Analyze CPU Timing', 'glyphicon-time', traceTiming);
}
// add menu clicks
2016-12-30 23:51:15 +00:00
$(".dropdown-menu").collapse({toggle: false});
$("#item_new_file").click(_createNewFile);
2018-06-26 23:57:03 +00:00
$("#item_upload_file").click(_uploadNewFile);
$("#item_github_login").click(_loginToGithub);
$("#item_github_logout").click(_logoutOfGithub);
$("#item_github_import").click(_importProjectFromGithub);
2019-05-07 19:37:37 +00:00
$("#item_github_publish").click(_publishProjectToGithub);
$("#item_github_push").click(_pushProjectToGithub);
$("#item_github_pull").click(_pullProjectFromGithub);
2019-05-16 14:08:09 +00:00
$("#item_repo_delete").click(_deleteRepository);
$("#item_share_file").click(_shareEmbedLink);
2018-08-21 14:16:47 +00:00
$("#item_reset_file").click(_revertFile);
2018-12-08 00:28:11 +00:00
$("#item_rename_file").click(_renameFile);
$("#item_delete_file").click(_deleteFile);
2017-11-24 19:14:22 +00:00
if (platform.runEval)
$("#item_debug_expr").click(_breakExpression).show();
else
$("#item_debug_expr").hide();
2017-02-02 19:11:52 +00:00
$("#item_download_rom").click(_downloadROMImage);
$("#item_download_file").click(_downloadSourceFile);
2018-08-25 18:29:51 +00:00
$("#item_download_zip").click(_downloadProjectZipFile);
$("#item_download_allzip").click(_downloadAllFilesZipFile);
2017-05-20 19:13:23 +00:00
$("#item_record_video").click(_recordVideo);
2019-05-08 23:15:26 +00:00
if (platform_id.startsWith('apple2'))
2018-09-25 23:46:24 +00:00
$("#item_export_cassette").click(_downloadCassetteFile);
else
$("#item_export_cassette").hide();
2018-02-26 23:18:23 +00:00
if (platform.setFrameRate && platform.getFrameRate) {
$("#dbg_slower").click(_slowerFrameRate);
$("#dbg_faster").click(_fasterFrameRate);
$("#dbg_slowest").click(_slowestFrameRate);
$("#dbg_fastest").click(_fastestFrameRate);
2018-02-26 23:18:23 +00:00
}
$("#item_addfile_include").click(_addIncludeFile);
$("#item_addfile_link").click(_addLinkFile);
2017-04-19 01:18:53 +00:00
updateDebugWindows();
// show help button?
if (platform.showHelp) {
uitoolbar.add('ctrl+alt+?', 'Show Help', 'glyphicon-question-sign', _lookupHelp);
}
// setup replay slider
if (platform.setRecorder && platform.advance) {
setupReplaySlider();
}
}
function setupReplaySlider() {
var replayslider = $("#replayslider");
var replayframeno = $("#replay_frame");
var updateFrameNo = (n) => {
replayframeno.text(n+"");
};
var sliderChanged = (e) => {
_pause();
var frame = (<any>e.target).value;
if (stateRecorder.loadFrame(frame)) {
updateFrameNo(frame);
projectWindows.tick();
}
};
var setFrameTo = (frame:number) => {
_pause();
if (stateRecorder.loadFrame(frame)) {
replayslider.val(frame);
updateFrameNo(frame);
projectWindows.tick();
console.log('seek to frame',frame);
}
};
stateRecorder.callbackStateChanged = () => {
replayslider.attr('min', 1);
replayslider.attr('max', stateRecorder.numFrames());
2018-08-23 22:52:56 +00:00
replayslider.val(stateRecorder.currentFrame());
updateFrameNo(stateRecorder.currentFrame());
};
replayslider.on('input', sliderChanged);
replayslider.on('change', sliderChanged);
$("#replay_min").click(() => { setFrameTo(1) });
$("#replay_max").click(() => { setFrameTo(stateRecorder.numFrames()); });
$("#replay_back").click(() => { setFrameTo(parseInt(replayslider.val().toString()) - 1); });
$("#replay_fwd").click(() => { setFrameTo(parseInt(replayslider.val().toString()) + 1); });
$("#replay_bar").show();
uitoolbar.add('ctrl+alt+0', 'Start/Stop Replay Recording', 'glyphicon-record', _toggleRecording).prop('id','dbg_record');
2016-12-16 01:21:51 +00:00
}
2018-12-30 18:57:33 +00:00
function isLandscape() {
try {
2019-02-15 17:33:13 +00:00
var object = window.screen['orientation'] || window.screen['msOrientation'] || window.screen['mozOrientation'] || null;
2018-12-30 18:57:33 +00:00
if (object) {
if (object.type.indexOf('landscape') !== -1) { return true; }
if (object.type.indexOf('portrait') !== -1) { return false; }
}
if ('orientation' in window) {
var value = window.orientation;
if (value === 0 || value === 180) {
return false;
} else if (value === 90 || value === 270) {
return true;
}
}
} catch (e) { }
// fallback to comparing width to height
return window.innerWidth > window.innerHeight;
}
2016-12-18 20:59:31 +00:00
function showWelcomeMessage() {
if (hasLocalStorage && !localStorage.getItem("8bitworkshop.hello")) {
2016-12-30 23:51:15 +00:00
// Instance the tour
2019-05-08 23:15:26 +00:00
var is_vcs = platform_id.startsWith('vcs');
var steps = [
2016-12-30 23:51:15 +00:00
{
2019-08-15 16:18:16 +00:00
element: "#platformsMenuButton",
placement: 'right',
2016-12-30 23:51:15 +00:00
title: "Welcome to 8bitworkshop!",
content: "You're currently on the \"<b>" + platform_id + "</b>\" platform. You can choose a different one from the menu."
},
{
element: "#preset_select",
title: "Project Selector",
content: "You can choose different code examples, create your own files, or import projects from GitHub."
2019-08-15 16:18:16 +00:00
},
{
element: "#workspace",
title: "Code Editor",
2017-04-20 00:55:13 +00:00
content: is_vcs ? "Type your 6502 assembly code into the editor, and it'll be assembled in real-time. All changes are saved to browser local storage."
: "Type your source code into the editor, and it'll be compiled in real-time. All changes are saved to browser local storage."
2016-12-30 23:51:15 +00:00
},
{
element: "#emulator",
placement: 'left',
2017-04-20 00:55:13 +00:00
title: "Emulator",
2019-08-15 16:18:16 +00:00
content: "We'll load your compiled code into the emulator whenever you make changes."
2016-12-30 23:51:15 +00:00
},
{
element: "#debug_bar",
placement: 'bottom',
title: "Debug Tools",
2017-04-20 00:55:13 +00:00
content: "Use these buttons to set breakpoints, single step through code, pause/resume, and use debugging tools."
2016-12-30 23:51:15 +00:00
},
{
element: "#dropdownMenuButton",
title: "Main Menu",
2019-08-15 16:18:16 +00:00
content: "Click the menu to create new files, download your code, or share your work with others."
2018-12-30 18:57:33 +00:00
},
{
element: "#sidebar",
title: "Sidebar",
2019-08-15 16:18:16 +00:00
content: "Pull right to expose the sidebar. It lets you switch between source files, view assembly listings, and use other tools like Disassembler, Memory Browser, and Asset Editor."
2018-12-30 18:57:33 +00:00
}
];
2018-07-07 14:55:27 +00:00
steps.push({
element: "#booksMenuButton",
2019-08-22 17:40:53 +00:00
placement: 'left',
title: "Books",
2019-08-15 16:18:16 +00:00
content: "Get some books that explain how to program all of this stuff, and write some games!"
2018-07-07 14:55:27 +00:00
});
2018-12-30 18:57:33 +00:00
if (!isLandscape()) {
steps.unshift({
element: "#controls_top",
placement: 'bottom',
title: "Portrait mode detected",
content: "This site works best on desktop browsers. For best results, rotate your device to landscape orientation."
});
}
var tour = new Tour({
autoscroll:false,
//storage:false,
steps:steps
});
setTimeout(() => { tour.start(); }, 2000);
2016-12-18 20:59:31 +00:00
}
}
2016-12-16 01:21:51 +00:00
///////////////////////////////////////////////////
2018-07-08 03:10:51 +00:00
var qs = (function (a : string[]) {
if (!a || a.length == 0)
2016-12-16 01:21:51 +00:00
return {};
var b = {};
for (var i = 0; i < a.length; ++i) {
var p = a[i].split('=', 2);
if (p.length == 1)
b[p[0]] = "";
else
b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
}
return b;
})(window.location.search.substr(1).split('&'));
// catch errors
function installErrorHandler() {
if (typeof window.onerror == "object") {
window.onerror = function (msgevent, url, line, col, error) {
2019-08-22 17:14:35 +00:00
var msgstr = msgevent+"";
2019-03-21 00:45:03 +00:00
console.log(msgevent, url, line, col, error);
2019-05-17 19:45:19 +00:00
// emulation threw EmuHalt
2019-03-21 00:45:03 +00:00
if (error instanceof EmuHalt || msgstr.indexOf("CPU STOP") >= 0) {
showErrorAlert([ {msg:msgstr, line:0} ]);
2019-08-16 09:53:49 +00:00
uiDebugCallback(platform.saveState && platform.saveState());
setDebugButtonState("pause", "stopped");
2019-03-21 00:45:03 +00:00
} else {
2019-05-17 19:45:19 +00:00
// send exception msg to GA
2019-06-03 14:08:29 +00:00
var msg = msgstr;
2019-06-08 16:01:22 +00:00
//if (typeof error == 'string') msg += ": " + error;
2019-08-16 09:53:49 +00:00
if (url) msg += " " + url;
2019-06-03 14:08:29 +00:00
if (line) msg += " (" + line + ":" + col + ")";
2019-05-17 19:45:19 +00:00
if (msg.length > 256) { msg = msg.substring(0, 256); }
if (ga) ga('send', 'exception', {
'exDescription': msg,
'exFatal': true
});
$.get("/error?msg=" + encodeURIComponent(msg), "text");
2019-05-17 19:45:19 +00:00
alertError(msg);
2019-03-21 00:45:03 +00:00
}
_pause();
};
2019-08-22 17:14:35 +00:00
}
if (typeof window.onunhandledrejection == "object") {
window.onunhandledrejection = function(event) {
var msg = (event && event.reason) + "";
if (ga) ga('send', 'exception', {
'exDescription': msg,
'exFatal': true
});
alertError(msg);
}
}
}
function uninstallErrorHandler() {
window.onerror = null;
}
2019-05-22 21:03:56 +00:00
function gotoNewLocation(replaceHistory? : boolean) {
uninstallErrorHandler();
2019-05-22 21:03:56 +00:00
if (replaceHistory)
window.location.replace("?" + $.param(qs));
else
window.location.href = "?" + $.param(qs);
}
function replaceURLState() {
if (platform_id) qs['platform'] = platform_id;
2019-08-20 23:13:41 +00:00
delete qs['']; // remove null parameter
history.replaceState({}, "", "?" + $.param(qs));
}
function addPageFocusHandlers() {
2017-05-02 13:09:53 +00:00
var hidden = false;
document.addEventListener("visibilitychange", () => {
2017-05-02 13:09:53 +00:00
if (document.visibilityState == 'hidden' && platform.isRunning()) {
2017-11-16 17:08:06 +00:00
_pause();
2017-05-02 13:09:53 +00:00
hidden = true;
} else if (document.visibilityState == 'visible' && hidden) {
2017-11-16 17:08:06 +00:00
_resume();
2017-05-02 13:09:53 +00:00
hidden = false;
}
});
$(window).on("focus", () => {
2017-05-02 13:09:53 +00:00
if (hidden) {
2017-11-16 17:08:06 +00:00
_resume();
2017-05-02 13:09:53 +00:00
hidden = false;
}
});
$(window).on("blur", () => {
2017-05-02 13:09:53 +00:00
if (platform.isRunning()) {
2017-11-16 17:08:06 +00:00
_pause();
2017-05-02 13:09:53 +00:00
hidden = true;
}
});
}
// TODO: merge w/ embed.html somehow?
2019-05-17 04:42:52 +00:00
function showInstructions() {
var div = $(document).find(".emucontrols-" + getRootBasePlatform(platform_id));
var vcanvas = $("#emulator").find("canvas");
if (vcanvas) {
vcanvas.on('focus', () => {
if (platform.isRunning()) {
div.fadeIn(200);
// toggle sound for browser autoplay
platform.resume();
}
});
vcanvas.on('blur', () => {
div.fadeOut(200);
});
}
2019-05-17 04:42:52 +00:00
}
2019-05-17 19:45:19 +00:00
function installGAHooks() {
if (ga) {
$(".dropdown-item").click((e) => {
if (e.target && e.target.id) {
gaEvent('menu', e.target.id);
2019-05-17 19:45:19 +00:00
}
});
ga('send', 'pageview', location.pathname+'?platform='+platform_id+(repo_id?('&repo='+repo_id):('&file='+qs['file'])));
2019-05-17 19:45:19 +00:00
}
}
2017-01-14 16:14:25 +00:00
function startPlatform() {
2017-01-29 21:06:05 +00:00
if (!PLATFORMS[platform_id]) throw Error("Invalid platform '" + platform_id + "'.");
2019-05-21 17:06:48 +00:00
platform = new PLATFORMS[platform_id]($("#emuscreen")[0]);
setPlatformUI();
stateRecorder = new StateRecorderImpl(platform);
2017-01-14 16:14:25 +00:00
PRESETS = platform.getPresets();
if (!qs['file']) {
// try to load last file (redirect)
var lastid;
if (hasLocalStorage) {
lastid = localStorage.getItem("__lastid_"+store_id);
}
// load first preset file, unless we're in a repo
var defaultfile = lastid || (repo_id ? null : PRESETS[0].id);
qs['file'] = defaultfile || 'DEFAULT';
if (!defaultfile) {
alertError("There is no default main file for this project. Try selecting one from the pulldown.");
}
}
// legacy vcs stuff
if (platform_id == 'vcs' && qs['file'].startsWith('examples/') && !qs['file'].endsWith('.a')) {
qs['file'] += '.a';
}
// start platform and load file
replaceURLState();
platform.start();
// TODO: ordering of loads?
2019-08-20 23:13:41 +00:00
installGAHooks();
loadBIOSFromProject();
initProject();
loadProject(qs['file']);
setupDebugControls();
updateSelector();
addPageFocusHandlers();
2019-05-17 04:42:52 +00:00
showInstructions();
revealTopBar();
return true;
2017-01-14 16:14:25 +00:00
}
function revealTopBar() {
setTimeout(() => { $("#controls_dynamic").css('visibility','inherit'); }, 250);
}
2018-11-23 18:29:11 +00:00
export function setupSplits() {
const splitName = 'workspace-split3-' + platform_id;
var sizes = [0, 50, 50];
if (!platform_id.startsWith('vcs'))
sizes = [12, 44, 44];
var sizesStr = hasLocalStorage && localStorage.getItem(splitName);
2018-11-23 18:29:11 +00:00
if (sizesStr) {
try {
sizes = JSON.parse(sizesStr);
} catch (e) { console.log(e); }
}
var split = Split(['#sidebar', '#workspace', '#emulator'], {
2018-11-23 18:29:11 +00:00
sizes: sizes,
minSize: [0, 250, 250],
2018-11-26 11:12:45 +00:00
onDrag: () => {
if (platform && platform.resize) platform.resize();
},
onDragEnd: () => {
if (hasLocalStorage)
localStorage.setItem(splitName, JSON.stringify(split.getSizes()))
if (projectWindows) projectWindows.resize();
2018-11-23 18:29:11 +00:00
},
});
}
2019-05-01 19:28:15 +00:00
function loadImportedURL(url : string) {
// TODO: zip file?
setWaitDialog(true);
getWithBinary(url, (data) => {
if (data) {
var path = 'shared/' + getFilenameForPath(url); // TODO: shared prefix?
2019-05-01 19:28:15 +00:00
// TODO: progress dialog
console.log("Importing " + data.length + " bytes as " + path);
store.getItem(path, (err, olddata) => {
setWaitDialog(false);
if (!olddata || confirm("Replace existing file '" + path + "'?")) {
store.setItem(path, data, (err, result) => {
if (err)
alert(err+""); // need to wait
2019-05-01 19:28:15 +00:00
if (result != null) {
delete qs['importURL'];
qs['file'] = path;
replaceURLState();
loadAndStartPlatform();
}
});
}
});
} else {
alertError("Could not load source code from URL: " + url);
2019-05-01 19:28:15 +00:00
setWaitDialog(false);
}
}, 'text');
}
function setPlatformUI() {
2019-08-27 16:12:56 +00:00
var name = platform.getPlatformName && platform.getPlatformName();
var menuitem = $('a[href="?platform='+platform_id+'"]');
if (menuitem.length) {
menuitem.addClass("dropdown-item-checked");
name = name || menuitem.text() || name;
}
$(".platform_name").text(name || platform_id);
}
2017-01-03 01:42:15 +00:00
// start
export function startUI(loadplatform : boolean) {
installErrorHandler();
// import from github?
if (qs['githubURL']) {
2019-05-22 21:03:56 +00:00
importProjectFromGithub(qs['githubURL'], true);
return;
}
// add default platform?
platform_id = qs['platform'] || (hasLocalStorage && localStorage.getItem("__lastplatform"));
if (!platform_id) {
platform_id = qs['platform'] = "vcs";
}
// lookup repository for this platform
repo_id = qs['repo'] || (hasLocalStorage && localStorage.getItem("__lastrepo_" + platform_id));
if (hasLocalStorage && repo_id && repo_id !== '/') {
var repo = getRepos()[repo_id];
if (repo) {
qs['repo'] = repo_id;
if (repo.platform_id)
qs['platform'] = platform_id = repo.platform_id;
if (!qs['file'])
qs['file'] = repo.mainPath;
}
} else {
repo_id = '';
delete qs['repo'];
}
2018-11-23 18:29:11 +00:00
setupSplits();
2019-05-08 23:15:26 +00:00
// create store
store_id = repo_id || getBasePlatform(platform_id);
store = createNewPersistentStore(store_id, (store) => {
// is this an importURL?
if (qs['importURL']) {
loadImportedURL(qs['importURL']);
return;
}
// is vcs? convert legacy stuff
convertLegacyVCS(store);
2019-05-08 23:15:26 +00:00
// load and start platform object
if (loadplatform) {
loadAndStartPlatform();
} else {
startPlatform();
revealTopBar();
2019-05-08 23:15:26 +00:00
}
});
2016-12-16 01:21:51 +00:00
}
2019-05-01 19:28:15 +00:00
function loadAndStartPlatform() {
var platformfn = 'gen/platform/' + platform_id.split(/[.-]/)[0] + '.js'; // required file
var machinefn = platformfn.replace('/platform/', '/machine/'); // optional file
loadScript(platformfn).then( () => {
return loadScript(machinefn).catch(() => { console.log('skipped',machinefn); }); // optional file skipped
}).then( () => {
console.log("starting platform", platform_id); // loaded required <platform_id>.js file
try {
startPlatform();
showWelcomeMessage();
document.title = document.title + " [" + platform_id + "] - " + (repo_id?('['+repo_id+'] - '):'') + current_project.mainPath;
} finally {
revealTopBar();
}
}).catch( () => {
alertError('Platform "' + platform_id + '" not supported.');
2019-05-01 19:28:15 +00:00
});
}
// TODO: remove eventually
function convertLegacyVCS(store) {
if (platform_id == 'vcs' && hasLocalStorage && !localStorage.getItem("__migratevcs")) {
store.keys().then((keys:string[]) => {
keys.forEach((key) => {
if (key.startsWith('examples/') && !key.endsWith('.a')) {
store.getItem(key).then( (val) => {
if (val) {
return store.setItem(key+'.a', val);
}
});
}
});
localStorage.setItem("__migratevcs", "1");
})
}
}
2019-05-18 22:33:38 +00:00
2019-06-03 14:08:29 +00:00
// HTTPS REDIRECT
2019-05-18 22:33:38 +00:00
const useHTTPSCookieName = "__use_https";
2019-05-26 03:15:25 +00:00
function setHTTPSCookie(val : number) {
document.cookie = useHTTPSCookieName + "=" + val + ";domain=8bitworkshop.com;path=/;max-age=315360000";
}
2019-05-21 19:41:17 +00:00
function shouldRedirectHTTPS() : boolean {
// cookie set? either true or false
var shouldRedir = getCookie(useHTTPSCookieName);
if (typeof shouldRedir === 'string') {
return !!shouldRedir; // convert to bool
}
// set a 10yr cookie, value depends on if it's our first time here
var val = hasLocalStorage && !localStorage.getItem("__lastplatform") ? 1 : 0;
2019-05-26 03:15:25 +00:00
setHTTPSCookie(val);
2019-05-21 19:41:17 +00:00
return !!val;
2019-05-18 22:33:38 +00:00
}
2019-05-26 03:15:25 +00:00
function _switchToHTTPS() {
bootbox.confirm('<p>Do you want to force the browser to use HTTPS from now on?</p>'+
'<p>WARNING: This will make all of your local files unavailable, so you should "Download All Changes" first for each platform where you have done work.</p>'+
'<p>You can go back to HTTP by setting the "'+useHTTPSCookieName+'" cookie to 0.</p>', (ok) => {
if (ok) {
setHTTPSCookie(1);
redirectToHTTPS();
}
});
}
2019-05-18 22:33:38 +00:00
function redirectToHTTPS() {
if (window.location.protocol == 'http:' && window.location.host == '8bitworkshop.com') {
if (shouldRedirectHTTPS()) {
window.location.replace(window.location.href.replace(/^http:/, 'https:'));
} else {
2019-05-26 03:15:25 +00:00
$("#item_switch_https").click(_switchToHTTPS).show();
2019-05-18 22:33:38 +00:00
}
}
}
// redirect to HTTPS after script loads?
redirectToHTTPS();