diff --git a/bootstrap/js/bootbox.all.min.js b/bootstrap/js/bootbox.all.min.js new file mode 100644 index 00000000..feecd0a8 --- /dev/null +++ b/bootstrap/js/bootbox.all.min.js @@ -0,0 +1,6 @@ +/** + * bootbox.js 5.1.3 + * + * http://bootboxjs.com/license.txt + */ +!function(t,e){'use strict';'function'==typeof define&&define.amd?define(['jquery'],e):'object'==typeof exports?module.exports=e(require('jquery')):t.bootbox=e(t.jQuery)}(this,function e(p,u){'use strict';var r,n,i,l;Object.keys||(Object.keys=(r=Object.prototype.hasOwnProperty,n=!{toString:null}.propertyIsEnumerable('toString'),l=(i=['toString','toLocaleString','valueOf','hasOwnProperty','isPrototypeOf','propertyIsEnumerable','constructor']).length,function(t){if('function'!=typeof t&&('object'!=typeof t||null===t))throw new TypeError('Object.keys called on non-object');var e,o,a=[];for(e in t)r.call(t,e)&&a.push(e);if(n)for(o=0;o
",header:"
",footer:'',closeButton:'',form:'
',button:'',option:'',promptMessage:'
',inputs:{text:'',textarea:'',email:'',select:'',checkbox:'
',radio:'
',date:'',time:'',number:'',password:'',range:''}},m={locale:'en',backdrop:'static',animate:!0,className:null,closeButton:!0,show:!0,container:'body',value:'',inputType:'text',swapButtonOrder:!1,centerVertical:!1,multiple:!1,scrollable:!1};function c(t,e,o){return p.extend(!0,{},t,function(t,e){var o=t.length,a={};if(o<1||2').attr('label',e.group)),o=i[e.group]);var a=p(f.option);a.attr('value',e.value).text(e.text),o.append(a)}),O(i,function(t,e){n.append(e)}),n.val(r.value);break;case'checkbox':var l=p.isArray(r.value)?r.value:[r.value];if(!(a=r.inputOptions||[]).length)throw new Error('prompt with "inputType" set to "checkbox" requires at least one option');n=p('
'),O(a,function(t,o){if(o.value===u||o.text===u)throw new Error('each option needs a "value" property and a "text" property');var a=p(f.inputs[r.inputType]);a.find('input').attr('value',o.value),a.find('label').append('\n'+o.text),O(l,function(t,e){e===o.value&&a.find('input').prop('checked',!0)}),n.append(a)});break;case'radio':if(r.value!==u&&p.isArray(r.value))throw new Error('prompt with "inputType" set to "radio" requires a single, non-array value for "value"');if(!(a=r.inputOptions||[]).length)throw new Error('prompt with "inputType" set to "radio" requires at least one option');n=p('
');var s=!0;O(a,function(t,e){if(e.value===u||e.text===u)throw new Error('each option needs a "value" property and a "text" property');var o=p(f.inputs[r.inputType]);o.find('input').attr('value',e.value),o.find('label').append('\n'+e.text),r.value!==u&&e.value===r.value&&(o.find('input').prop('checked',!0),s=!1),n.append(o)}),s&&n.find('input[type="radio"]').first().prop('checked',!0)}if(t.append(n),t.on('submit',function(t){t.preventDefault(),t.stopPropagation(),e.find('.bootbox-accept').trigger('click')}),''!==p.trim(r.message)){var c=p(f.promptMessage).html(r.message);t.prepend(c),r.message=t}else r.message=t;return(e=d.dialog(r)).off('shown.bs.modal'),e.on('shown.bs.modal',function(){n.focus()}),!0===o&&e.modal('show'),e},d.addLocale('en',{OK:'OK',CANCEL:'Cancel',CONFIRM:'OK'}),d}); \ No newline at end of file diff --git a/doc/notes.txt b/doc/notes.txt index 056e0deb..e7314257 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -153,10 +153,8 @@ TODO: - support projects with subdirectories, file list? - emulator needs reset shortcut for nes - local/ files in repository? - - build ca65 projects? https://github.com/pinobatch/thwaite-nes/blob/master/makefile + - build ca65 projects w/ no crt0? https://github.com/pinobatch/thwaite-nes/blob/master/makefile - switching platform of a repo? - - retaining repo_id in localstorage? - - don't like alert() after pushing - should have a preview of commit diff --git a/index.html b/index.html index 570c31be..9b3b180e 100644 --- a/index.html +++ b/index.html @@ -364,6 +364,7 @@ if (window.location.host.endsWith('8bitworkshop.com')) { + diff --git a/package-lock.json b/package-lock.json index 6bd926d1..b6d59499 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,18 @@ { "name": "8bitworkshop", - "version": "3.3.1", + "version": "3.4.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/bootbox": { + "version": "4.4.36", + "resolved": "https://registry.npmjs.org/@types/bootbox/-/bootbox-4.4.36.tgz", + "integrity": "sha512-RNNb0guOAm9RXPMOHlRILqLfuNaSjL/VEXej6izAwpWrN7pTtHLLhmh/eCkCLaItcrXuivXNdJLQT2okBT0Dog==", + "dev": true, + "requires": { + "@types/jquery": "*" + } + }, "@types/bootstrap": { "version": "3.3.42", "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-3.3.42.tgz", @@ -315,6 +324,15 @@ "safer-buffer": "^2.1.0" } }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -382,6 +400,28 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fetch-vcr": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fetch-vcr/-/fetch-vcr-1.1.2.tgz", + "integrity": "sha512-bFOx3+5YtViximcqhG05tqMlsyPRXNOmiToDCf6TyVUCKHYP/vGPmn0HUhGVNd1jI0KpElwz+RH3X/ZQo0Asfg==", + "dev": true, + "requires": { + "node-fetch": "^1.6.3", + "whatwg-fetch": "^2.0.3" + }, + "dependencies": { + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "dev": true, + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + } + } + }, "filepath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/filepath/-/filepath-1.1.0.tgz", @@ -514,6 +554,12 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -527,9 +573,9 @@ "dev": true }, "jquery": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", - "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==", "dev": true }, "jsbn": { @@ -763,6 +809,12 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node-fetch": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.5.0.tgz", + "integrity": "sha512-YuZKluhWGJwCcUu4RlZstdAxr8bFfOVHakc1mplwHkk8J+tqM1Y5yraYvIUpeX8aY7+crCwiELJq7Vl0o0LWXw==", + "dev": true + }, "nwsapi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.0.tgz", @@ -775,6 +827,17 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, + "octokat": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/octokat/-/octokat-0.10.0.tgz", + "integrity": "sha512-VJ21L1gMlByYMurduLYiOcI8AwlZkUV8OXRN8pMXsbkIqIVqn0tgdTfxzWM9spX4VJTTG02OgqwDTqQsOmDing==", + "dev": true, + "requires": { + "fetch-vcr": "^1.1.0", + "lodash": "^4.16.4", + "node-fetch": "^2.0.0" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1113,6 +1176,12 @@ "iconv-lite": "0.4.24" } }, + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==", + "dev": true + }, "whatwg-mimetype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", diff --git a/package.json b/package.json index f1d4ff3d..26dcbc26 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "author": "Steven Hugg", "dependencies": {}, "devDependencies": { + "@types/bootbox": "^4.4.36", "@types/bootstrap": "^3.3.42", "@types/jquery": "^3.3.29", "@types/w2ui": "^1.4.x", diff --git a/src/services.ts b/src/services.ts index 2cd28526..0c42543c 100644 --- a/src/services.ts +++ b/src/services.ts @@ -80,7 +80,7 @@ export class GithubService { console.log("Stored GitHub OAUTH key"); }).catch( (error) => { console.log(error); - alert("Could not login to GitHub: " + error); + bootbox.alert("Could not login to GitHub: " + error); }); } diff --git a/src/ui.ts b/src/ui.ts index 96c7cc48..d1f2ab51 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -74,6 +74,13 @@ var TOOL_TO_SOURCE_STYLE = { 'xasm6809': 'z80' } +function alertError(s:string) { + bootbox.alert(s); +} +function alertInfo(s:string) { + bootbox.alert(s); +} + function newWorker() : Worker { return new Worker("./src/worker/loader.js"); } @@ -98,6 +105,10 @@ function getCurrentPresetTitle() : string { function setLastPreset(id:string) { if (hasLocalStorage) { + if (repo_id) + localStorage.setItem("__lastrepo", repo_id); + else + localStorage.removeItem("__lastrepo"); localStorage.setItem("__lastplatform", platform_id); localStorage.setItem("__lastid_"+store_id, id); } @@ -237,7 +248,7 @@ function loadProject(preset_id:string) { // load files from storage or web URLs current_project.loadFiles([preset_id], function(err, result) { if (err) { - alert(err); + alertError(err); } else if (result && result.length) { // we need this to build create functions for the editor refreshWindowList(); @@ -250,9 +261,9 @@ function loadProject(preset_id:string) { } function reloadProject(id:string) { - // leave repository == '..' - if (id == '..') { - qs = {}; + // leave repository == '/' + if (id == '/') { + qs = {repo:'/'}; } else if (id.indexOf('://') >= 0) { var urlparse = parseGithubURL(id); if (urlparse) { @@ -269,13 +280,13 @@ function getSkeletonFile(fileid:string) : Promise { var ext = platform.getToolForFilename(fileid); // TODO: .mame return $.get( "presets/"+getBasePlatform(platform_id)+"/skeleton."+ext, 'text').catch((e) => { - alert("Could not load skeleton for " + platform_id + "/" + ext + "; using blank file"); + alertError("Could not load skeleton for " + platform_id + "/" + ext + "; using blank file"); }); } function checkEnteredFilename(fn : string) : boolean { if (fn.indexOf(" ") >= 0) { - alert("No spaces in filenames, please."); + alertError("No spaces in filenames, please."); return false; } return true; @@ -283,19 +294,24 @@ function checkEnteredFilename(fn : string) : boolean { function _createNewFile(e) { // TODO: support spaces - var filename = prompt("Create New File", "newfile" + platform.getDefaultExtension()); - if (filename && filename.trim().length > 0) { - if (!checkEnteredFilename(filename)) return; - if (filename.indexOf(".") < 0) { - filename += platform.getDefaultExtension(); + 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 = "local/" + filename; + getSkeletonFile(path).then( (result) => { + return store.setItem(path, result || "\n"); + }).then(() => { + reloadProject("local/" + filename); + }); + } } - var path = "local/" + filename; - getSkeletonFile(path).then( (result) => { - return store.setItem(path, result || "\n"); - }).then(() => { - reloadProject("local/" + filename); - }); - } + } as any); return true; } @@ -315,7 +331,7 @@ function handleFileUpload(files: File[]) { gotoNewLocation(); } else { updateSelector(); - alert("Files uploaded."); + alertInfo("Files uploaded."); } } else { var path = "local/" + f.name; @@ -333,7 +349,7 @@ function handleFileUpload(files: File[]) { // TODO: use projectWindows uploadFile() store.setItem(path, data, function(err, result) { if (err) - alert("Error uploading " + path + ": " + err); + alertError("Error uploading " + path + ": " + err); else { console.log("Uploaded " + path + " " + data.length + " bytes"); if (index == 1) { @@ -386,7 +402,7 @@ function getGithubService() { function getBoundGithubURL() : string { var toks = (repo_id||'').split('/'); if (toks.length != 2) { - alert("You are not in a GitHub repository. Choose Import or Publish first."); + alertError("You are not in a GitHub repository. Choose Import or Publish first."); return null; } return 'https://github.com/' + toks[0] + '/' + toks[1]; @@ -396,7 +412,7 @@ function importProjectFromGithub(githuburl:string) { var sess : GHSession; var urlparse = parseGithubURL(githuburl); if (!urlparse) { - alert('Could not parse Github URL.'); + alertError('Could not parse Github URL.'); return; } // redirect to repo if exists @@ -422,7 +438,7 @@ function importProjectFromGithub(githuburl:string) { }).catch( (e) => { setWaitDialog(false); console.log(e); - alert("Could not import " + githuburl + ": " + e); + alertError("Could not import " + githuburl + ": " + e); }); } @@ -439,7 +455,7 @@ function _importProjectFromGithub(e) { function _publishProjectToGithub(e) { if (repo_id) { - alert("This project (" + current_project.mainPath + ") is already bound to a Github repository. Choose 'Push Changes' to update."); + alertError("This project (" + current_project.mainPath + ") is already bound to a Github repository. Choose 'Push Changes' to update."); return; } var modal = $("#publishGithubModal"); @@ -466,7 +482,7 @@ function _publishProjectToGithub(e) { }).catch( (e) => { setWaitDialog(false); console.log(e); - alert("Could not publish GitHub repository: " + e); + alertError("Could not publish GitHub repository: " + e); }); }); } @@ -512,22 +528,22 @@ function pushChangesToGithub(message:string) { return getGithubService().commitPush(ghurl, message, files); }).then( (sess) => { setWaitDialog(false); - alert("Pushed files to " + ghurl); + alertInfo("Pushed files to " + ghurl); return sess; }).catch( (e) => { setWaitDialog(false); console.log(e); - alert("Could not push GitHub repository: " + e); + alertError("Could not push GitHub repository: " + e); }); } function _shareEmbedLink(e) { if (current_output == null) { // TODO - alert("Please fix errors before sharing."); + alertError("Please fix errors before sharing."); return true; } if (!(current_output instanceof Uint8Array)) { - alert("Can't share a Verilog executable yet. (It's not actually a ROM...)"); + alertError("Can't share a Verilog executable yet. (It's not actually a ROM...)"); return true; } loadClipboardLibrary(); @@ -573,12 +589,12 @@ function get8bitworkshopLink(linkqs : string, fn : string) { function _downloadCassetteFile(e) { if (current_output == null) { // TODO - alert("Please fix errors before exporting."); + alertError("Please fix errors before exporting."); return true; } var addr = compparams && compparams.code_start; if (addr === undefined) { - alert("Cassette export is not supported on this platform."); + alertError("Cassette export is not supported on this platform."); return true; } loadScript('lib/c2t.js', () => { @@ -600,7 +616,7 @@ function _downloadCassetteFile(e) { var blob = new Blob([audout], {type: "audio/wav"}); saveAs(blob, audpath); stdout += "Then connect your audio output to the cassette input, turn up the volume, and play the audio file."; - alert(stdout); + alertInfo(stdout); } }); } @@ -610,17 +626,19 @@ function _revertFile(e) { if (wnd && wnd.setText) { var fn = projectWindows.getActiveID(); // TODO: .mame - $.get( "presets/"+getBasePlatform(platform_id)+"/"+fn, function(text) { - if (confirm("Reset '" + fn + "' to default?")) { - wnd.setText(text); - } + $.get( "presets/"+getBasePlatform(platform_id)+"/"+fn, (text) => { + bootbox.confirm("Reset '" + fn + "' to default?", (ok) => { + if (ok) { + wnd.setText(text); + } + }); }, 'text') .fail(() => { // TODO: delete file - alert("Can only revert built-in files."); + alertError("Can only revert built-in files."); }); } else { - alert("Cannot revert the active window. Please choose a text file."); + alertError("Cannot revert the active window. Please choose a text file."); } } @@ -629,23 +647,25 @@ function _deleteFile(e) { if (wnd && wnd.getPath) { var fn = projectWindows.getActiveID(); if (repo_id || fn.startsWith("local/") || fn.startsWith("shared/")) { - if (confirm("Delete '" + fn + "'?")) { - store.removeItem(fn).then( () => { - // if we delete what is selected - if (qs['file'] == fn) { - unsetLastPreset(); - gotoNewLocation(); - } else { - updateSelector(); - alert("Deleted " + fn); - } - }); - } + 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); + } + }); + } + }); } else { - alert("Can only delete local files."); + alertError("Can only delete local files."); } } else { - alert("Cannot delete the active window."); + alertError("Cannot delete the active window."); } } @@ -660,21 +680,21 @@ function _renameFile(e) { store.removeItem(fn).then( () => { return store.setItem(newfn, data); }).then( () => { - alert("Renamed " + fn + " to " + newfn); updateSelector(); + alert("Renamed " + fn + " to " + newfn); // need alert() so it pauses if (fn == current_project.mainPath) { reloadProject(newfn); } }); } } else { - alert("Cannot rename the active window."); + alertError("Cannot rename the active window."); } } function _downloadROMImage(e) { if (current_output == null) { - alert("Please finish compiling with no errors before downloading ROM."); + alertError("Please finish compiling with no errors before downloading ROM."); return true; } if (current_output instanceof Uint8Array) { @@ -793,7 +813,7 @@ function updateSelector() { }); }); } else { - sel.append($("