diff --git a/codemirror/themes/vscode-dark.css b/codemirror/themes/vscode-dark.css index fb860bb..aa03859 100644 --- a/codemirror/themes/vscode-dark.css +++ b/codemirror/themes/vscode-dark.css @@ -105,6 +105,8 @@ dialog a:hover { color: #fff !important; } a:before { content: ""; } a:after { content: ""; } a:hover { text-decoration: underline; } +dialog a.disabled, dialog a.disabled:hover { color: #999 !important; } +dialog a.disabled:hover { text-decoration: none; } #fs-contents a { font-variant: normal; font-family: Verdana, Geneva, Tahoma, sans-serif; font-size: 13px; } #fs-contents a:hover { text-decoration: none; } diff --git a/codemirror/themes/win-classic.css b/codemirror/themes/win-classic.css index 6f341dc..e530b52 100644 --- a/codemirror/themes/win-classic.css +++ b/codemirror/themes/win-classic.css @@ -43,6 +43,7 @@ a:active { border: 2px inset;} a:hover { color: #000; } a:before { content: ""; } a:after { content: ""; } +dialog a.disabled:active { border: 2px outset; } li a, li a:link, li a:visited { border: 0; text-decoration: underline; } li a:active { border: 0; } diff --git a/img/upload-hover.svg b/img/upload-hover.svg new file mode 100644 index 0000000..c11f240 --- /dev/null +++ b/img/upload-hover.svg @@ -0,0 +1,73 @@ + + + + + + + + + + diff --git a/img/upload.svg b/img/upload.svg new file mode 100644 index 0000000..855e483 --- /dev/null +++ b/img/upload.svg @@ -0,0 +1,73 @@ + + + + + + + + + + diff --git a/index.html b/index.html index 9a0b7ab..e5e150c 100644 --- a/index.html +++ b/index.html @@ -27,21 +27,21 @@
- - + +
- - - + + +
- + - +
- - - - + + + +
@@ -55,7 +55,7 @@
-
Console
Javascript
Files
Help
+
Console
Javascript
Files
Help
@@ -63,8 +63,9 @@
/ -
-
+
+
+
@@ -92,15 +93,15 @@
Copy the link below to share your code:
Launch Mode: -
- - Close - Test + Export + Close + Test @@ -110,14 +111,14 @@
- Ok - Cancel + Ok + Cancel
Select a theme:
- @@ -125,7 +126,7 @@
- Close + Close
@@ -139,14 +140,14 @@ QBJS brings the fun and accessibility of QBasic to the browser.
Learn more:

Copyright (c) 2022-2023 boxgaming

- Close + Close
diff --git a/lib/web/console.bas b/lib/web/console.bas index 7c86083..18c9580 100644 --- a/lib/web/console.bas +++ b/lib/web/console.bas @@ -42,19 +42,20 @@ Sub Log (msg As String, msgType As String) console.log(msgType + ":" + msg); return; } - var errorLine = await getErrorLine(new Error(), 1); + var errorLine = await IDE.getErrorLine(new Error(), 1); var tr = document.createElement("tr"); - addWarningCell(tr, msgType); - addWarningCell(tr, ":"); - addWarningCell(tr, errorLine); - addWarningCell(tr, ":"); - addWarningCell(tr, await func_EscapeHtml(msg), "99%"); + IDE.addWarningCell(tr, msgType); + IDE.addWarningCell(tr, ":"); + IDE.addWarningCell(tr, errorLine); + IDE.addWarningCell(tr, ":"); + IDE.addWarningCell(tr, await func_EscapeHtml(msg), "99%"); tr.codeLine = errorLine - 1; - tr.onclick = gotoWarning; + tr.onclick = IDE.gotoWarning; t.append(tr); var container = document.getElementById("output-content"); container.scrollTop = container.scrollHeight; - changeTab("console"); + IDE.changeTab("console"); + IDE.showConsole(); $End If End Sub @@ -66,12 +67,13 @@ Sub Echo (msg As String) return; } var tr = document.createElement("tr"); - addWarningCell(tr, await func_EscapeHtml(msg)); + IDE.addWarningCell(tr, await func_EscapeHtml(msg)); tr.firstChild.colSpan = "5"; t.append(tr); var container = document.getElementById("output-content"); container.scrollTop = container.scrollHeight; - changeTab("console"); + IDE.changeTab("console"); + IDE.showConsole(); $End If End Sub diff --git a/qbjs-ide.css b/qbjs-ide.css index fa86865..9748a7d 100644 --- a/qbjs-ide.css +++ b/qbjs-ide.css @@ -193,6 +193,7 @@ dialog textarea, } dialog a { color: #333 !important; } dialog a:hover { color: #000 !important; } +dialog a.disabled, dialog a.disabled:hover { color: #999 !important; } #logo { position: absolute; @@ -213,7 +214,7 @@ dialog a:hover { color: #000 !important; } padding: 5px; border: 1px solid #333; } -#fs-refresh, #fs-new-folder { +#fs-refresh, #fs-new-folder, #fs-upload { float: right; margin-top: -3px; cursor: pointer; @@ -236,6 +237,13 @@ dialog a:hover { color: #000 !important; } #fs-new-folder:hover { background-image: url('img/new-folder-hover.svg'); } +#fs-upload { + margin-right: 8px; + background-image: url('img/upload.svg'); +} +#fs-upload:hover { + background-image: url('img/upload-hover.svg'); +} .fs-file, .fs-dir, .fs-delete { display: block; diff --git a/qbjs-ide.js b/qbjs-ide.js index 47b4cc5..86532e3 100644 --- a/qbjs-ide.js +++ b/qbjs-ide.js @@ -1,982 +1,1073 @@ -var QBCompiler = null; -// if code has been passed on the query string load it into the editor -var qbcode = ""; -var url = location.href; -var sizeMode = "normal"; -var appMode = "ide"; -var consoleVisible = false; -var currTab = "js"; -var editor; -var selectedError = null; -var currPath = "/"; -var mainProg = null; -var theme = "qbjs"; -var splitWidth = 600; -var splitHeight = 327; -var sliding = false; -var vsliding = false; -var _e = { - ideTheme: _el("ide-theme"), - loadScreen: _el("gx-load-screen"), - jsCode: _el("js-code"), - warningContainer: _el("warning-container"), - gxContainer: _el("gx-container"), - shareMode: _el("share-mode"), - shareCode: _el("share-code"), - shareDialog: _el("share-dialog"), - exportButton: _el("export-button"), - fileInput: _el("file-input"), - progSelSources: _el("prog-sel-sources"), - progSelDialog: _el("prog-sel-dialog"), - optionsDialog: _el("options-dialog"), - aboutDialog: _el("about-dialog"), - toolbar: _el("toolbar"), - tbConsoleShow: _el("toolbar-button-console-show"), - tbConsoleHide: _el("toolbar-button-console-hide"), - tbSlideRight: _el("toolbar-button-slide-right"), - tbSlideLeft: _el("toolbar-button-slide-left"), - tbRun: _el("toolbar-button-run"), - tbStop: _el("toolbar-button-stop"), - outputContainer: _el("output-container"), - outputContent: _el("output-content"), - codeContainer: _el("code-container"), - rightPanel: _el("game-container"), - slider: _el("slider"), - vslider: _el("vslider"), - fsBrowser: _el("fs-browser"), - fsContents: _el("fs-contents"), - fsUrl: _el("fs-url"), - code: _el("code"), - themePicker: _el("theme-picker"), - help: _el("help"), - helpSidebar: _el("help-sidebar"), - helpPage: _el("help-page"), - helpContainer: _el("help-container") -}; +var IDE = new function() { -function _el(id) { - return document.getElementById(id); -} + var QBCompiler = null; + // if code has been passed on the query string load it into the editor + var qbcode = ""; + var url = location.href; + var sizeMode = "normal"; + var appMode = "ide"; + var consoleVisible = false; + var currTab = "js"; + var editor; + var selectedError = null; + var currPath = "/"; + var mainProg = null; + var theme = "qbjs"; + var splitWidth = 600; + var splitHeight = 327; + var sliding = false; + var vsliding = false; + var _e = { + ideTheme: _el("ide-theme"), + loadScreen: _el("gx-load-screen"), + jsCode: _el("js-code"), + warningContainer: _el("warning-container"), + gxContainer: _el("gx-container"), + shareMode: _el("share-mode"), + shareCode: _el("share-code"), + shareDialog: _el("share-dialog"), + exportButton: _el("export-button"), + fileInput: _el("file-input"), + progSelSources: _el("prog-sel-sources"), + progSelDialog: _el("prog-sel-dialog"), + optionsDialog: _el("options-dialog"), + aboutDialog: _el("about-dialog"), + toolbar: _el("toolbar"), + tbConsoleShow: _el("toolbar-button-console-show"), + tbConsoleHide: _el("toolbar-button-console-hide"), + tbSlideRight: _el("toolbar-button-slide-right"), + tbSlideLeft: _el("toolbar-button-slide-left"), + tbRun: _el("toolbar-button-run"), + tbStop: _el("toolbar-button-stop"), + outputContainer: _el("output-container"), + outputContent: _el("output-content"), + codeContainer: _el("code-container"), + rightPanel: _el("game-container"), + slider: _el("slider"), + vslider: _el("vslider"), + fsBrowser: _el("fs-browser"), + fsContents: _el("fs-contents"), + fsUrl: _el("fs-url"), + code: _el("code"), + themePicker: _el("theme-picker"), + help: _el("help"), + helpSidebar: _el("help-sidebar"), + helpPage: _el("help-page"), + helpContainer: _el("help-container") + }; -async function init() { - document.body.style.display = "initial"; - - if (window.innerWidth < 1200) { - sizeMode = "max"; + function _el(id) { + return document.getElementById(id); } - var srcUrl = null; - if (url && (url.indexOf("?") || url.indexOf("#"))) { - var pindex = url.indexOf("?"); - if (pindex == -1) { - pindex = url.indexOf("#"); - } - var queryString = url.substring(pindex + 1); - var nvpairs = queryString.split("&"); - for (var i = 0; i < nvpairs.length; i++) { - var pname = ""; - var pvalue = ""; - var nvidx = nvpairs[i].indexOf("="); - if (nvidx > -1) { - pname = nvpairs[i].substring(0, nvidx); - pvalue = nvpairs[i].substring(nvidx + 1); + async function _init() { + document.body.style.display = "initial"; - if (pname == "qbcode") { - var zin = new Shorty(); - qbcode = zin.inflate(atob(pvalue)); - break; - } - else if (pname == "code") { - qbcode = LZUTF8.decompress(pvalue, { inputEncoding: "Base64" }); - } - else if (pname == "mode") { - appMode = pvalue; - } - else if (pname == "src") { - srcUrl = decodeURIComponent(pvalue); - } - else if (pname == "main") { - mainProg = pvalue; + if (window.innerWidth < 1200) { + sizeMode = "max"; + } + + var srcUrl = null; + if (url && (url.indexOf("?") || url.indexOf("#"))) { + var pindex = url.indexOf("?"); + if (pindex == -1) { + pindex = url.indexOf("#"); + } + var queryString = url.substring(pindex + 1); + var nvpairs = queryString.split("&"); + for (var i = 0; i < nvpairs.length; i++) { + var pname = ""; + var pvalue = ""; + var nvidx = nvpairs[i].indexOf("="); + if (nvidx > -1) { + pname = nvpairs[i].substring(0, nvidx); + pvalue = nvpairs[i].substring(nvidx + 1); + + if (pname == "qbcode") { + var zin = new Shorty(); + qbcode = zin.inflate(atob(pvalue)); + break; + } + else if (pname == "code") { + qbcode = LZUTF8.decompress(pvalue, { inputEncoding: "Base64" }); + } + else if (pname == "mode") { + appMode = pvalue; + } + else if (pname == "src") { + srcUrl = decodeURIComponent(pvalue); + } + else if (pname == "main") { + mainProg = pvalue; + } } } } - } - if (appMode == "play") { - _e.loadScreen.style.display = "block"; - } - else if (appMode == "ide") { - var stheme = localStorage.getItem("@@_theme"); - if (stheme && stheme != "") { - theme = stheme; + if (appMode == "play") { + _e.loadScreen.style.display = "block"; } - _e.ideTheme.href = "codemirror/themes/" + theme + ".css"; - GitHelp.navhome(); - } - - // initialize the code editor - editor = CodeMirror(document.querySelector("#code"), { - lineNumbers: true, - tabSize: 4, - indentUnit: 4, - value: qbcode, - module: "qbjs", - theme: theme, - height: "auto", - styleActiveLine: true, - smartIndent: false, - extraKeys: { - "Tab": function(cm) { - cm.replaceSelection(" ", "end"); + else if (appMode == "ide") { + var stheme = localStorage.getItem("@@_theme"); + if (stheme && stheme != "") { + theme = stheme; } + _e.ideTheme.href = "codemirror/themes/" + theme + ".css"; + GitHelp.navhome(); } - }); - editor.setSize(600, 600); - editor.on("beforeChange", (cm, change) => { - if (change.origin === "paste") { - const newText = change.text.map(line => line.replace(/\t/g, " ")); - change.update(null, null, newText); - } - }); - // if IDE mode, capture the F5 event - if (appMode != "play" && appMode != "auto") { - window.addEventListener("keydown", function(event) { - // run - if (event.code == 'F5') { - event.preventDefault(); - runProgram(); - } - // compile - else if (event.code == 'F11') { - event.preventDefault(); - shareProgram(); + // initialize the code editor + editor = CodeMirror(document.querySelector("#code"), { + lineNumbers: true, + tabSize: 4, + indentUnit: 4, + value: qbcode, + module: "qbjs", + theme: theme, + height: "auto", + styleActiveLine: true, + smartIndent: false, + extraKeys: { + "Tab": function(cm) { + cm.replaceSelection(" ", "end"); + } } }); - } - if (appMode == "ide") { - editor.focus(); - } - - if (srcUrl) { - var res = await fetch(srcUrl); - var contentType = res.headers.get("Content-Type"); - if (contentType == "application/zip" || - contentType == "application/zip-compressed" || - contentType == "application/x-zip-compressed") { - // load a project - await loadProject(await res.arrayBuffer(), mainProg, function() { - if (appMode == "auto") { - runProgram(); + editor.setSize(600, 600); + editor.on("beforeChange", (cm, change) => { + if (change.origin === "paste") { + const newText = change.text.map(line => line.replace(/\t/g, " ")); + change.update(null, null, newText); + } + }); + + // if IDE mode, capture the F5 event + if (appMode != "play" && appMode != "auto") { + window.addEventListener("keydown", function(event) { + // run + if (event.code == 'F5') { + if (event.shiftKey) { + QB.halt(); + GX.sceneStop(); + } + else if (!QB.running() && !GX.sceneActive()) { + event.preventDefault(); + _runProgram(); + } + } + // compile + else if (event.code == 'F11') { + event.preventDefault(); + shareProgram(); } }); } - else { - // otherwise, assume a single source file - var decoder = new TextDecoder("iso-8859-1"); - qbcode = decoder.decode(await res.arrayBuffer()); - editor.setValue(qbcode); + if (appMode == "ide") { + editor.focus(); } - } - - var warnCount = 0; - changeTab("console"); - window.onresize(); - - if (appMode == "auto") { - runProgram(); - } -} - -async function getErrorLine(error, stackDepth) { - if (!stackDepth) { - stackDepth = 0; - } - else if (error._stackDepth) { - stackDepth = error._stackDepth; - } - - var cdepth = 0; - var srcLine = ""; - if (error.line) { // safari - srcLine = error.line - 1; - } - - if (!error.stack) { return 0; } - - var stack = error.stack.split("\n"); - for (var i=0; i < stack.length; i++) { - // chrome - if (stack[i].trim().indexOf("(eval at runProgram") > -1) { - if (cdepth == stackDepth) { - var idx = stack[i].indexOf(":"); - var pos = stack[i].substring(idx + 12); - pos = pos.substring(0, pos.length - 1); - pos = pos.split(":"); - srcLine = pos[0] - 2; + + if (srcUrl) { + var res = await fetch(srcUrl); + var contentType = res.headers.get("Content-Type"); + if (contentType == "application/zip" || + contentType == "application/zip-compressed" || + contentType == "application/x-zip-compressed") { + // load a project + await loadProject(await res.arrayBuffer(), mainProg, function() { + if (appMode == "auto") { + _runProgram(); + } + }); } - cdepth++; - } - // firefox - else if (stack[i].trim().indexOf("> AsyncFunction:") > -1) { - if (cdepth == stackDepth) { - var idx = stack[i].indexOf("> AsyncFunction:"); - var pos = stack[i].substring(idx + 16); - pos = pos.split(":"); - srcLine = pos[0] - 2; + else { + // otherwise, assume a single source file + var decoder = new TextDecoder("iso-8859-1"); + qbcode = decoder.decode(await res.arrayBuffer()); + editor.setValue(qbcode); } - cdepth++; - } - } - - if (!isNaN(srcLine)) { - srcLine = QBCompiler.getSourceLine(srcLine); - } - - return srcLine; -} - -async function runProgram() { - _e.loadScreen.style.display = "none"; - - if (sizeMode == "max") { - slideLeft(); - } - GX.reset(); - QB.start(); - var qbCode = editor.getValue(); - if (!QBCompiler) { QBCompiler = await _QBCompiler(); } - var jsCode = await QBCompiler.compile(qbCode); - - await displayWarnings(); - - _e.jsCode.innerHTML = jsCode; - window.onresize(); - - try { - const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; - var codeFn = new AsyncFunction(jsCode); - await codeFn(); - } - catch (error) { - console.error(error); - - // find the source line, if possible - var srcLine = await getErrorLine(error); - - var table = _el("warning-table"); - if (table) { - tr = document.createElement("tr"); - addWarningCell(tr, "ERROR"); - addWarningCell(tr, ":"); - addWarningCell(tr, srcLine); - addWarningCell(tr, ":"); - addWarningCell(tr, "
" + error.message + "\n
" + error.stack + "
", "99%"); - tr.codeLine = srcLine - 1; - tr.onclick = gotoWarning; - table.append(tr); } - consoleVisible = true; + var warnCount = 0; + _changeTab("console"); window.onresize(); + + if (appMode == "auto") { + _runProgram(); + } + } + + function _getErrorLine(error, stackDepth) { + if (!stackDepth) { + stackDepth = 0; + } + else if (error._stackDepth) { + stackDepth = error._stackDepth; + } + + var cdepth = 0; + var srcLine = ""; + if (error.line) { // safari + srcLine = error.line - 1; + } + + if (!error.stack) { return 0; } + + var stack = error.stack.split("\n"); + for (var i=0; i < stack.length; i++) { + // chrome + if (stack[i].trim().indexOf("(eval at runProgram") > -1) { + if (cdepth == stackDepth) { + var idx = stack[i].indexOf(":"); + var pos = stack[i].substring(idx + 12); + pos = pos.substring(0, pos.length - 1); + pos = pos.split(":"); + srcLine = pos[0] - 2; + } + cdepth++; + } + // firefox + else if (stack[i].trim().indexOf("> AsyncFunction:") > -1) { + if (cdepth == stackDepth) { + var idx = stack[i].indexOf("> AsyncFunction:"); + var pos = stack[i].substring(idx + 16); + pos = pos.split(":"); + srcLine = pos[0] - 2; + } + cdepth++; + } + } + + if (!isNaN(srcLine)) { + srcLine = QBCompiler.getSourceLine(srcLine); + } + + return srcLine; + } + + async function _runProgram() { + _e.loadScreen.style.display = "none"; + + if (sizeMode == "max") { + _slideLeft(); + } + GX.reset(); + QB.start(); + var qbCode = editor.getValue(); + if (!QBCompiler) { QBCompiler = await _QBCompiler(); } + var jsCode = await QBCompiler.compile(qbCode); + + await displayWarnings(); + + _e.jsCode.innerHTML = jsCode; + window.onresize(); + + try { + const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; + var codeFn = new AsyncFunction(jsCode); + await codeFn(); + } + catch (error) { + console.error(error); + + // find the source line, if possible + var srcLine = await _getErrorLine(error); + + var table = _el("warning-table"); + if (table) { + tr = document.createElement("tr"); + _addWarningCell(tr, "ERROR"); + _addWarningCell(tr, ":"); + _addWarningCell(tr, srcLine); + _addWarningCell(tr, ":"); + _addWarningCell(tr, "
" + error.message + "\n
" + error.stack + "
", "99%"); + tr.codeLine = srcLine - 1; + tr.onclick = _gotoWarning; + table.append(tr); + } + + consoleVisible = true; + window.onresize(); + QB.halt(); + GX.sceneStop(); + } + _e.gxContainer.focus(); + + return false; + } + + function _stopProgram() { QB.halt(); GX.sceneStop(); } - _e.gxContainer.focus(); - return false; -} + function _shareProgram() { + var zout = new Shorty(); + var b64 = LZUTF8.compress(editor.getValue(), { outputEncoding: "Base64" }); + var baseUrl = location.href.split('?')[0]; -function stopProgram() { - QB.halt(); - GX.sceneStop(); -} + var mode = _e.shareMode.value; + var codeShare = _e.shareCode; + var url = baseUrl + "?"; + if (mode) { + url += "mode=" + mode + "&"; + } + url += "code=" + b64; + codeShare.value = url; + if (!_e.shareDialog.open) { + _e.shareDialog.showModal(); + } + codeShare.focus(); + codeShare.select(); -function shareProgram() { - var zout = new Shorty(); - var b64 = LZUTF8.compress(editor.getValue(), { outputEncoding: "Base64" }); - var baseUrl = location.href.split('?')[0]; - - var mode = _e.shareMode.value; - var codeShare = _e.shareCode; - var url = baseUrl + "?"; - if (mode) { - url += "mode=" + mode + "&"; - } - url += "code=" + b64; - codeShare.value = url; - if (!_e.shareDialog.open) { - _e.shareDialog.showModal(); - } - codeShare.focus(); - codeShare.select(); - - var exportVisible = (mode == "play" || mode == "auto"); - _e.exportButton.style.display = (exportVisible) ? "block" : "none"; -} - -function changeTheme(newTheme) { - theme = newTheme; - _e.ideTheme.href = "codemirror/themes/" + theme + ".css"; - editor.setOption("theme", theme); - localStorage.setItem("@@_theme", theme); -} - -function showOptionDialog() { - _e.themePicker.value = theme; - showDialog(_e.optionsDialog); -} - -function showDialog(dlg) { - if (typeof dlg == "string") { - dlg = _el(dlg); - } - if (!dlg.open) { - dlg.showModal(); - } -} - -async function exportProgram() { - var zip = new JSZip(); - - var qbCode = editor.getValue(); - if (!QBCompiler) { QBCompiler = await _QBCompiler(); } - var jsCode = "async function __qbjs_run() {\n" + await QBCompiler.compile(qbCode) + "\n}"; - - var mode = _e.shareMode.value; - zip.file("index.html", await getFile("export/" + mode + ".html", "text")); - zip.file("program.js", jsCode); - zip.file("fullscreen.png", await getFile("export/fullscreen.png", "blob")); - zip.file("logo.png", await getFile("logo.png", "blob")); - zip.file("dosvga.ttf", await getFile("dosvga.ttf", "blob")); - zip.file("play.png", await getFile("play.png", "blob")); - zip.file("qbjs.css", await getFile("export/qbjs.css", "text")); - zip.file("qb.js", await getFile("qb.js", "text")); - zip.file("vfs.js", await getFile("vfs.js", "text")); - - zip.file("gx/gx.js", await getFile("gx/gx.js", "text")); - zip.file("gx/__gx_font_default.png", await getFile("gx/__gx_font_default.png", "blob")); - zip.file("gx/__gx_font_default_black.png", await getFile("gx/__gx_font_default_black.png", "blob")); - - // include vfs content - var vfs = QB.vfs(); - var node = vfs.getNode("/"); - addVFSFiles(vfs, zip, node); - - zip.generateAsync({type:"blob"}).then(function(content) { - const link = document.createElement("a"); - link.href = URL.createObjectURL(content); - link.download = "program.zip"; - link.click(); - link.remove(); - }); -} - -function addVFSFiles(vfs, zip, parent) { - var files = vfs.getChildren(parent, vfs.FILE); - for (var i=0; i < files.length; i++) { - var f = files[i]; - var path = vfs.fullPath(f).substring(1); - zip.file(path, f.data); + var exportVisible = (mode == "play" || mode == "auto"); + _e.exportButton.title = (exportVisible) ? "" : "Select Play or Auto mode to enable Export"; + if (exportVisible) { + _e.exportButton.classList.remove("disabled"); + } + else { + _e.exportButton.classList.add("disabled"); + } } - var dirs = vfs.getChildren(parent, vfs.DIRECTORY); - for (var i=0; i < dirs.length; i++) { - addVFSFiles(vfs, zip, dirs[i]); - } -} - -async function saveProject() { - var vfs = QB.vfs(); - var node = vfs.getNode("/"); - var count = vfs.getChildren(node, vfs.FILE).length; - if (count < 1) { - count = vfs.getChildren(node, vfs.DIRECTORY).length; + function _changeTheme(newTheme) { + theme = newTheme; + _e.ideTheme.href = "codemirror/themes/" + theme + ".css"; + editor.setOption("theme", theme); + localStorage.setItem("@@_theme", theme); } - // save a single .bas file - if (count == 0) { - var progFile = new Blob([ editor.getValue() ]); - QB.downloadFile(progFile, "program.bas"); + function _showOptionDialog() { + _e.themePicker.value = theme; + _showDialog(_e.optionsDialog); } - // save a project .zip file - else { + function _showDialog(dlg) { + if (typeof dlg == "string") { + dlg = _el(dlg); + } + if (!dlg.open) { + dlg.showModal(); + } + } + + async function _exportProgram() { + var mode = _e.shareMode.value; + if (mode == "") { return; } + var zip = new JSZip(); - zip.file("main.bas", editor.getValue()); + var qbCode = editor.getValue(); + if (!QBCompiler) { QBCompiler = await _QBCompiler(); } + var jsCode = "async function __qbjs_run() {\n" + await QBCompiler.compile(qbCode) + "\n}"; + + var mode = _e.shareMode.value; + zip.file("index.html", await getFile("export/" + mode + ".html", "text")); + zip.file("program.js", jsCode); + zip.file("fullscreen.png", await getFile("export/fullscreen.png", "blob")); + zip.file("logo.png", await getFile("logo.png", "blob")); + zip.file("dosvga.ttf", await getFile("dosvga.ttf", "blob")); + zip.file("play.png", await getFile("play.png", "blob")); + zip.file("qbjs.css", await getFile("export/qbjs.css", "text")); + zip.file("qb.js", await getFile("qb.js", "text")); + zip.file("vfs.js", await getFile("vfs.js", "text")); + + zip.file("gx/gx.js", await getFile("gx/gx.js", "text")); + zip.file("gx/__gx_font_default.png", await getFile("gx/__gx_font_default.png", "blob")); + zip.file("gx/__gx_font_default_black.png", await getFile("gx/__gx_font_default_black.png", "blob")); + + // include vfs content var vfs = QB.vfs(); var node = vfs.getNode("/"); addVFSFiles(vfs, zip, node); zip.generateAsync({type:"blob"}).then(function(content) { - QB.downloadFile(content, "project.zip"); + const link = document.createElement("a"); + link.href = URL.createObjectURL(content); + link.download = "program.zip"; + link.click(); + link.remove(); }); } -} -async function openProject() { - _e.fileInput.click(); -} - -async function onOpenProject(event) { - var f = event.target.files[0]; - - // load a single BASIC source file - if (f.name.toLowerCase().endsWith(".bas") || f.type.startsWith("text/")) { - var fr = new FileReader(); - fr.onload = function() { - editor.setValue(fr.result); + function addVFSFiles(vfs, zip, parent) { + var files = vfs.getChildren(parent, vfs.FILE); + for (var i=0; i < files.length; i++) { + var f = files[i]; + var path = vfs.fullPath(f).substring(1); + zip.file(path, f.data); + } + + var dirs = vfs.getChildren(parent, vfs.DIRECTORY); + for (var i=0; i < dirs.length; i++) { + addVFSFiles(vfs, zip, dirs[i]); } - fr.readAsText(f); } - // load a project from a zip file - else if (f.name.endsWith(".zip") || f.type == "application/x-zip-compressed") { - await loadProject(f); + async function _saveProject() { + var vfs = QB.vfs(); + var node = vfs.getNode("/"); + var count = vfs.getChildren(node, vfs.FILE).length; + if (count < 1) { + count = vfs.getChildren(node, vfs.DIRECTORY).length; + } + + // save a single .bas file + if (count == 0) { + var progFile = new Blob([ editor.getValue() ]); + QB.downloadFile(progFile, "program.bas"); + } + + // save a project .zip file + else { + var zip = new JSZip(); + zip.file("main.bas", editor.getValue()); + + var vfs = QB.vfs(); + var node = vfs.getNode("/"); + addVFSFiles(vfs, zip, node); + + zip.generateAsync({type:"blob"}).then(function(content) { + QB.downloadFile(content, "project.zip"); + }); + } } -} -_e.fileInput.onchange = onOpenProject; + async function _openProject() { + _e.fileInput.click(); + } -async function loadProject(zipData, mainFilename, fnCallback) { - if (!mainFilename) { - mainFilename = "main.bas"; - } - else { - mainFilename = mainFilename.toLowerCase(); - } - var vfs = GX.vfs(); - vfs.reset(); - JSZip.loadAsync(zipData).then(async function(zip) { - var basFiles = []; - var fnames = ""; - var mainFound = false; - for (let [filename, file] of Object.entries(zip.files)) { - fnames += filename + " - " + zip.files[filename].name + "\n"; - var parentDir = dirFromPath(vfs.getParentPath(filename)); - if (filename.toLowerCase() == mainFilename) { - var text = await zip.file(filename).async("text"); - editor.setValue(text); - mainFound = true; + async function onOpenProject(event) { + var f = event.target.files[0]; + + // load a single BASIC source file + if (f.name.toLowerCase().endsWith(".bas") || f.type.startsWith("text/")) { + var fr = new FileReader(); + fr.onload = function() { + editor.setValue(fr.result); } - else { - if (zip.file(filename)) { - var fdata = await zip.file(filename).async("arraybuffer"); - var f = vfs.createFile(vfs.getFileName(filename), parentDir); - vfs.writeData(f, fdata); - if (filename.toLowerCase().endsWith(".bas")) { - basFiles.push(filename); + fr.readAsText(f); + } + + // load a project from a zip file + else if (f.name.endsWith(".zip") || f.type == "application/x-zip-compressed") { + await loadProject(f); + } + + } + _e.fileInput.onchange = onOpenProject; + + async function loadProject(zipData, mainFilename, fnCallback) { + if (!mainFilename) { + mainFilename = "main.bas"; + } + else { + mainFilename = mainFilename.toLowerCase(); + } + var vfs = GX.vfs(); + vfs.reset(); + JSZip.loadAsync(zipData).then(async function(zip) { + var basFiles = []; + var fnames = ""; + var mainFound = false; + for (let [filename, file] of Object.entries(zip.files)) { + fnames += filename + " - " + zip.files[filename].name + "\n"; + var parentDir = dirFromPath(vfs.getParentPath(filename)); + if (filename.toLowerCase() == mainFilename) { + var text = await zip.file(filename).async("text"); + editor.setValue(text); + mainFound = true; + } + else { + if (zip.file(filename)) { + var fdata = await zip.file(filename).async("arraybuffer"); + var f = vfs.createFile(vfs.getFileName(filename), parentDir); + vfs.writeData(f, fdata); + if (filename.toLowerCase().endsWith(".bas")) { + basFiles.push(filename); + } } } } - } - if (!mainFound) { - var fileList = _e.progSelSources; - fileList.innerHTML = ""; - for (var i=0; i < basFiles.length; i++) { - var opt = new Option(basFiles[i], basFiles[i]); - fileList.append(opt); + if (!mainFound) { + var fileList = _e.progSelSources; + fileList.innerHTML = ""; + for (var i=0; i < basFiles.length; i++) { + var opt = new Option(basFiles[i], basFiles[i]); + fileList.append(opt); + } + _showDialog(_e.progSelDialog); } - showDialog(_e.progSelDialog); - } - refreshFS(); - if (fnCallback) { - fnCallback(); - } - }); - - function dirFromPath(path) { - var vfs = GX.vfs(); - if (path == "") { return vfs.rootDirectory(); } - - var dirnames = path.split("/"); - var dirpath = "" - var parent = vfs.rootDirectory(); - for (var i=0; i < dirnames.length; i++) { - dirpath += "/" + dirnames[i]; - var dir = vfs.getNode(dirpath); - if (!dir) { - dir = vfs.createDirectory(dirnames[i], parent); + _refreshFS(); + if (fnCallback) { + fnCallback(); } - parent = dir; - } - return parent; - } -} + }); -function onSelMainProg() { - var fileList = _e.progSelSources; - if (fileList.value == "") { - alert("No file selected."); - } - else { - var vfs = GX.vfs(); - var file = vfs.getNode("/" + fileList.value); - editor.setValue(vfs.readText(file)); - vfs.removeFile(file); - closeProgSelDlg(); - } -} - -async function getFile(path, type) { - var file = await fetch(path); - if (type == "text") { - return await file.text(); - } - else if (type == "blob") { - return await file.blob(); - } -} - -function testShare() { - open(_e.shareCode.value, "_blank"); -} - -function closeDialog() { - _e.shareDialog.close(); - _e.progSelDialog.close(); - _e.optionsDialog.close(); - _e.aboutDialog.close(); -} - -async function displayWarnings() { - var wstr = ""; - var w = await QBCompiler.getWarnings(); - warnCount = w.length; - - var wdiv = _e.warningContainer; - wdiv.innerHTML = ""; - var table = document.createElement("table"); - table.style.width = "100%"; - table.id = "warning-table"; - table.cellPadding = 2; - table.cellSpacing = 0; - table.style.cursor = "default"; - wdiv.appendChild(table); - - selectedError = null; - if (warnCount > 0) { - for (var i=0; i < w.length; i++) { - var tr = document.createElement("tr"); - var td1 = document.createElement("td"); - var td2 = document.createElement("td"); - var td3 = document.createElement("td"); - addWarningCell(tr, "WARN"); - addWarningCell(tr, ":"); - addWarningCell(tr, w[i].line); - addWarningCell(tr, ":"); - addWarningCell(tr, w[i].text, "99%"); - table.append(tr); - tr.codeLine = w[i].line - 1; - tr.onclick = gotoWarning; + function dirFromPath(path) { + var vfs = GX.vfs(); + if (path == "") { return vfs.rootDirectory(); } + + var dirnames = path.split("/"); + var dirpath = "" + var parent = vfs.rootDirectory(); + for (var i=0; i < dirnames.length; i++) { + dirpath += "/" + dirnames[i]; + var dir = vfs.getNode(dirpath); + if (!dir) { + dir = vfs.createDirectory(dirnames[i], parent); + } + parent = dir; + } + return parent; } } - if (!consoleVisible && w.length > 0) { - consoleVisible = true; - } -} -function gotoWarning() { - if (selectedError ) { selectedError.classList.remove("selected"); } - editor.setCursor({ line: this.codeLine}); - this.classList.add("selected"); - selectedError = this; -}; - -function addWarningCell(tr, text, width) { - var td = document.createElement("td"); - td.innerHTML = text; - td.vAlign = "top"; - if (width != undefined) { - td.width = width; - } - tr.append(td); -} - -function showConsole() { - consoleVisible = !consoleVisible; - if (!consoleVisible) { - _e.tbConsoleShow.style.display = "inline-block"; - _e.tbConsoleHide.style.display = "none"; - - } - else { - _e.tbConsoleHide.style.display = "inline-block"; - _e.tbConsoleShow.style.display = "none"; - } - window.dispatchEvent(new Event('resize')); -} - -function changeTab(tabName) { - if (tabName == currTab) { return; } - _el("tab-" + currTab).classList.remove("active"); - _el("tab-" + tabName).classList.add("active"); - currTab = tabName; - - if (currTab == "console") { - _e.warningContainer.style.display = "block"; - _e.jsCode.style.display = "none"; - _e.fsBrowser.style.display = "none"; - _e.help.style.display = "none"; - } - else if (currTab == "js") { - _e.warningContainer.style.display = "none"; - _e.fsBrowser.style.display = "none"; - _e.jsCode.style.display = "block"; - _e.help.style.display = "none"; - } - else if (currTab == "fs") { - _e.fsBrowser.style.display = "block"; - _e.warningContainer.style.display = "none"; - _e.jsCode.style.display = "none"; - _e.help.style.display = "none"; - refreshFS(); - } - else if (currTab == "help") { - _e.warningContainer.style.display = "none"; - _e.jsCode.style.display = "none"; - _e.fsBrowser.style.display = "none"; - _e.help.style.display = "block"; - } -} - -function showHelp(page) { - changeTab("help"); - var helpUrl = ""; - if (page == "language") { GitHelp.wikinav("https://raw.githubusercontent.com/wiki/boxgaming/qbjs/QBasic-Language-Support.md"); } - else if (page == "keywords") { GitHelp.wikinav("https://raw.githubusercontent.com/wiki/boxgaming/qbjs/Supported-Keywords.md"); } - else if (page == "samples") { GitHelp.wikinav("https://raw.githubusercontent.com/wiki/boxgaming/qbjs/Samples.md"); } - else { GitHelp.navhome(); } - consoleVisible = false; - showConsole(); -} - -function displayTypes() { - var tstr = ""; - var t = QBCompiler.getTypes(); - for (var i=0; i < t.length; i++) { - tstr += t[i].name - } - var wdiv = _e.warningContainer; - wdiv.innerHTML = tstr; -} - -function slideLeft() { - _e.tbSlideRight.style.display = "inline-block"; - if (sizeMode == "max" && window.innerWidth >= 1200) { - sizeMode = "normal" - } - else { - sizeMode = "min" - _e.tbSlideLeft.style.display = "none"; - } - window.dispatchEvent(new Event('resize')); -} - -function slideRight() { - _e.tbSlideLeft.style.display = "inline-block"; - if (sizeMode == "min" && window.innerWidth >= 1200) { - sizeMode = "normal" - } - else { - sizeMode = "max" - _e.tbSlideRight.style.display = "none"; - } - window.dispatchEvent(new Event('resize')); -} - -window.onresize = function() { - if (!editor) { return; } - - var f = _e.gxContainer; - var jsDiv = _e.outputContainer; - - if (appMode == "play" || appMode == "auto") { - f.style.left = "0px"; - f.style.top = "0px"; - f.style.width = window.innerWidth + "px"; - f.style.height = window.innerHeight + "px"; - f.style.border = "0px"; - _e.codeContainer.style.display = "none"; - _e.slider.style.display = "none"; - _e.rightPanel.style.left = "0px"; - _e.rightPanel.style.top = "0px"; - _e.rightPanel.style.right = "0px"; - _e.rightPanel.style.bottom = "0px"; - _e.rightPanel.style.backgroundColor = "#000"; - _e.toolbar.style.display = "none"; - jsDiv.style.display = "none"; - _e.vslider.style.display = "none"; - splitHeight = 0; - } - else { - var cmwidth = splitWidth; - if (sizeMode == "min") { - cmwidth = -10; - editor.getWrapperElement().style.display = "none"; - _e.rightPanel.style.display = "block"; - _e.slider.style.display = "none"; - } - else if (sizeMode == "max") { - cmwidth = window.innerWidth - 12; - _e.rightPanel.style.display = "none"; - _e.slider.style.display = "none"; - editor.getWrapperElement().style.display = "block"; + function _onSelMainProg() { + var fileList = _e.progSelSources; + if (fileList.value == "") { + alert("No file selected."); } else { - editor.getWrapperElement().style.display = "block"; - _e.rightPanel.style.display = "block"; - _e.slider.style.display = "block"; + var vfs = GX.vfs(); + var file = vfs.getNode("/" + fileList.value); + editor.setValue(vfs.readText(file)); + vfs.removeFile(file); + closeProgSelDlg(); } + } - _e.rightPanel.style.left = (cmwidth + 15) + "px"; - f.style.width = (window.innerWidth - (cmwidth + 22)) + "px"; - jsDiv.style.width = f.style.width; + async function getFile(path, type) { + var file = await fetch(path); + if (type == "text") { + return await file.text(); + } + else if (type == "blob") { + return await file.blob(); + } + } - _e.slider.style.left = (cmwidth + 7) + "px"; + function _testShare() { + open(_e.shareCode.value, "_blank"); + } + + function _closeDialog() { + _e.shareDialog.close(); + _e.progSelDialog.close(); + _e.optionsDialog.close(); + _e.aboutDialog.close(); + } + + async function displayWarnings() { + var wstr = ""; + var w = await QBCompiler.getWarnings(); + warnCount = w.length; + + var wdiv = _e.warningContainer; + wdiv.innerHTML = ""; + var table = document.createElement("table"); + table.style.width = "100%"; + table.id = "warning-table"; + table.cellPadding = 2; + table.cellSpacing = 0; + table.style.cursor = "default"; + wdiv.appendChild(table); + + selectedError = null; + if (warnCount > 0) { + for (var i=0; i < w.length; i++) { + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + var td2 = document.createElement("td"); + var td3 = document.createElement("td"); + _addWarningCell(tr, "WARN"); + _addWarningCell(tr, ":"); + _addWarningCell(tr, w[i].line); + _addWarningCell(tr, ":"); + _addWarningCell(tr, w[i].text, "99%"); + table.append(tr); + tr.codeLine = w[i].line - 1; + tr.onclick = _gotoWarning; + } + } + if (!consoleVisible && w.length > 0) { + consoleVisible = true; + } + } + + function _gotoWarning() { + if (selectedError ) { selectedError.classList.remove("selected"); } + editor.setCursor({ line: this.codeLine}); + this.classList.add("selected"); + selectedError = this; + } + + function _addWarningCell(tr, text, width) { + var td = document.createElement("td"); + td.innerHTML = text; + td.vAlign = "top"; + if (width != undefined) { + td.width = width; + } + tr.append(td); + } + + function _showConsole() { + consoleVisible = !consoleVisible; + if (!consoleVisible) { + _e.tbConsoleShow.style.display = "inline-block"; + _e.tbConsoleHide.style.display = "none"; - if (consoleVisible) { - _e.vslider.style.display = "block"; - f.style.height = (window.innerHeight - splitHeight) + "px"; - jsDiv.style.display = "block"; - jsDiv.style.top = (window.innerHeight - splitHeight + 10) + "px"; - _e.outputContent.style.height = (splitHeight - 77) + "px"; - _e.helpContainer.style.height = (splitHeight - 110) + "px"; } else { - _e.vslider.style.display = "none"; - f.style.height = (window.innerHeight - 40) + "px"; + _e.tbConsoleHide.style.display = "inline-block"; + _e.tbConsoleShow.style.display = "none"; + } + window.dispatchEvent(new Event('resize')); + } + + function _changeTab(tabName) { + if (tabName == currTab) { return; } + _el("tab-" + currTab).classList.remove("active"); + _el("tab-" + tabName).classList.add("active"); + currTab = tabName; + + if (currTab == "console") { + _e.warningContainer.style.display = "block"; + _e.jsCode.style.display = "none"; + _e.fsBrowser.style.display = "none"; + _e.help.style.display = "none"; + } + else if (currTab == "js") { + _e.warningContainer.style.display = "none"; + _e.fsBrowser.style.display = "none"; + _e.jsCode.style.display = "block"; + _e.help.style.display = "none"; + } + else if (currTab == "fs") { + _e.fsBrowser.style.display = "block"; + _e.warningContainer.style.display = "none"; + _e.jsCode.style.display = "none"; + _e.help.style.display = "none"; + _refreshFS(); + } + else if (currTab == "help") { + _e.warningContainer.style.display = "none"; + _e.jsCode.style.display = "none"; + _e.fsBrowser.style.display = "none"; + _e.help.style.display = "block"; + } + } + + function _showHelp(page) { + _changeTab("help"); + var helpUrl = ""; + if (page == "language") { GitHelp.wikinav("https://raw.githubusercontent.com/wiki/boxgaming/qbjs/QBasic-Language-Support.md"); } + else if (page == "keywords") { GitHelp.wikinav("https://raw.githubusercontent.com/wiki/boxgaming/qbjs/Supported-Keywords.md"); } + else if (page == "samples") { GitHelp.wikinav("https://raw.githubusercontent.com/wiki/boxgaming/qbjs/Samples.md"); } + else { GitHelp.navhome(); } + consoleVisible = false; + _showConsole(); + } + + function displayTypes() { + var tstr = ""; + var t = QBCompiler.getTypes(); + for (var i=0; i < t.length; i++) { + tstr += t[i].name + } + var wdiv = _e.warningContainer; + wdiv.innerHTML = tstr; + } + + function _slideLeft() { + _e.tbSlideRight.style.display = "inline-block"; + if (sizeMode == "max" && window.innerWidth >= 1200) { + sizeMode = "normal" + } + else { + sizeMode = "min" + _e.tbSlideLeft.style.display = "none"; + } + window.dispatchEvent(new Event('resize')); + } + + function _slideRight() { + _e.tbSlideLeft.style.display = "inline-block"; + if (sizeMode == "min" && window.innerWidth >= 1200) { + sizeMode = "normal" + } + else { + sizeMode = "max" + _e.tbSlideRight.style.display = "none"; + } + window.dispatchEvent(new Event('resize')); + } + + window.onresize = function() { + if (!editor) { return; } + + var f = _e.gxContainer; + var jsDiv = _e.outputContainer; + + if (appMode == "play" || appMode == "auto") { + f.style.left = "0px"; + f.style.top = "0px"; + f.style.width = window.innerWidth + "px"; + f.style.height = window.innerHeight + "px"; + f.style.border = "0px"; + _e.codeContainer.style.display = "none"; + _e.slider.style.display = "none"; + _e.rightPanel.style.left = "0px"; + _e.rightPanel.style.top = "0px"; + _e.rightPanel.style.right = "0px"; + _e.rightPanel.style.bottom = "0px"; + _e.rightPanel.style.backgroundColor = "#000"; + _e.toolbar.style.display = "none"; jsDiv.style.display = "none"; + _e.vslider.style.display = "none"; + splitHeight = 0; } - - editor.setSize(cmwidth, window.innerHeight - 40); - _e.code.style.height = (window.innerHeight - 40) + "px"; - _e.slider.style.height = (window.innerHeight - 40) + "px"; - } - QB.resize(f.clientWidth, f.clientHeight); -} -window.onresize(); + else { + var cmwidth = splitWidth; + if (sizeMode == "min") { + cmwidth = -10; + editor.getWrapperElement().style.display = "none"; + _e.rightPanel.style.display = "block"; + _e.slider.style.display = "none"; + } + else if (sizeMode == "max") { + cmwidth = window.innerWidth - 12; + _e.rightPanel.style.display = "none"; + _e.slider.style.display = "none"; + editor.getWrapperElement().style.display = "block"; + } + else { + editor.getWrapperElement().style.display = "block"; + _e.rightPanel.style.display = "block"; + _e.slider.style.display = "block"; + } + _e.rightPanel.style.left = (cmwidth + 15) + "px"; + f.style.width = (window.innerWidth - (cmwidth + 22)) + "px"; + jsDiv.style.width = f.style.width; -function checkButtonState() { - var stopButton = _e.tbStop; - var runButton = _e.tbRun; - if (GX.sceneActive() || QB.running()) { - stopButton.style.display = "inline-block"; - runButton.style.display = "none"; - } - else { - stopButton.style.display = "none"; - runButton.style.display = "inline-block"; - } - setTimeout(checkButtonState, 100); -} -checkButtonState(); -init(); + _e.slider.style.left = (cmwidth + 7) + "px"; -// Virtual File System Viewer -function refreshFS() { - var vfs = QB.vfs(); - var node = vfs.getNode(currPath); - if (!node) { - currPath = "/"; - node = vfs.getNode(currPath); - } - - var contents = _e.fsContents; - while (contents.firstChild) { - contents.removeChild(contents.firstChild); - } - - if (!node) { - // TODO: better error reporting - return; - } - - currPath = vfs.fullPath(node) - _e.fsUrl.innerHTML = currPath; - - if (currPath != "/") { - var a = document.createElement("a"); - a.className = "fs-dir"; - a.innerHTML = ".."; - a.fullpath = currPath + "/.."; - a.onclick = function() { chdir(this.fullpath); }; - contents.appendChild(a); - contents.appendChild(document.createElement("span")); - } - - var folders = vfs.getChildren(node, vfs.DIRECTORY); - for (var i=0; i < folders.length; i++) { - var a = document.createElement("a"); - a.className = "fs-dir"; - a.innerHTML = folders[i].name; - a.fullpath = vfs.fullPath(folders[i]); - a.onclick = function() { chdir(this.fullpath); }; - contents.appendChild(a); - a = document.createElement("a"); - a.className = "fs-delete"; - a.vfsnode = folders[i]; - a.onclick = function() { deleteDir(this.vfsnode); }; - contents.appendChild(a); - } - - var files = vfs.getChildren(node, vfs.FILE); - for (var i=0; i < files.length; i++) { - var a = document.createElement("a"); - a.className = "fs-file"; - a.innerHTML = files[i].name; - a.fullpath = vfs.fullPath(files[i]); - a.onclick = function() { saveFile(this.fullpath); }; - contents.appendChild(a); - a = document.createElement("a"); - a.className = "fs-delete"; - a.vfsnode = files[i]; - a.onclick = function() { deleteFile(this.vfsnode); }; - contents.appendChild(a); - } - - function deleteFile(node) { - if (confirm("This will permanently delete file '" + node.name + "'.\nAre you sure you wish to continue?")) { - vfs.removeFile(node); - refreshFS(); + if (consoleVisible) { + _e.vslider.style.display = "block"; + f.style.height = (window.innerHeight - splitHeight) + "px"; + jsDiv.style.display = "block"; + jsDiv.style.top = (window.innerHeight - splitHeight + 10) + "px"; + _e.outputContent.style.height = (splitHeight - 77) + "px"; + _e.helpContainer.style.height = (splitHeight - 110) + "px"; + } + else { + _e.vslider.style.display = "none"; + f.style.height = (window.innerHeight - 40) + "px"; + jsDiv.style.display = "none"; + } + + editor.setSize(cmwidth, window.innerHeight - 40); + _e.code.style.height = (window.innerHeight - 40) + "px"; + _e.slider.style.height = (window.innerHeight - 40) + "px"; } + QB.resize(f.clientWidth, f.clientHeight); } + window.onresize(); - function deleteDir(node) { - if (vfs.getChildren(node).length > 0) { - alert("Directory is not empty."); + + function checkButtonState() { + var stopButton = _e.tbStop; + var runButton = _e.tbRun; + if (GX.sceneActive() || QB.running()) { + stopButton.style.display = "inline-block"; + runButton.style.display = "none"; + } + else { + stopButton.style.display = "none"; + runButton.style.display = "inline-block"; + } + setTimeout(checkButtonState, 100); + } + checkButtonState(); + _init(); + + // Virtual File System Viewer + function _refreshFS() { + var vfs = QB.vfs(); + var node = vfs.getNode(currPath); + if (!node) { + currPath = "/"; + node = vfs.getNode(currPath); + } + + var contents = _e.fsContents; + while (contents.firstChild) { + contents.removeChild(contents.firstChild); + } + + if (!node) { + // TODO: better error reporting return; } - if (confirm("This will permanently delete directory '" + node.name + "'.\nAre you sure you wish to continue?")) { - vfs.removeDirectory(node); - refreshFS(); + + currPath = vfs.fullPath(node) + _e.fsUrl.innerHTML = currPath; + + if (currPath != "/") { + var a = document.createElement("a"); + a.className = "fs-dir"; + a.innerHTML = ".."; + a.fullpath = currPath + "/.."; + a.onclick = function() { chdir(this.fullpath); }; + contents.appendChild(a); + contents.appendChild(document.createElement("span")); + } + + var folders = vfs.getChildren(node, vfs.DIRECTORY); + for (var i=0; i < folders.length; i++) { + var a = document.createElement("a"); + a.className = "fs-dir"; + a.innerHTML = folders[i].name; + a.fullpath = vfs.fullPath(folders[i]); + a.onclick = function() { chdir(this.fullpath); }; + contents.appendChild(a); + a = document.createElement("a"); + a.className = "fs-delete"; + a.vfsnode = folders[i]; + a.onclick = function() { deleteDir(this.vfsnode); }; + contents.appendChild(a); + } + + var files = vfs.getChildren(node, vfs.FILE); + for (var i=0; i < files.length; i++) { + var a = document.createElement("a"); + a.className = "fs-file"; + a.innerHTML = files[i].name; + a.fullpath = vfs.fullPath(files[i]); + a.onclick = function() { saveFile(this.fullpath); }; + contents.appendChild(a); + a = document.createElement("a"); + a.className = "fs-delete"; + a.vfsnode = files[i]; + a.onclick = function() { deleteFile(this.vfsnode); }; + contents.appendChild(a); + } + + function deleteFile(node) { + if (confirm("This will permanently delete file '" + node.name + "'.\nAre you sure you wish to continue?")) { + vfs.removeFile(node); + _refreshFS(); + } + } + + function deleteDir(node) { + if (vfs.getChildren(node).length > 0) { + alert("Directory is not empty."); + return; + } + if (confirm("This will permanently delete directory '" + node.name + "'.\nAre you sure you wish to continue?")) { + vfs.removeDirectory(node); + _refreshFS(); + } } } -} -function onNewDirectory() { - var vfs = QB.vfs(); - var parent = vfs.getNode(currPath); - var dirname = prompt("Enter new directory name"); - if (dirname && dirname != "") { - vfs.createDirectory(dirname, parent); - refreshFS(); - } -} - -function chdir(path) { - currPath = path; - refreshFS(); -} - -function saveFile(path) { - var vfs = QB.vfs(); - var fileNode = vfs.getNode(path); - var fileBlob = new Blob([fileNode.data]); - var url = URL.createObjectURL(fileBlob); - - var link = document.createElement("a"); - link.href = url; - link.download = fileNode.name; - link.click(); -} - -async function fileDrop(e) { - if (currTab != "fs") { return; } - e.stopPropagation(); - e.preventDefault(); - dropArea.style.backgroundColor = "transparent"; - - var dt = e.dataTransfer; - var files = dt.files; - - var vfs = QB.vfs(); - var parentDir = vfs.getNode(currPath); - var fr = new FileReader(); - - console.log(files.length); - for (var i=0; i < files.length; i++) { - var f = files[i]; - if (!f.type && f.size%4096 == 0) { - // this is a folder, skip - continue; + function _onNewDirectory() { + var vfs = QB.vfs(); + var parent = vfs.getNode(currPath); + var dirname = prompt("Enter new directory name"); + if (dirname && dirname != "") { + vfs.createDirectory(dirname, parent); + _refreshFS(); } - - var file = vfs.createFile(f.name, parentDir); - var data = await f.arrayBuffer(); - vfs.writeData(file, data); } - refreshFS(); -} - -_e.slider.addEventListener("mousedown", function(event) { - sliding = true; -}); -_e.vslider.addEventListener("mousedown", function(event) { - vsliding = true; -}); - -window.addEventListener("mousemove", function(event) { - if (!sliding && !vsliding) { return; } - if (sliding) { - splitWidth = event.pageX - 10; - window.onresize(); + function chdir(path) { + currPath = path; + _refreshFS(); } - else { - splitHeight = window.innerHeight - event.pageY + 35; - window.onresize(); + + function saveFile(path) { + var vfs = QB.vfs(); + var fileNode = vfs.getNode(path); + var fileBlob = new Blob([fileNode.data]); + var url = URL.createObjectURL(fileBlob); + + var link = document.createElement("a"); + link.href = url; + link.download = fileNode.name; + link.click(); } -}); -window.addEventListener("mouseup", function() { - sliding = false; - vsliding = false; -}); - - - -function fileDragEnter(e) { - if (currTab != "fs") { return; } - e.stopPropagation(); - e.preventDefault(); - - dropArea.style.backgroundColor = "rgb(255,255,255,.1)"; -} - -function fileDragLeave(e) { - if (currTab != "fs") { return; } - e.stopPropagation(); - e.preventDefault(); - - dropArea.style.backgroundColor = "transparent"; -} - -function fileDragOver(e) { - if (currTab != "fs") { return; } - e.stopPropagation(); - e.preventDefault(); -} - -var dropArea = _e.outputContent; -dropArea.addEventListener("drop", fileDrop, false); -dropArea.addEventListener("dragover", fileDragOver, false); -dropArea.addEventListener("dragenter", fileDragEnter, false); -dropArea.addEventListener("dragleave", fileDragLeave, false); - -if (!inIframe() && appMode == "ide") { - addEventListener("beforeunload", function(e) { + async function fileDrop(e) { + if (currTab != "fs") { return; } + e.stopPropagation(); e.preventDefault(); - return e.returnValue = "stop"; - }); -} + dropArea.style.backgroundColor = "transparent"; -function inIframe () { - try { - return window.self !== window.top; - } catch (e) { - return true; + var dt = e.dataTransfer; + var files = dt.files; + + var vfs = QB.vfs(); + var parentDir = vfs.getNode(currPath); + var fr = new FileReader(); + + for (var i=0; i < files.length; i++) { + var f = files[i]; + if (!f.type && f.size%4096 == 0) { + // this is a folder, skip + continue; + } + + var file = vfs.createFile(f.name, parentDir); + var data = await f.arrayBuffer(); + vfs.writeData(file, data); + } + + _refreshFS(); } -} \ No newline at end of file + + function _onUploadFile() { + _uploadFile(_e.fsUrl.innerHTML, null, function() { + _refreshFS(); + }); + } + + function _uploadFile(destpath, filter, fnCallback) { + var vfs = QB.vfs(); + var parentDir = null; + if (destpath == undefined || destpath == "" || destpath == "/") { + parentDir = vfs.rootDirectory(); //QB.vfsCwd(); + } + else { + parentDir = vfs.getNode(destpath, vfs.rootDirectory()); //QB.vfsCwd()); + if (!parentDir) { + throw Object.assign(new Error("Path not found: [" + destpath + "]"), { _stackDepth: 1 }); + } + else if (parentDir && parentDir.type != vfs.DIRECTORY) { + throw Object.assign(new Error("Path is not a directory: [" + destpath + "]"), { _stackDepth: 1 }); + } + } + var fileInput = document.getElementById("upload-file-input"); + if (fileInput == null) { + fileInput = document.createElement("input"); + fileInput.id = "upload-file-input"; + fileInput.type = "file"; + } + fileInput.value = null; + if (filter != undefined) { + fileInput.accept = filter; + } + fileInput.onchange = function(event) { + if (event.target.files.length > 0) { + var f = event.target.files[0]; + var fr = new FileReader(); + fr.onload = function() { + var file = vfs.createFile(f.name, parentDir); + vfs.writeData(file, fr.result); + + if (fnCallback) { + fnCallback(vfs.fullPath(file)); + } + } + fr.readAsArrayBuffer(f); + } + }; + fileInput.click(); + } + + _e.slider.addEventListener("mousedown", function(event) { + sliding = true; + }); + _e.vslider.addEventListener("mousedown", function(event) { + vsliding = true; + }); + + window.addEventListener("mousemove", function(event) { + if (!sliding && !vsliding) { return; } + if (sliding) { + splitWidth = event.pageX - 10; + window.onresize(); + } + else { + splitHeight = window.innerHeight - event.pageY + 35; + window.onresize(); + } + }); + + window.addEventListener("mouseup", function() { + sliding = false; + vsliding = false; + }); + + + + function fileDragEnter(e) { + if (currTab != "fs") { return; } + e.stopPropagation(); + e.preventDefault(); + + dropArea.style.backgroundColor = "rgb(255,255,255,.1)"; + } + + function fileDragLeave(e) { + if (currTab != "fs") { return; } + e.stopPropagation(); + e.preventDefault(); + + dropArea.style.backgroundColor = "transparent"; + } + + function fileDragOver(e) { + if (currTab != "fs") { return; } + e.stopPropagation(); + e.preventDefault(); + } + + var dropArea = _e.outputContent; + dropArea.addEventListener("drop", fileDrop, false); + dropArea.addEventListener("dragover", fileDragOver, false); + dropArea.addEventListener("dragenter", fileDragEnter, false); + dropArea.addEventListener("dragleave", fileDragLeave, false); + + if (!inIframe() && appMode == "ide") { + addEventListener("beforeunload", function(e) { + e.preventDefault(); + return e.returnValue = "stop"; + }); + } + + function inIframe () { + try { + return window.self !== window.top; + } catch (e) { + return true; + } + } + + this.getErrorLine = _getErrorLine; + this.runProgram = _runProgram; + this.stopProgram = _stopProgram; + this.shareProgram = _shareProgram; + this.changeTheme = _changeTheme; + this.showOptionDialog = _showOptionDialog; + this.showDialog = _showDialog; + this.exportProgram = _exportProgram; + this.saveProject = _saveProject; + this.openProject = _openProject; + this.onSelMainProg = _onSelMainProg; + this.testShare = _testShare; + this.closeDialog = _closeDialog; + this.gotoWarning = _gotoWarning; + this.addWarningCell = _addWarningCell; + this.showConsole = _showConsole; + this.changeTab = _changeTab; + this.showHelp = _showHelp; + this.slideLeft = _slideLeft; + this.slideRight = _slideRight; + this.refreshFS = _refreshFS; + this.onNewDirectory = _onNewDirectory; + this.onUploadFile = _onUploadFile; + this.uploadFile = _uploadFile; +}; \ No newline at end of file