diff --git a/core/app.js b/core/app.js index 7c34b808..97afb5e7 100644 --- a/core/app.js +++ b/core/app.js @@ -64,6 +64,7 @@ function socket(request, response, client) { var packageName; var blobId; var match; + var parentApp; if (match = /^\/([&%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(message.path)) { blobId = match[1]; } else if (match = /^\/\~([^\/]+)\/([^\/]+)\/$/.exec(message.path)) { @@ -74,8 +75,20 @@ function socket(request, response, client) { response.send(JSON.stringify({action: "error", error: message.path + ' not found'}), 0x1); return; } + if (user != 'core') { + var coreId = await new Database('core').get('path:' + path); + parentApp = { + path: '/~core/' + path + '/', + id: coreId, + }; + } } - response.send(JSON.stringify({action: "session", credentials: credentials}), 0x1); + response.send(JSON.stringify({ + action: "session", + credentials: credentials, + parentApp: parentApp, + id: blobId, + }), 0x1); options.api = message.api || []; options.credentials = credentials; diff --git a/core/client.js b/core/client.js index cf03978d..a7fc3cf3 100644 --- a/core/client.js +++ b/core/client.js @@ -9,6 +9,7 @@ var gApp = {files: {}}; var gEditor; var gSplit; var gGraphs = {}; +var gParentApp; var kErrorColor = "#dc322f"; var kStatusColor = "#fff"; @@ -164,97 +165,114 @@ function guessMode(name) { } function loadFile(name, id) { - var request = new XMLHttpRequest(); - request.addEventListener("loadend", function() { - if (request.status == 200) { - gFiles[name].doc = new CodeMirror.Doc(request.responseText, guessMode(name)); - if (!Object.values(gFiles).some(x => !x.doc)) { - document.getElementById("editPane").style.display = 'flex'; - openFile(Object.keys(gFiles).sort()[0]); + return new Promise(function(resolve, reject) { + var request = new XMLHttpRequest(); + request.addEventListener("loadend", function() { + if (request.status == 200) { + gFiles[name].doc = new CodeMirror.Doc(request.responseText, guessMode(name)); + if (!Object.values(gFiles).some(x => !x.doc)) { + document.getElementById("editPane").style.display = 'flex'; + openFile(Object.keys(gFiles).sort()[0]); + resolve(); + } + } else { + reject('Error loading source.'); } - } + }); + request.addEventListener("error", function() { + alert("Error loading source."); + closeEditor(); + reject('Error loading source.'); + }); + request.addEventListener("timeout", function() { + alert("Timed out loading source."); + closeEditor(); + reject('Timed out loading source.'); + }); + request.addEventListener("abort", function() { + alert("Loading source aborted."); + closeEditor(); + reject('Loading source aborted.'); + }); + request.open("GET", "/" + id + "/view"); + request.send(); }); - request.addEventListener("error", function() { - alert("Error loading source."); - closeEditor(); - }); - request.addEventListener("timeout", function() { - alert("Timed out loading source."); - closeEditor(); - }); - request.addEventListener("abort", function() { - alert("Loading source aborted."); - closeEditor(); - }); - request.open("GET", "/" + id + "/view"); - request.send(); } -function load() { - var request = new XMLHttpRequest(); - request.addEventListener("loadend", function() { - if (request.status == 200 || request.status == 404) { - if (!gEditor) { - gEditor = CodeMirror.fromTextArea(document.getElementById("editor"), { - 'theme': 'base16-dark', - 'lineNumbers': true, - 'tabSize': 4, - 'indentUnit': 4, - 'indentWithTabs': true, - 'showTrailingSpace': true, - }); - gEditor.on('changes', function() { - updateFiles(); - }); - } - gFiles = {}; - var text; - var isApp = false; - if (request.status == 200) { - text = request.responseText; - try { - var json = JSON.parse(text); - if (json && json['type'] == 'tildefriends-app') { - isApp = true; - Object.keys(json['files']).forEach(function(name) { - gFiles[name] = {}; - loadFile(name, json['files'][name]); - }); - if (Object.keys(json['files']).length == 0) { - document.getElementById("editPane").style.display = 'flex'; +function load(path) { + return new Promise(function(resolve, reject) { + var request = new XMLHttpRequest(); + request.addEventListener("loadend", function() { + if (request.status == 200 || request.status == 404) { + if (!gEditor) { + gEditor = CodeMirror.fromTextArea(document.getElementById("editor"), { + 'theme': 'base16-dark', + 'lineNumbers': true, + 'tabSize': 4, + 'indentUnit': 4, + 'indentWithTabs': true, + 'showTrailingSpace': true, + }); + gEditor.on('changes', function() { + updateFiles(); + }); + } + gFiles = {}; + var text; + var isApp = false; + var promises = []; + if (request.status == 200) { + text = request.responseText; + try { + var json = JSON.parse(text); + if (json && json['type'] == 'tildefriends-app') { + isApp = true; + Object.keys(json['files']).forEach(function(name) { + gFiles[name] = {}; + promises.push(loadFile(name, json['files'][name])); + }); + if (Object.keys(json['files']).length == 0) { + document.getElementById("editPane").style.display = 'flex'; + } + gApp = JSON.parse(text); } - gApp = JSON.parse(text); + } catch { } - } catch { + } else { + reject('Load failed.'); } - } - if (!isApp) { - document.getElementById("editPane").style.display = 'flex'; - if (!text) { - text = '// New script.\n'; + if (!isApp) { + document.getElementById("editPane").style.display = 'flex'; + if (!text) { + text = '// New script.\n'; + } + gCurrentFile = 'app.js'; + gFiles[gCurrentFile] = { + doc: new CodeMirror.Doc(text, guessMode(gCurrentFile)), + }; + openFile(gCurrentFile); } - gCurrentFile = 'app.js'; - gFiles[gCurrentFile] = { - doc: new CodeMirror.Doc(text, guessMode(gCurrentFile)), - }; - openFile(gCurrentFile); + Promise.all(promises).then(resolve).catch(reject); } - } + }); + request.addEventListener("error", function() { + alert("Error loading source."); + closeEditor(); + reject('Error loading source.'); + }); + request.addEventListener("timeout", function() { + alert("Timed out loading source."); + closeEditor(); + reject('Timed out loading source.'); + }); + request.addEventListener("abort", function() { + alert("Loading source aborted."); + closeEditor(); + reject('Loading source aborted.'); + }); + request.open("GET", (path || url()) + "view"); + request.send(); }); - request.addEventListener("error", function() { - alert("Error loading source."); - closeEditor(); - }); - request.addEventListener("timeout", function() { - alert("Timed out loading source."); - closeEditor(); - }); - request.addEventListener("abort", function() { - alert("Loading source aborted."); - closeEditor(); - }); - request.open("GET", url() + "view"); - request.send(); } function closeStats() { @@ -277,22 +295,34 @@ function explodePath() { return /^\/~([^\/]+)\/([^\/]+)(.*)/.exec(window.location.pathname); } -function save() { +function save(save_to) { document.getElementById("save").disabled = true; + document.getElementById("push_to_parent").disabled = true; + document.getElementById("pull_from_parent").disabled = true; if (gCurrentFile) { gFiles[gCurrentFile].doc = gEditor.getDoc(); } - var run = document.getElementById("run").checked; - var appFinished = function(success) { document.getElementById("save").disabled = false; + document.getElementById("push_to_parent").disabled = false; + document.getElementById("pull_from_parent").disabled = false; Object.values(gFiles).forEach(function(file) { file.generation = file.doc.changeGeneration(); }); updateFiles(); } + var save_path = save_to; + if (!save_to) { + var name = document.getElementById("name"); + if (name && name.value) { + save_path = name.value; + } else { + save_path = url(); + } + } + var always = function() { var anyUnfinished = Object.values(gFiles).some(x => x.request); var anyUnsaved = Object.values(gFiles).some(x => !x.doc.isClean(x.generation) && !x.id); @@ -312,15 +342,10 @@ function save() { }); request.addEventListener("loadend", function() { if (request.status == 200) { - var latest = document.getElementById('latest'); - latest.href = request.responseText; - latest.style.visibility = request.responseText != window.location.path ? 'visible' : 'hidden'; - if (run) { - if (request.responseText) { - reconnect(request.responseText); - } else { - reconnect(window.location.path); - } + if (save_path != window.location.pathname) { + alert('Saved to ' + save_path + '.'); + } else { + reconnect(save_path); } appFinished(true); } else { @@ -337,14 +362,7 @@ function save() { appFinished(false); }); - var saveTo = null; - var name = document.getElementById("name"); - if (name && name.value) { - saveTo = name.value + "save"; - } else { - saveTo = url() + "save"; - } - request.open("POST", saveTo, true); + request.open("POST", save_path + 'save', true); request.setRequestHeader("Content-Type", "text/json"); request.send(JSON.stringify(app)); } else if (!anyUnfinished) { @@ -399,6 +417,14 @@ function save() { } } +function pullFromParent() { + load(gParentApp ? gParentApp.path : null).then(x => save()); +} + +function pushToParent() { + save(gParentApp ? gParentApp.path : null); +} + function url() { var hash = window.location.href.indexOf('#'); var question = window.location.href.indexOf('?'); @@ -422,7 +448,11 @@ function receive(message) { if (message && message.action == "session") { setStatusMessage("...Executing...", kStatusColor, true); gCredentials = message.credentials; + gParentApp = message.parentApp; updateLogin(); + var parent_enabled = message.parentApp; + document.getElementById('push_to_parent').style.display = parent_enabled ? 'inline-block' : 'none'; + document.getElementById('pull_from_parent').style.display = parent_enabled ? 'inline-block' : 'none'; } else if (message && message.action == "ready") { setStatusMessage(null); if (window.location.hash) { diff --git a/core/core.js b/core/core.js index 54ed9b92..5f637b04 100644 --- a/core/core.js +++ b/core/core.js @@ -479,22 +479,25 @@ async function blobHandler(request, response, blobId, uri) { var user = match[1]; var app = match[2]; var credentials = auth.query(request.headers); - if (!credentials || !credentials.session || credentials.session.name != user) { + if (credentials && credentials.session && + (credentials.session.name == user || + (credentials.permissions.administration && user == 'core'))) { + var database = new Database(user); + var apps = new Set(); + try { + apps = new Set(JSON.parse(database.get('apps'))); + } catch { + } + if (!apps.has(app)) { + apps.add(app); + database.set('apps', JSON.stringify([...apps])); + } + database.set('path:' + app, newBlobId); + } else { response.writeHead(401, {"Content-Type": "text/plain; charset=utf-8"}); response.end("401 Unauthorized"); return; } - var database = new Database(user); - var apps = new Set(); - try { - apps = new Set(JSON.parse(database.get('apps'))); - } catch { - } - if (!apps.has(app)) { - apps.add(app); - database.set('apps', JSON.stringify([...apps])); - } - database.set('path:' + app, newBlobId); } response.writeHead(200, {"Content-Type": "text/plain; charset=utf-8"}); @@ -594,7 +597,7 @@ loadSettings().then(function() { return staticFileHandler(request, response, null, match[1]); } else if (match = /^\/perfetto\/([\.\w-/]*)$/.exec(request.uri)) { return perfettoHandler(request, response, match[1]); - } else if (match = /^(.*)(\/save)$/.exec(request.uri)) { + } else if (match = /^(.*)(\/save?)$/.exec(request.uri)) { return blobHandler(request, response, match[1], match[2]); } else if (match = /^\/trace$/.exec(request.uri)) { var data = trace(); diff --git a/core/index.html b/core/index.html index adcdbd40..eb30e9d5 100644 --- a/core/index.html +++ b/core/index.html @@ -31,9 +31,9 @@ - + + - Latest