diff --git a/core/client.js b/core/client.js index 596f756f..0e48996b 100644 --- a/core/client.js +++ b/core/client.js @@ -1,19 +1,30 @@ "use strict"; -var gSocket; -var gCredentials; +let gSocket; +let gCredentials; -var gCurrentFile; -var gFiles = {}; -var gApp = {files: {}}; -var gEditor; -var gSplit; -var gGraphs = {}; -var gTimeSeries = {}; -var gParentApp; +let gCurrentFile; +let gFiles = {}; +let gApp = {files: {}}; +let gEditor; +let gSplit; +let gGraphs = {}; +let gTimeSeries = {}; +let gParentApp; +let gOriginalInput; -var kErrorColor = "#dc322f"; -var kStatusColor = "#fff"; +let kErrorColor = "#dc322f"; +let kStatusColor = "#fff"; + +/* Functions that server-side app code can call through app.setDocument()-style + * calls. */ +const k_api = { + setDocument: {args: ['content'], func: api_setDocument}, + postMessage: {args: ['message'], func: api_postMessage}, + error: {args: ['error'], func: api_error}, + localStorageSet: {args: ['key', 'value'], func: api_localStorageSet}, + localStorageGet: {args: ['key'], func: api_localStorageGet}, +}; window.addEventListener("keydown", function(event) { if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) { @@ -35,13 +46,13 @@ function ensureLoaded(nodes, callback) { return; } - var search = nodes.shift(); - var head = document.head; - var found = false; - for (var i = 0; i < head.childNodes.length; i++) { + let search = nodes.shift(); + let head = document.head; + let found = false; + for (let i = 0; i < head.childNodes.length; i++) { if (head.childNodes[i].tagName == search.tagName) { - var match = true; - for (var attribute in search.attributes) { + let match = true; + for (let attribute in search.attributes) { if (head.childNodes[i].attributes[attribute].value != search.attributes[attribute]) { match = false; } @@ -55,11 +66,11 @@ function ensureLoaded(nodes, callback) { if (found) { ensureLoaded(nodes, callback); } else { - var node = document.createElement(search.tagName); + let node = document.createElement(search.tagName); node.onreadystatechange = node.onload = function() { ensureLoaded(nodes, callback); }; - for (var attribute in search.attributes) { + for (let attribute in search.attributes) { node.setAttribute(attribute, search.attributes[attribute]); } head.insertBefore(node, head.firstChild); @@ -133,8 +144,8 @@ function trace() { } return response.arrayBuffer(); }).then(function(data) { - var perfetto = window.open('/perfetto/'); - var done = false; + let perfetto = window.open('/perfetto/'); + let done = false; if (perfetto) { function message_handler(message) { if (message.data == 'PONG') { @@ -232,8 +243,8 @@ function load(path) { }); } gFiles = {}; - var isApp = false; - var promises = []; + let isApp = false; + let promises = []; if (json && json['type'] == 'tildefriends-app') { isApp = true; @@ -248,7 +259,7 @@ function load(path) { } if (!isApp) { document.getElementById("editPane").style.display = 'flex'; - var text = '// New script.\n'; + let text = '// New script.\n'; gCurrentFile = 'app.js'; gFiles[gCurrentFile] = { doc: new CodeMirror.Doc(text, guessMode(gCurrentFile)), @@ -280,9 +291,9 @@ function save(save_to) { gFiles[gCurrentFile].doc = gEditor.getDoc(); } - var save_path = save_to; + let save_path = save_to; if (!save_path) { - var name = document.getElementById("name"); + let name = document.getElementById("name"); if (name && name.value) { save_path = name.value; } else { @@ -290,7 +301,7 @@ function save(save_to) { } } - var promises = []; + let promises = []; for (let name of Object.keys(gFiles)) { let file = gFiles[name]; if (file.doc.isClean(file.generation)) { @@ -318,7 +329,7 @@ function save(save_to) { } return Promise.all(promises).then(function() { - var app = { + let app = { type: "tildefriends-app", files: Object.fromEntries(Object.keys(gFiles).map(x => [x, gFiles[x].id || gApp.files[x]])), }; @@ -366,9 +377,9 @@ function pushToParent() { } function url() { - var hash = window.location.href.indexOf('#'); - var question = window.location.href.indexOf('?'); - var end = -1; + let hash = window.location.href.indexOf('#'); + let question = window.location.href.indexOf('?'); + let end = -1; if (hash != -1 && (hash < end || end == -1)) { end = hash; @@ -384,13 +395,42 @@ function hash() { return window.location.hash != "#" ? window.location.hash : ""; } +function api_setDocument(content) { + let iframe = document.getElementById("document"); + iframe.srcdoc = content; +} + +function api_postMessage(message) { + let iframe = document.getElementById("document"); + iframe.contentWindow.postMessage(message, "*"); +} + +function api_error(error) { + if (error) { + if (typeof(error) == 'string') { + setStatusMessage('⚠️ ' + error, '#f00'); + } else { + setStatusMessage('⚠️ ' + merror.message + '\n' + error.stack, '#f00'); + } + } + console.log('error', error); +} + +function api_localStorageSet(key, value) { + window.localStorage.setItem('app:' + key, value); +} + +function api_localStorageGet(key, value) { + send({message: 'localStorage', key: key, value: window.localStorage.getItem('app:' + key)}); +} + function receive(message) { if (message && message.action == "session") { setStatusMessage("🟢 Executing...", kStatusColor); gCredentials = message.credentials; gParentApp = message.parentApp; updateLogin(); - var parent_enabled = message.parentApp; + let 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") { @@ -402,32 +442,13 @@ function receive(message) { /* Stats were opened before we connected. */ send({action: 'enableStats', enabled: true}); } - } else if (message && message.action == "setDocument") { - var iframe = document.getElementById("document"); - iframe.srcdoc = message.content; - } else if (message && message.action == "postMessage") { - var iframe = document.getElementById("document"); - iframe.contentWindow.postMessage(message.message, "*"); } else if (message && message.action == "ping") { send({action: "pong"}); - } else if (message && message.action == "error") { - if (message.error) { - if (typeof(message.error) == 'string') { - setStatusMessage('⚠️ ' + message.error, '#f00'); - } else { - setStatusMessage('⚠️ ' + mmessage.error.message + '\n' + message.error.stack, '#f00'); - } - } - console.log('error', message); - } else if (message && message.action == "localStorageSet") { - window.localStorage.setItem('app:' + message.key, message.value); - } else if (message && message.action == "localStorageGet") { - send({message: 'localStorage', key: message.key, value: window.localStorage.getItem('app:' + message.key)}); } else if (message && message.action == "print") { console.log('app>', ...message.args); } else if (message && message.action == "stats") { - var now = new Date().getTime(); - for (var key of Object.keys(message.stats)) { + let now = new Date().getTime(); + for (let key of Object.keys(message.stats)) { const k_groups = { rpc_in: {group: 'rpc', name: 'in'}, rpc_out: {group: 'rpc', name: 'out'}, @@ -499,6 +520,11 @@ function receive(message) { } timeseries.append(now, message.stats[key]); } + } else if (message && message.action) { + let api = k_api[message.action]; + if (api) { + api.func(...api.args.map(x => message[x])); + } } } @@ -515,7 +541,7 @@ function keyEvent(event) { } function setStatusMessage(message, color) { - var node = document.getElementById("status"); + let node = document.getElementById("status"); while (node.firstChild) { node.removeChild(node.firstChild); } @@ -536,12 +562,12 @@ function send(value) { } function updateLogin() { - var login = document.getElementById("login"); + let login = document.getElementById("login"); while (login.firstChild) { login.removeChild(login.firstChild); } - var a = document.createElement("a"); + let a = document.createElement("a"); if (gCredentials && gCredentials.session) { a.appendChild(document.createTextNode("logout " + gCredentials.session.name)); a.setAttribute("href", "/login/logout?return=" + encodeURIComponent(url() + hash())); @@ -552,11 +578,10 @@ function updateLogin() { login.appendChild(a); } -var gOriginalInput; function dragHover(event) { event.stopPropagation(); event.preventDefault(); - var input = document.getElementById("input"); + let input = document.getElementById("input"); if (event.type == "dragover") { if (!input.classList.contains("drop")) { input.classList.add("drop"); @@ -570,17 +595,17 @@ function dragHover(event) { } function fixImage(sourceData, maxWidth, maxHeight, callback) { - var result = sourceData; - var image = new Image(); + let result = sourceData; + let image = new Image(); image.crossOrigin = "anonymous"; image.referrerPolicy = "no-referrer"; image.onload = function() { if (image.width > maxWidth || image.height > maxHeight) { - var downScale = Math.min(maxWidth / image.width, maxHeight / image.height); - var canvas = document.createElement("canvas"); + let downScale = Math.min(maxWidth / image.width, maxHeight / image.height); + let canvas = document.createElement("canvas"); canvas.width = image.width * downScale; canvas.height = image.height * downScale; - var context = canvas.getContext("2d"); + let context = canvas.getContext("2d"); context.clearRect(0, 0, canvas.width, canvas.height); image.width = canvas.width; image.height = canvas.height; @@ -605,13 +630,13 @@ function fileDropRead(event) { function fileDrop(event) { dragHover(event); - var done = false; + let done = false; if (!done) { - var files = event.target.files || event.dataTransfer.files; - for (var i = 0; i < files.length; i++) { - var file = files[i]; + let files = event.target.files || event.dataTransfer.files; + for (let i = 0; i < files.length; i++) { + let file = files[i]; if (file.type.substring(0, "image/".length) == "image/") { - var reader = new FileReader(); + let reader = new FileReader(); reader.onloadend = fileDropRead; reader.readAsDataURL(file); done = true; @@ -620,8 +645,8 @@ function fileDrop(event) { } if (!done) { - var html = event.dataTransfer.getData("text/html"); - var match = / [].concat([key], value.args)), })); } gSocket.onmessage = function(event) { @@ -755,8 +774,8 @@ function connectSocket(path) { } function openFile(name) { - var newDoc = (name && gFiles[name]) ? gFiles[name].doc : new CodeMirror.Doc("", guessMode(name)); - var oldDoc = gEditor.swapDoc(newDoc); + let newDoc = (name && gFiles[name]) ? gFiles[name].doc : new CodeMirror.Doc("", guessMode(name)); + let oldDoc = gEditor.swapDoc(newDoc); if (gFiles[gCurrentFile]) { gFiles[gCurrentFile].doc = oldDoc; } @@ -770,13 +789,13 @@ function onFileClicked(event) { } function updateFiles() { - var node = document.getElementById("files"); + let node = document.getElementById("files"); while (node.firstChild) { node.removeChild(node.firstChild); } - for (var file of Object.keys(gFiles).sort()) { - var li = document.createElement("li"); + for (let file of Object.keys(gFiles).sort()) { + let li = document.createElement("li"); li.onclick = onFileClicked; li.appendChild(document.createTextNode(file)); if (file == gCurrentFile) { @@ -800,7 +819,7 @@ function makeNewFile(name) { } function newFile() { - var name = prompt("Name of new file:", "file.js"); + let name = prompt("Name of new file:", "file.js"); if (name && !gFiles[name]) { makeNewFile(name); } @@ -823,18 +842,18 @@ window.addEventListener("load", function() { for (let tag of document.getElementsByTagName('a')) { if (tag.accessKey) { tag.classList.add('tooltip_parent'); - var tooltip = document.createElement('div'); + let tooltip = document.createElement('div'); tooltip.classList.add('tooltip'); if (tag.dataset.tip) { - var description = document.createElement('div'); + let description = document.createElement('div'); description.innerText = tag.dataset.tip; tooltip.appendChild(description); } - var parts = tag.accessKeyLabel ? tag.accessKeyLabel.split('+') : []; - for (var i = 0; i < parts.length; i++) + let parts = tag.accessKeyLabel ? tag.accessKeyLabel.split('+') : []; + for (let i = 0; i < parts.length; i++) { - var key = parts[i]; - var kbd = document.createElement('kbd'); + let key = parts[i]; + let kbd = document.createElement('kbd'); kbd.innerText = key; tooltip.appendChild(kbd); if (i < parts.length - 1) {