2016-03-12 13:50:43 -05:00
|
|
|
"use strict";
|
|
|
|
|
2016-05-01 09:24:37 -04:00
|
|
|
var gSocket;
|
2016-03-12 13:50:43 -05:00
|
|
|
var gCredentials;
|
2016-04-06 21:44:22 -04:00
|
|
|
|
2021-01-02 13:10:00 -05:00
|
|
|
var gCurrentFile;
|
|
|
|
var gFiles = {};
|
2021-01-12 21:40:46 -05:00
|
|
|
var gApp = {files: {}};
|
2017-01-16 10:24:44 -05:00
|
|
|
var gEditor;
|
2022-01-12 21:18:40 -05:00
|
|
|
var gSplit;
|
2022-01-20 22:09:23 -05:00
|
|
|
var gGraphs = {};
|
2022-01-30 09:51:09 -05:00
|
|
|
var gParentApp;
|
2016-03-12 13:50:43 -05:00
|
|
|
|
2017-05-22 15:38:49 -04:00
|
|
|
var kErrorColor = "#dc322f";
|
|
|
|
var kStatusColor = "#fff";
|
|
|
|
|
2017-01-16 10:24:44 -05:00
|
|
|
window.addEventListener("keydown", function(event) {
|
2022-02-03 18:57:47 -05:00
|
|
|
if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
|
2017-01-16 10:24:44 -05:00
|
|
|
if (editing()) {
|
|
|
|
save();
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
} else if (event.keyCode == 66 && event.altKey) {
|
|
|
|
if (editing()) {
|
|
|
|
closeEditor();
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
function ensureLoaded(nodes, callback) {
|
|
|
|
if (!nodes.length) {
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var search = nodes.shift();
|
|
|
|
var head = document.head;
|
|
|
|
var found = false;
|
|
|
|
for (var i = 0; i < head.childNodes.length; i++) {
|
|
|
|
if (head.childNodes[i].tagName == search.tagName) {
|
|
|
|
var match = true;
|
|
|
|
for (var attribute in search.attributes) {
|
|
|
|
if (head.childNodes[i].attributes[attribute].value != search.attributes[attribute]) {
|
|
|
|
match = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (match) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (found) {
|
|
|
|
ensureLoaded(nodes, callback);
|
|
|
|
} else {
|
|
|
|
var node = document.createElement(search.tagName);
|
|
|
|
node.onreadystatechange = node.onload = function() {
|
|
|
|
ensureLoaded(nodes, callback);
|
|
|
|
};
|
|
|
|
for (var attribute in search.attributes) {
|
|
|
|
node.setAttribute(attribute, search.attributes[attribute]);
|
|
|
|
}
|
|
|
|
head.insertBefore(node, head.firstChild);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function editing() {
|
|
|
|
return document.getElementById("editPane").style.display != 'none';
|
|
|
|
}
|
|
|
|
|
2022-02-03 18:57:47 -05:00
|
|
|
function toggleEdit() {
|
|
|
|
if (editing()) {
|
|
|
|
closeEditor();
|
|
|
|
} else {
|
|
|
|
edit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-16 10:24:44 -05:00
|
|
|
function edit() {
|
|
|
|
if (editing()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-07 16:06:20 -05:00
|
|
|
window.localStorage.setItem('editing', '1');
|
2022-02-03 18:57:47 -05:00
|
|
|
if (gSplit) {
|
|
|
|
gSplit.destroy();
|
|
|
|
gSplit = undefined;
|
|
|
|
}
|
2022-01-12 21:18:40 -05:00
|
|
|
gSplit = Split(['#editPane', '#viewPane'], {minSize: 0});
|
|
|
|
|
2017-01-16 10:24:44 -05:00
|
|
|
ensureLoaded([
|
2021-12-22 09:27:52 -05:00
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/codemirror.min.js"}},
|
|
|
|
{tagName: "link", attributes: {rel: "stylesheet", href: "/static/codemirror/base16-dark.min.css"}},
|
|
|
|
{tagName: "link", attributes: {rel: "stylesheet", href: "/static/codemirror/matchesonscrollbar.min.css"}},
|
|
|
|
{tagName: "link", attributes: {rel: "stylesheet", href: "/static/codemirror/dialog.min.css"}},
|
|
|
|
{tagName: "link", attributes: {rel: "stylesheet", href: "/static/codemirror/codemirror.min.css"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/trailingspace.min.js"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/dialog.min.js"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/search.min.js"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/searchcursor.min.js"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/jump-to-line.min.js"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/matchesonscrollbar.min.js"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/annotatescrollbar.min.js"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/javascript.min.js"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/css.min.js"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/xml.min.js"}},
|
|
|
|
{tagName: "script", attributes: {src: "/static/codemirror/htmlmixed.min.js"}},
|
2017-01-16 10:24:44 -05:00
|
|
|
], function() {
|
2022-02-16 21:29:04 -05:00
|
|
|
load().catch(function(error) {
|
|
|
|
alert(error);
|
|
|
|
closeEditor();
|
|
|
|
});
|
2017-01-16 10:24:44 -05:00
|
|
|
});
|
2021-01-02 13:10:00 -05:00
|
|
|
}
|
|
|
|
|
2022-02-26 17:51:35 -05:00
|
|
|
function hideFiles() {
|
2022-03-07 16:06:20 -05:00
|
|
|
window.localStorage.setItem('files', '0');
|
2022-02-26 17:51:35 -05:00
|
|
|
document.getElementById('filesPane').classList.add('collapsed');
|
|
|
|
}
|
|
|
|
|
|
|
|
function showFiles() {
|
2022-03-07 16:06:20 -05:00
|
|
|
window.localStorage.setItem('files', '1');
|
2022-02-26 17:51:35 -05:00
|
|
|
document.getElementById('filesPane').classList.remove('collapsed');
|
|
|
|
}
|
|
|
|
|
2022-01-02 14:10:45 -05:00
|
|
|
function trace() {
|
2022-02-16 21:29:04 -05:00
|
|
|
fetch('/trace')
|
|
|
|
.then(function(response) {
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Request failed: ' + response.status + ' ' + response.statusText);
|
|
|
|
}
|
|
|
|
return response.arrayBuffer();
|
|
|
|
}).then(function(data) {
|
2022-01-02 14:10:45 -05:00
|
|
|
var perfetto = window.open('/perfetto/');
|
|
|
|
var done = false;
|
|
|
|
if (perfetto) {
|
|
|
|
function message_handler(message) {
|
|
|
|
if (message.data == 'PONG') {
|
|
|
|
perfetto.postMessage({
|
|
|
|
perfetto: {
|
2022-02-16 21:29:04 -05:00
|
|
|
buffer: data,
|
2022-01-02 14:10:45 -05:00
|
|
|
title: 'Tilde Friends Trace',
|
|
|
|
url: window.location.href,
|
|
|
|
}
|
|
|
|
}, '*');
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
window.addEventListener('message', message_handler);
|
|
|
|
function ping_perfetto() {
|
|
|
|
perfetto.postMessage('PING', window.location.origin);
|
|
|
|
if (!done && !perfetto.closed) {
|
|
|
|
setTimeout(ping_perfetto, 50);
|
|
|
|
} else {
|
|
|
|
window.removeEventListener('message', message_handler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setTimeout(ping_perfetto, 50);
|
|
|
|
} else {
|
|
|
|
alert("Unable to open perfetto.");
|
|
|
|
}
|
2022-02-16 21:29:04 -05:00
|
|
|
}).catch(function(error) {
|
|
|
|
alert('Failed to load trace: ' + error);
|
|
|
|
});
|
2022-01-02 14:10:45 -05:00
|
|
|
}
|
|
|
|
|
2022-01-20 21:53:15 -05:00
|
|
|
function stats() {
|
2022-03-07 16:06:20 -05:00
|
|
|
window.localStorage.setItem('stats', '1');
|
2022-01-20 21:53:15 -05:00
|
|
|
document.getElementById("statsPane").style.display = 'flex';
|
2022-02-26 16:17:15 -05:00
|
|
|
send({action: 'enableStats', enabled: true});
|
|
|
|
}
|
|
|
|
|
|
|
|
function closeStats() {
|
2022-03-07 16:06:20 -05:00
|
|
|
window.localStorage.setItem('stats', '0');
|
2022-02-26 16:17:15 -05:00
|
|
|
document.getElementById("statsPane").style.display = 'none';
|
|
|
|
send({action: 'enableStats', enabled: false});
|
2022-01-20 21:53:15 -05:00
|
|
|
}
|
|
|
|
|
2022-02-05 18:04:04 -05:00
|
|
|
function toggleStats() {
|
|
|
|
if (document.getElementById("statsPane").style.display == 'none') {
|
|
|
|
stats();
|
|
|
|
} else {
|
|
|
|
closeStats();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-02 13:10:00 -05:00
|
|
|
function guessMode(name) {
|
|
|
|
return name.endsWith(".js") ? "javascript" :
|
|
|
|
name.endsWith(".html") ? "htmlmixed" :
|
|
|
|
null;
|
|
|
|
}
|
2017-01-16 10:24:44 -05:00
|
|
|
|
2021-01-02 13:10:00 -05:00
|
|
|
function loadFile(name, id) {
|
2022-02-16 21:29:04 -05:00
|
|
|
return fetch('/' + id + '/view').then(function(response) {
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Request failed: ' + response.status + ' ' + response.statusText);
|
|
|
|
}
|
|
|
|
return response.text();
|
|
|
|
}).then(function(text) {
|
|
|
|
gFiles[name].doc = new CodeMirror.Doc(text, guessMode(name));
|
|
|
|
if (!Object.values(gFiles).some(x => !x.doc)) {
|
|
|
|
document.getElementById("editPane").style.display = 'flex';
|
|
|
|
openFile(Object.keys(gFiles).sort()[0]);
|
|
|
|
}
|
2021-01-02 13:10:00 -05:00
|
|
|
});
|
2017-01-16 10:24:44 -05:00
|
|
|
}
|
|
|
|
|
2022-01-30 09:51:09 -05:00
|
|
|
function load(path) {
|
2022-02-16 21:29:04 -05:00
|
|
|
return fetch((path || url()) + 'view').then(function(response) {
|
|
|
|
if (!response.ok) {
|
2022-02-26 21:35:51 -05:00
|
|
|
if (response.status == 404) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
throw new Error(response.status + ' ' + response.statusText);
|
|
|
|
}
|
2022-02-16 21:29:04 -05:00
|
|
|
}
|
|
|
|
return response.json();
|
|
|
|
}).then(function(json) {
|
|
|
|
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 isApp = false;
|
|
|
|
var promises = [];
|
|
|
|
|
|
|
|
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';
|
2017-01-18 18:21:42 -05:00
|
|
|
}
|
2022-02-16 21:29:04 -05:00
|
|
|
gApp = json;
|
|
|
|
}
|
|
|
|
if (!isApp) {
|
|
|
|
document.getElementById("editPane").style.display = 'flex';
|
|
|
|
var text = '// New script.\n';
|
|
|
|
gCurrentFile = 'app.js';
|
|
|
|
gFiles[gCurrentFile] = {
|
|
|
|
doc: new CodeMirror.Doc(text, guessMode(gCurrentFile)),
|
|
|
|
};
|
|
|
|
openFile(gCurrentFile);
|
|
|
|
}
|
|
|
|
return Promise.all(promises);
|
2017-01-16 10:24:44 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function closeEditor() {
|
2022-03-07 16:06:20 -05:00
|
|
|
window.localStorage.setItem('editing', '0');
|
2017-01-16 10:24:44 -05:00
|
|
|
document.getElementById("editPane").style.display = 'none';
|
2022-01-20 21:53:15 -05:00
|
|
|
if (gSplit) {
|
|
|
|
gSplit.destroy();
|
|
|
|
gSplit = undefined;
|
|
|
|
}
|
2017-01-16 10:24:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function explodePath() {
|
|
|
|
return /^\/~([^\/]+)\/([^\/]+)(.*)/.exec(window.location.pathname);
|
|
|
|
}
|
|
|
|
|
2022-01-30 09:51:09 -05:00
|
|
|
function save(save_to) {
|
2017-01-16 10:24:44 -05:00
|
|
|
document.getElementById("save").disabled = true;
|
2022-01-30 09:51:09 -05:00
|
|
|
document.getElementById("push_to_parent").disabled = true;
|
|
|
|
document.getElementById("pull_from_parent").disabled = true;
|
2021-01-02 13:10:00 -05:00
|
|
|
if (gCurrentFile) {
|
|
|
|
gFiles[gCurrentFile].doc = gEditor.getDoc();
|
|
|
|
}
|
2017-01-16 10:24:44 -05:00
|
|
|
|
2022-01-30 09:51:09 -05:00
|
|
|
var save_path = save_to;
|
2022-02-16 21:29:04 -05:00
|
|
|
if (!save_path) {
|
2022-01-30 09:51:09 -05:00
|
|
|
var name = document.getElementById("name");
|
|
|
|
if (name && name.value) {
|
|
|
|
save_path = name.value;
|
|
|
|
} else {
|
|
|
|
save_path = url();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-16 21:29:04 -05:00
|
|
|
var promises = [];
|
|
|
|
for (let name of Object.keys(gFiles)) {
|
|
|
|
let file = gFiles[name];
|
2021-01-12 21:40:46 -05:00
|
|
|
if (file.doc.isClean(file.generation)) {
|
2022-02-16 21:29:04 -05:00
|
|
|
continue;
|
2021-01-12 21:40:46 -05:00
|
|
|
}
|
|
|
|
|
2021-01-02 13:10:00 -05:00
|
|
|
delete file.id;
|
2022-02-16 21:29:04 -05:00
|
|
|
promises.push(fetch('/save', {
|
|
|
|
method: 'POST',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'text/plain',
|
|
|
|
},
|
|
|
|
body: file.doc.getValue(),
|
|
|
|
}).then(function(response) {
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Saving "' + name + '": ' + response.status + ' ' + response.statusText);
|
|
|
|
}
|
|
|
|
return response.text();
|
|
|
|
}).then(function(text) {
|
|
|
|
file.id = text;
|
|
|
|
if (file.id.charAt(0) == '/') {
|
|
|
|
file.id = file.id.substr(1);
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.all(promises).then(function() {
|
|
|
|
var app = {
|
|
|
|
type: "tildefriends-app",
|
|
|
|
files: Object.fromEntries(Object.keys(gFiles).map(x => [x, gFiles[x].id || gApp.files[x]])),
|
|
|
|
};
|
|
|
|
Object.values(gFiles).forEach(function(file) { delete file.id; });
|
|
|
|
gApp = JSON.parse(JSON.stringify(app));
|
|
|
|
|
|
|
|
return fetch(save_path + 'save', {
|
|
|
|
method: 'POST',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
},
|
|
|
|
body: JSON.stringify(app),
|
|
|
|
}).then(function(response) {
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error(response.status + ' ' + response.statusText);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (save_path != window.location.pathname) {
|
|
|
|
alert('Saved to ' + save_path + '.');
|
2021-01-02 13:10:00 -05:00
|
|
|
} else {
|
2022-02-16 21:29:04 -05:00
|
|
|
reconnect(save_path);
|
2021-01-02 13:10:00 -05:00
|
|
|
}
|
|
|
|
});
|
2022-02-16 21:29:04 -05:00
|
|
|
}).catch(function(error) {
|
|
|
|
alert(error);
|
|
|
|
}).finally(function() {
|
|
|
|
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();
|
2021-01-02 13:10:00 -05:00
|
|
|
});
|
2022-02-16 21:29:04 -05:00
|
|
|
updateFiles();
|
2021-01-02 13:10:00 -05:00
|
|
|
});
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
|
2022-01-30 09:51:09 -05:00
|
|
|
function pullFromParent() {
|
2022-02-16 21:29:04 -05:00
|
|
|
load(gParentApp ? gParentApp.path : null).then(x => save()).catch(function(error) {
|
|
|
|
alert(error)
|
|
|
|
});
|
2022-01-30 09:51:09 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function pushToParent() {
|
|
|
|
save(gParentApp ? gParentApp.path : null);
|
|
|
|
}
|
|
|
|
|
2016-03-12 13:50:43 -05:00
|
|
|
function url() {
|
|
|
|
var hash = window.location.href.indexOf('#');
|
|
|
|
var question = window.location.href.indexOf('?');
|
2016-04-06 21:30:07 -04:00
|
|
|
var end = -1;
|
|
|
|
if (hash != -1 && (hash < end || end == -1))
|
|
|
|
{
|
|
|
|
end = hash;
|
|
|
|
}
|
|
|
|
if (question != -1 && (question < end || end == -1))
|
|
|
|
{
|
|
|
|
end = question;
|
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
return end != -1 ? window.location.href.substring(0, end) : window.location.href;
|
|
|
|
}
|
|
|
|
|
2016-04-06 21:30:07 -04:00
|
|
|
function hash() {
|
|
|
|
return window.location.hash != "#" ? window.location.hash : "";
|
|
|
|
}
|
|
|
|
|
2021-01-02 13:10:00 -05:00
|
|
|
function receive(message) {
|
|
|
|
if (message && message.action == "session") {
|
|
|
|
setStatusMessage("...Executing...", kStatusColor, true);
|
|
|
|
gCredentials = message.credentials;
|
2022-01-30 09:51:09 -05:00
|
|
|
gParentApp = message.parentApp;
|
2021-01-02 13:10:00 -05:00
|
|
|
updateLogin();
|
2022-01-30 09:51:09 -05:00
|
|
|
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';
|
2021-01-02 13:10:00 -05:00
|
|
|
} else if (message && message.action == "ready") {
|
|
|
|
setStatusMessage(null);
|
|
|
|
if (window.location.hash) {
|
|
|
|
send({event: "hashChange", hash: window.location.hash});
|
|
|
|
}
|
2022-03-07 16:06:20 -05:00
|
|
|
if (window.localStorage.getItem('stats') == '1') {
|
|
|
|
/* Stats were opened before we connected. */
|
|
|
|
send({action: 'enableStats', enabled: true});
|
|
|
|
}
|
2021-01-02 13:10:00 -05:00
|
|
|
} 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") {
|
2022-01-29 15:43:19 -05:00
|
|
|
send({action: "pong"});
|
2021-01-02 13:10:00 -05:00
|
|
|
} else if (message && message.action == "error") {
|
2022-01-20 21:53:15 -05:00
|
|
|
if (message.error) {
|
|
|
|
if (typeof(message.error) == 'string') {
|
|
|
|
setStatusMessage(message.error, '#f00', false);
|
|
|
|
} else {
|
|
|
|
setStatusMessage(message.error.message + '\n' + message.error.stack, '#f00', false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
console.log('error', message);
|
2022-03-27 15:53:02 -04:00
|
|
|
} 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)});
|
2022-02-13 17:39:22 -05:00
|
|
|
} else if (message && message.action == "print") {
|
|
|
|
console.log('app>', ...message.args);
|
2022-01-20 21:53:15 -05:00
|
|
|
} else if (message && message.action == "stats") {
|
2022-01-20 22:09:23 -05:00
|
|
|
var now = new Date().getTime();
|
|
|
|
for (var key of Object.keys(message.stats)) {
|
|
|
|
if (!gGraphs[key]) {
|
|
|
|
var graph = {
|
|
|
|
chart: new SmoothieChart({
|
2022-02-05 12:52:37 -05:00
|
|
|
millisPerPixel: 100,
|
|
|
|
minValue: 0,
|
2022-01-20 22:09:23 -05:00
|
|
|
grid: {
|
2022-02-05 12:52:37 -05:00
|
|
|
millisPerLine: 1000,
|
|
|
|
verticalSections: 10,
|
2022-01-20 22:09:23 -05:00
|
|
|
},
|
|
|
|
}),
|
|
|
|
canvas: document.createElement('canvas'),
|
|
|
|
timeseries: new TimeSeries(),
|
|
|
|
};
|
|
|
|
gGraphs[key] = graph;
|
2022-03-07 16:06:20 -05:00
|
|
|
graph.canvas.width = 240;
|
|
|
|
graph.canvas.height = 64;
|
2022-01-20 22:09:23 -05:00
|
|
|
var div = document.createElement('div');
|
|
|
|
div.innerText = key;
|
2022-03-07 16:06:20 -05:00
|
|
|
div.style.flex = '0';
|
2022-01-20 22:09:23 -05:00
|
|
|
document.getElementById('graphs').appendChild(div);
|
|
|
|
document.getElementById('graphs').appendChild(graph.canvas);
|
|
|
|
graph.chart.streamTo(graph.canvas, 1000);
|
2022-02-05 12:52:37 -05:00
|
|
|
graph.chart.addTimeSeries(graph.timeseries, {lineWidth: 2});
|
2022-01-20 21:53:15 -05:00
|
|
|
}
|
2022-01-20 22:09:23 -05:00
|
|
|
gGraphs[key].timeseries.append(now, message.stats[key]);
|
2022-01-17 16:46:32 -05:00
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-02 19:01:55 -04:00
|
|
|
function keyEvent(event) {
|
|
|
|
send({
|
|
|
|
event: "key",
|
|
|
|
type: event.type,
|
|
|
|
which: event.which,
|
|
|
|
keyCode: event.keyCode,
|
|
|
|
charCode: event.charCode,
|
|
|
|
character: String.fromCharCode(event.keyCode || event.which),
|
2016-06-02 19:48:45 -04:00
|
|
|
altKey: event.altKey,
|
2016-06-02 19:01:55 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-05-22 15:38:49 -04:00
|
|
|
function setStatusMessage(message, color, keep) {
|
2016-04-10 20:28:42 -04:00
|
|
|
var node = document.getElementById("status");
|
2017-05-22 15:38:49 -04:00
|
|
|
if (!keep) {
|
|
|
|
while (node.firstChild) {
|
|
|
|
node.removeChild(node.firstChild);
|
|
|
|
}
|
2016-04-10 20:28:42 -04:00
|
|
|
}
|
2016-05-07 07:07:54 -04:00
|
|
|
if (message) {
|
|
|
|
node.appendChild(document.createTextNode(message));
|
2021-01-02 13:10:00 -05:00
|
|
|
node.setAttribute("style", "display: inline-block; vertical-align: top; white-space: pre; color: " + (color || kErrorColor));
|
2016-05-07 07:07:54 -04:00
|
|
|
}
|
2016-04-10 20:28:42 -04:00
|
|
|
}
|
|
|
|
|
2021-01-02 13:10:00 -05:00
|
|
|
function send(value) {
|
2016-04-10 20:09:21 -04:00
|
|
|
try {
|
2021-01-02 13:10:00 -05:00
|
|
|
gSocket.send(JSON.stringify(value));
|
2016-04-10 20:09:21 -04:00
|
|
|
} catch (error) {
|
2017-05-22 15:38:49 -04:00
|
|
|
setStatusMessage("Send failed: " + error.toString(), kErrorColor);
|
2016-04-10 20:09:21 -04:00
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function updateLogin() {
|
|
|
|
var login = document.getElementById("login");
|
|
|
|
while (login.firstChild) {
|
|
|
|
login.removeChild(login.firstChild);
|
|
|
|
}
|
|
|
|
|
|
|
|
var a = document.createElement("a");
|
|
|
|
if (gCredentials && gCredentials.session) {
|
|
|
|
a.appendChild(document.createTextNode("logout " + gCredentials.session.name));
|
2021-01-02 13:10:00 -05:00
|
|
|
a.setAttribute("href", "/login/logout?return=" + encodeURIComponent(url() + hash()));
|
2016-03-12 13:50:43 -05:00
|
|
|
} else {
|
2016-04-14 19:37:23 -04:00
|
|
|
a.appendChild(document.createTextNode("login"));
|
|
|
|
a.setAttribute("href", "/login?return=" + encodeURIComponent(url() + hash()));
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
login.appendChild(a);
|
|
|
|
}
|
|
|
|
|
|
|
|
var gOriginalInput;
|
|
|
|
function dragHover(event) {
|
|
|
|
event.stopPropagation();
|
|
|
|
event.preventDefault();
|
2016-04-11 11:54:26 -04:00
|
|
|
var input = document.getElementById("input");
|
2016-03-12 13:50:43 -05:00
|
|
|
if (event.type == "dragover") {
|
2016-04-11 11:54:26 -04:00
|
|
|
if (!input.classList.contains("drop")) {
|
|
|
|
input.classList.add("drop");
|
|
|
|
gOriginalInput = input.value;
|
|
|
|
input.value = "drop file to upload";
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
} else {
|
2016-04-11 11:54:26 -04:00
|
|
|
input.classList.remove("drop");
|
|
|
|
input.value = gOriginalInput;
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function fixImage(sourceData, maxWidth, maxHeight, callback) {
|
|
|
|
var result = sourceData;
|
|
|
|
var 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");
|
|
|
|
canvas.width = image.width * downScale;
|
|
|
|
canvas.height = image.height * downScale;
|
|
|
|
var context = canvas.getContext("2d");
|
|
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
image.width = canvas.width;
|
|
|
|
image.height = canvas.height;
|
|
|
|
context.drawImage(image, 0, 0, image.width, image.height);
|
|
|
|
result = canvas.toDataURL();
|
|
|
|
}
|
|
|
|
callback(result);
|
|
|
|
};
|
|
|
|
image.src = sourceData;
|
|
|
|
}
|
|
|
|
|
|
|
|
function sendImage(image) {
|
|
|
|
fixImage(image, 320, 240, function(result) {
|
|
|
|
send({image: result});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function fileDropRead(event) {
|
|
|
|
sendImage(event.target.result);
|
|
|
|
}
|
|
|
|
|
|
|
|
function fileDrop(event) {
|
|
|
|
dragHover(event);
|
|
|
|
|
|
|
|
var done = false;
|
|
|
|
if (!done) {
|
|
|
|
var files = event.target.files || event.dataTransfer.files;
|
|
|
|
for (var i = 0; i < files.length; i++) {
|
|
|
|
var file = files[i];
|
|
|
|
if (file.type.substring(0, "image/".length) == "image/") {
|
|
|
|
var reader = new FileReader();
|
|
|
|
reader.onloadend = fileDropRead;
|
|
|
|
reader.readAsDataURL(file);
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!done) {
|
|
|
|
var html = event.dataTransfer.getData("text/html");
|
|
|
|
var match = /<img.*src="([^"]+)"/.exec(html);
|
|
|
|
if (match) {
|
|
|
|
sendImage(match[1]);
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!done) {
|
|
|
|
var text = event.dataTransfer.getData("text/plain");
|
|
|
|
if (text) {
|
|
|
|
send(text);
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function enableDragDrop() {
|
|
|
|
var body = document.body;
|
|
|
|
body.addEventListener("dragover", dragHover);
|
|
|
|
body.addEventListener("dragleave", dragHover);
|
|
|
|
body.addEventListener("drop", fileDrop);
|
|
|
|
}
|
|
|
|
|
|
|
|
function hashChange() {
|
|
|
|
send({event: 'hashChange', hash: window.location.hash});
|
|
|
|
}
|
|
|
|
|
|
|
|
function focus() {
|
2016-05-07 07:07:54 -04:00
|
|
|
if (gSocket && gSocket.readyState == gSocket.CLOSED) {
|
|
|
|
connectSocket();
|
|
|
|
} else {
|
|
|
|
send({event: "focus"});
|
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function blur() {
|
2016-05-07 07:07:54 -04:00
|
|
|
if (gSocket && gSocket.readyState == gSocket.OPEN) {
|
|
|
|
send({event: "blur"});
|
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
|
2021-01-02 13:10:00 -05:00
|
|
|
function message(event) {
|
2016-09-17 16:53:03 -04:00
|
|
|
if (event.data && event.data.event == "resizeMe" && event.data.width && event.data.height) {
|
|
|
|
var iframe = document.getElementById("iframe_" + event.data.name);
|
|
|
|
iframe.setAttribute("width", event.data.width);
|
|
|
|
iframe.setAttribute("height", event.data.height);
|
2022-01-06 20:52:47 -05:00
|
|
|
} else if (event.data && event.data.action == "setHash") {
|
|
|
|
window.location.hash = event.data.hash;
|
2022-01-27 22:11:09 -05:00
|
|
|
} else if (event.data && event.data.action == 'storeBlob') {
|
2022-02-16 21:29:04 -05:00
|
|
|
fetch('/save', {
|
|
|
|
method: 'POST',
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/binary',
|
|
|
|
},
|
|
|
|
body: event.data.blob.buffer,
|
|
|
|
}).then(function(response) {
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error(response.status + ' ' + response.statusText);
|
2022-01-27 22:11:09 -05:00
|
|
|
}
|
2022-02-16 21:29:04 -05:00
|
|
|
return response.text();
|
|
|
|
}).then(function(text) {
|
|
|
|
var iframe = document.getElementById("document");
|
2022-04-14 19:47:06 -04:00
|
|
|
iframe.contentWindow.postMessage({'storeBlobComplete': {name: event.data.blob.name, path: text, type: event.data.blob.type, context: event.data.context}}, '*');
|
2022-01-27 22:11:09 -05:00
|
|
|
});
|
2016-09-17 16:53:03 -04:00
|
|
|
} else {
|
2021-01-02 13:10:00 -05:00
|
|
|
send({event: "message", message: event.data});
|
2016-05-01 09:24:37 -04:00
|
|
|
}
|
|
|
|
}
|
2016-04-10 20:09:21 -04:00
|
|
|
|
2021-01-02 13:10:00 -05:00
|
|
|
function reconnect(path) {
|
2017-01-16 10:24:44 -05:00
|
|
|
let oldSocket = gSocket;
|
|
|
|
gSocket = null
|
2021-01-19 21:01:14 -05:00
|
|
|
oldSocket.onopen = null;
|
|
|
|
oldSocket.onclose = null;
|
|
|
|
oldSocket.onmessage = null;
|
2017-01-16 10:24:44 -05:00
|
|
|
oldSocket.close();
|
2021-01-02 13:10:00 -05:00
|
|
|
connectSocket(path);
|
2017-01-16 10:24:44 -05:00
|
|
|
}
|
|
|
|
|
2021-01-02 13:10:00 -05:00
|
|
|
function connectSocket(path) {
|
2021-01-19 21:01:14 -05:00
|
|
|
if (!gSocket || gSocket.readyState != gSocket.OPEN) {
|
|
|
|
if (gSocket) {
|
|
|
|
gSocket.onopen = null;
|
|
|
|
gSocket.onclose = null;
|
|
|
|
gSocket.onmessage = null;
|
|
|
|
gSocket.close();
|
|
|
|
}
|
|
|
|
setStatusMessage("Connecting...", kStatusColor, false);
|
2016-05-07 07:07:54 -04:00
|
|
|
gSocket = new WebSocket(
|
|
|
|
(window.location.protocol == "https:" ? "wss://" : "ws://")
|
|
|
|
+ window.location.hostname
|
|
|
|
+ (window.location.port.length ? ":" + window.location.port : "")
|
2021-01-02 13:10:00 -05:00
|
|
|
+ "/app/socket");
|
2016-05-07 07:07:54 -04:00
|
|
|
gSocket.onopen = function() {
|
2017-05-22 15:38:49 -04:00
|
|
|
setStatusMessage("...Authenticating...", kStatusColor, true);
|
2016-05-07 07:07:54 -04:00
|
|
|
gSocket.send(JSON.stringify({
|
|
|
|
action: "hello",
|
2021-01-02 13:10:00 -05:00
|
|
|
path: path,
|
|
|
|
api: [
|
|
|
|
['setDocument', 'content'],
|
|
|
|
['postMessage', 'message'],
|
|
|
|
['error', 'error'],
|
2022-03-27 15:53:02 -04:00
|
|
|
['localStorageSet', 'key', 'value'],
|
|
|
|
['localStorageGet', 'key'],
|
2016-05-07 07:07:54 -04:00
|
|
|
],
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
gSocket.onmessage = function(event) {
|
|
|
|
receive(JSON.parse(event.data));
|
|
|
|
}
|
|
|
|
gSocket.onclose = function(event) {
|
2022-01-29 15:43:19 -05:00
|
|
|
const k_codes = {
|
|
|
|
1000: 'Normal closure',
|
|
|
|
1001: 'Going away',
|
|
|
|
1002: 'Protocol error',
|
|
|
|
1003: 'Unsupported data',
|
|
|
|
1005: 'No status received',
|
|
|
|
1006: 'Abnormal closure',
|
|
|
|
1007: 'Invalid frame payload data',
|
|
|
|
1008: 'Policy violation',
|
|
|
|
1009: 'Message too big',
|
|
|
|
1010: 'Missing extension',
|
|
|
|
1011: 'Internal error',
|
|
|
|
1012: 'Service restart',
|
|
|
|
1013: 'Try again later',
|
|
|
|
1014: 'Bad gateway',
|
|
|
|
1015: 'TLS handshake',
|
|
|
|
};
|
|
|
|
setStatusMessage("Connection closed: " + (k_codes[event.code] || event.code), kErrorColor);
|
2016-05-07 07:07:54 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-02 13:10:00 -05:00
|
|
|
function openFile(name) {
|
|
|
|
var newDoc = (name && gFiles[name]) ? gFiles[name].doc : new CodeMirror.Doc("", guessMode(name));
|
|
|
|
var oldDoc = gEditor.swapDoc(newDoc);
|
|
|
|
if (gFiles[gCurrentFile]) {
|
|
|
|
gFiles[gCurrentFile].doc = oldDoc;
|
|
|
|
}
|
|
|
|
gCurrentFile = name;
|
|
|
|
updateFiles();
|
|
|
|
gEditor.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
function onFileClicked(event) {
|
|
|
|
openFile(event.target.textContent);
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateFiles() {
|
|
|
|
var node = document.getElementById("files");
|
|
|
|
while (node.firstChild) {
|
|
|
|
node.removeChild(node.firstChild);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var file of Object.keys(gFiles).sort()) {
|
|
|
|
var li = document.createElement("li");
|
|
|
|
li.onclick = onFileClicked;
|
|
|
|
li.appendChild(document.createTextNode(file));
|
|
|
|
if (file == gCurrentFile) {
|
|
|
|
li.classList.add("current");
|
|
|
|
}
|
2021-01-12 21:40:46 -05:00
|
|
|
if (!gFiles[file].doc.isClean(gFiles[file].generation)) {
|
2021-01-12 21:15:09 -05:00
|
|
|
li.classList.add("dirty");
|
|
|
|
}
|
2021-01-02 13:10:00 -05:00
|
|
|
node.appendChild(li);
|
|
|
|
}
|
|
|
|
|
|
|
|
gEditor.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
function makeNewFile(name) {
|
|
|
|
gFiles[name] = {
|
2021-01-12 21:40:46 -05:00
|
|
|
doc: new CodeMirror.Doc("", guessMode(name)),
|
|
|
|
generation: -1,
|
2021-01-02 13:10:00 -05:00
|
|
|
};
|
|
|
|
openFile(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
function newFile() {
|
|
|
|
var name = prompt("Name of new file:", "file.js");
|
|
|
|
if (name && !gFiles[name]) {
|
|
|
|
makeNewFile(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeFile() {
|
|
|
|
if (confirm("Remove " + gCurrentFile + "?")) {
|
|
|
|
delete gFiles[gCurrentFile];
|
|
|
|
openFile(Object.keys(gFiles)[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-11 11:54:26 -04:00
|
|
|
window.addEventListener("load", function() {
|
2016-03-12 13:50:43 -05:00
|
|
|
window.addEventListener("hashchange", hashChange);
|
|
|
|
window.addEventListener("focus", focus);
|
|
|
|
window.addEventListener("blur", blur);
|
2021-01-02 13:10:00 -05:00
|
|
|
window.addEventListener("message", message, false);
|
2016-05-07 07:07:54 -04:00
|
|
|
window.addEventListener("online", connectSocket);
|
2021-01-02 13:10:00 -05:00
|
|
|
document.getElementById("name").value = window.location.pathname;
|
2022-02-03 18:57:47 -05:00
|
|
|
for (let tag of document.getElementsByTagName('a')) {
|
|
|
|
if (tag.accessKey) {
|
|
|
|
tag.classList.add('tooltip_parent');
|
|
|
|
var tooltip = document.createElement('div');
|
|
|
|
tooltip.classList.add('tooltip');
|
|
|
|
if (tag.dataset.tip) {
|
|
|
|
var description = document.createElement('div');
|
|
|
|
description.innerText = tag.dataset.tip;
|
|
|
|
tooltip.appendChild(description);
|
|
|
|
}
|
2022-02-03 21:32:00 -05:00
|
|
|
var parts = tag.accessKeyLabel ? tag.accessKeyLabel.split('+') : [];
|
2022-02-03 18:57:47 -05:00
|
|
|
for (var i = 0; i < parts.length; i++)
|
|
|
|
{
|
|
|
|
var key = parts[i];
|
|
|
|
var kbd = document.createElement('kbd');
|
|
|
|
kbd.innerText = key;
|
|
|
|
tooltip.appendChild(kbd);
|
|
|
|
if (i < parts.length - 1) {
|
|
|
|
tooltip.appendChild(document.createTextNode('+'));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tag.appendChild(tooltip);
|
|
|
|
}
|
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
enableDragDrop();
|
2021-01-02 13:10:00 -05:00
|
|
|
connectSocket(window.location.pathname);
|
2022-03-07 16:06:20 -05:00
|
|
|
|
|
|
|
if (window.localStorage.getItem('editing') == '1') {
|
|
|
|
edit();
|
|
|
|
} else {
|
|
|
|
closeEditor();
|
|
|
|
}
|
|
|
|
if (window.localStorage.getItem('files') == '1') {
|
|
|
|
showFiles();
|
|
|
|
} else {
|
|
|
|
hideFiles();
|
|
|
|
}
|
|
|
|
if (window.localStorage.getItem('stats') == '1') {
|
|
|
|
stats();
|
|
|
|
} else {
|
|
|
|
closeStats();
|
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
});
|