core: Minor cleanup and style.

This commit is contained in:
2025-10-09 12:45:38 -04:00
parent 6ab3fd168b
commit 25dbac804c

View File

@@ -9,18 +9,17 @@
import {LitElement, html, css, svg} from '/lit/lit-all.min.js'; import {LitElement, html, css, svg} from '/lit/lit-all.min.js';
let cm6; let cm6;
let gSocket;
let gCurrentFile; let g_socket;
let gFiles = {}; let g_current_file;
let gApp = {files: {}, emoji: '📦'}; let g_files = {};
let gEditor; let g_app = {files: {}, emoji: '📦'};
let gOriginalInput; let g_editor;
let gUnloading; let g_unloading;
let kErrorColor = '#dc322f'; let k_color_error = '#dc322f';
let kDisconnectColor = '#f00'; let k_color_disconnect = '#f00';
let kStatusColor = '#fff'; let k_color_status = '#fff';
/** \endcond */ /** \endcond */
/** Functions that server-side app code can call through the app object. */ /** Functions that server-side app code can call through the app object. */
@@ -410,7 +409,7 @@ class TfNavigationElement extends LitElement {
<link type="text/css" rel="stylesheet" href="/static/w3.css" /> <link type="text/css" rel="stylesheet" href="/static/w3.css" />
<div <div
class="w3-bar-item" class="w3-bar-item"
style="color: ${this.status.color ?? kStatusColor}" style="color: ${this.status.color ?? k_color_status}"
> >
${this.status.message} ${this.status.message}
</div> </div>
@@ -429,7 +428,7 @@ class TfNavigationElement extends LitElement {
<div class="w3-model w3-animate-top" style="position: absolute; left: 50%; transform: translate(-50%); z-index: 1"> <div class="w3-model w3-animate-top" style="position: absolute; left: 50%; transform: translate(-50%); z-index: 1">
<dijv class="w3-modal-content w3-card-4" style="display: block; padding: 1em"> <dijv class="w3-modal-content w3-card-4" style="display: block; padding: 1em">
<span id="close_error" @click=${self.clear_error} class="w3-button w3-display-topright">&times;</span> <span id="close_error" @click=${self.clear_error} class="w3-button w3-display-topright">&times;</span>
<div style="color: ${this.status.color ?? kErrorColor}"><b>ERROR:</b><p id="error" style="white-space: pre">${this.status.message}</p></div> <div style="color: ${this.status.color ?? k_color_error}"><b>ERROR:</b><p id="error" style="white-space: pre">${this.status.message}</p></div>
</div> </div>
</div> </div>
` `
@@ -521,7 +520,7 @@ class TfFilesElement extends LitElement {
for (let file of event.dataTransfer.files) { for (let file of event.dataTransfer.files) {
let buffer = await file.arrayBuffer(); let buffer = await file.arrayBuffer();
let text = new TextDecoder('latin1').decode(buffer); let text = new TextDecoder('latin1').decode(buffer);
gFiles[file.name] = { g_files[file.name] = {
doc: cm6.EditorState.create({ doc: cm6.EditorState.create({
doc: text, doc: text,
extensions: cm6.extensions, extensions: cm6.extensions,
@@ -529,9 +528,9 @@ class TfFilesElement extends LitElement {
buffer: buffer, buffer: buffer,
isNew: true, isNew: true,
}; };
gCurrentFile = file.name; g_current_file = file.name;
} }
openFile(gCurrentFile); openFile(g_current_file);
updateFiles(); updateFiles();
} }
@@ -895,11 +894,11 @@ async function edit() {
: 'flex'; : 'flex';
try { try {
if (!gEditor) { if (!g_editor) {
cm6 = await import('/codemirror/cm6.js'); cm6 = await import('/codemirror/cm6.js');
gEditor = cm6.TildeFriendsEditorView(document.getElementById('editor')); g_editor = cm6.TildeFriendsEditorView(document.getElementById('editor'));
} }
gEditor.onDocChange = updateFiles; g_editor.onDocChange = updateFiles;
await load(); await load();
} catch (error) { } catch (error) {
alert(`${error.message}\n\n${error.stack}`); alert(`${error.message}\n\n${error.stack}`);
@@ -932,13 +931,13 @@ function loadFile(name, id) {
return response.text(); return response.text();
}) })
.then(function (text) { .then(function (text) {
gFiles[name].doc = cm6.EditorState.create({ g_files[name].doc = cm6.EditorState.create({
doc: text, doc: text,
extensions: cm6.extensions, extensions: cm6.extensions,
}); });
gFiles[name].original = gFiles[name].doc.doc.toString(); g_files[name].original = g_files[name].doc.doc.toString();
if (!Object.values(gFiles).some((x) => !x.doc)) { if (!Object.values(g_files).some((x) => !x.doc)) {
openFile(Object.keys(gFiles).sort()[0]); openFile(Object.keys(g_files).sort()[0]);
} }
}); });
} }
@@ -956,31 +955,31 @@ async function load(path) {
} else if (response.status != 404) { } else if (response.status != 404) {
throw new Error(response.status + ' ' + response.statusText); throw new Error(response.status + ' ' + response.statusText);
} }
gFiles = {}; g_files = {};
let isApp = false; let isApp = false;
let promises = []; let promises = [];
if (json && json['type'] == 'tildefriends-app') { if (json && json['type'] == 'tildefriends-app') {
isApp = true; isApp = true;
Object.keys(json['files']).forEach(function (name) { Object.keys(json['files']).forEach(function (name) {
gFiles[name] = {}; g_files[name] = {};
promises.push(loadFile(name, json['files'][name])); promises.push(loadFile(name, json['files'][name]));
}); });
if (Object.keys(json['files']).length == 0) { if (Object.keys(json['files']).length == 0) {
document.getElementById('editPane').style.display = 'flex'; document.getElementById('editPane').style.display = 'flex';
} }
gApp = json; g_app = json;
gApp.emoji = gApp.emoji || '📦'; g_app.emoji = g_app.emoji || '📦';
document.getElementById('icon').innerHTML = gApp.emoji; document.getElementById('icon').innerHTML = g_app.emoji;
} }
if (!isApp) { if (!isApp) {
document.getElementById('editPane').style.display = 'flex'; document.getElementById('editPane').style.display = 'flex';
let text = '// New script.\n'; let text = '// New script.\n';
gCurrentFile = 'app.js'; g_current_file = 'app.js';
gFiles[gCurrentFile] = { g_files[g_current_file] = {
doc: cm6.EditorState.create({doc: text, extensions: cm6.extensions}), doc: cm6.EditorState.create({doc: text, extensions: cm6.extensions}),
}; };
openFile(gCurrentFile); openFile(g_current_file);
} }
return Promise.all(promises); return Promise.all(promises);
} }
@@ -1001,13 +1000,14 @@ function closeEditor() {
*/ */
function save(save_to) { function save(save_to) {
document.getElementById('save').disabled = true; document.getElementById('save').disabled = true;
if (gCurrentFile) { if (g_current_file) {
gFiles[gCurrentFile].doc = gEditor.state; g_files[g_current_file].doc = g_editor.state;
if ( if (
!gFiles[gCurrentFile].isNew && !g_files[g_current_file].isNew &&
!gFiles[gCurrentFile].doc.doc.toString() == gFiles[gCurrentFile].original !g_files[g_current_file].doc.doc.toString() ==
g_files[g_current_file].original
) { ) {
delete gFiles[gCurrentFile].buffer; delete g_files[g_current_file].buffer;
} }
} }
@@ -1022,8 +1022,8 @@ function save(save_to) {
} }
let promises = []; let promises = [];
for (let name of Object.keys(gFiles)) { for (let name of Object.keys(g_files)) {
let file = gFiles[name]; let file = g_files[name];
if (!file.isNew && file.doc.doc.toString() == file.original) { if (!file.isNew && file.doc.doc.toString() == file.original) {
continue; continue;
} }
@@ -1065,14 +1065,14 @@ function save(save_to) {
let app = { let app = {
type: 'tildefriends-app', type: 'tildefriends-app',
files: Object.fromEntries( files: Object.fromEntries(
Object.keys(gFiles).map((x) => [x, gFiles[x].id || gApp.files[x]]) Object.keys(g_files).map((x) => [x, g_files[x].id || g_app.files[x]])
), ),
emoji: gApp.emoji || '📦', emoji: g_app.emoji || '📦',
}; };
Object.values(gFiles).forEach(function (file) { Object.values(g_files).forEach(function (file) {
delete file.id; delete file.id;
}); });
gApp = JSON.parse(JSON.stringify(app)); g_app = JSON.parse(JSON.stringify(app));
return fetch(save_path + 'save', { return fetch(save_path + 'save', {
method: 'POST', method: 'POST',
@@ -1087,7 +1087,7 @@ function save(save_to) {
if (save_path != window.location.pathname) { if (save_path != window.location.pathname) {
alert('Saved to ' + save_path + '.'); alert('Saved to ' + save_path + '.');
} else if (!gFiles['app.js']) { } else if (!g_files['app.js']) {
window.location.reload(); window.location.reload();
} else { } else {
reconnect(save_path); reconnect(save_path);
@@ -1099,7 +1099,7 @@ function save(save_to) {
}) })
.finally(function () { .finally(function () {
document.getElementById('save').disabled = false; document.getElementById('save').disabled = false;
Object.values(gFiles).forEach(function (file) { Object.values(g_files).forEach(function (file) {
file.original = file.doc.doc.toString(); file.original = file.doc.doc.toString();
}); });
updateFiles(); updateFiles();
@@ -1112,8 +1112,8 @@ function save(save_to) {
function changeIcon() { function changeIcon() {
let value = prompt('Enter a new app icon emoji:'); let value = prompt('Enter a new app icon emoji:');
if (value !== undefined) { if (value !== undefined) {
gApp.emoji = value || '📦'; g_app.emoji = value || '📦';
document.getElementById('icon').innerHTML = gApp.emoji; document.getElementById('icon').innerHTML = g_app.emoji;
} }
} }
@@ -1190,9 +1190,12 @@ function api_postMessage(message) {
function api_error(error) { function api_error(error) {
if (error) { if (error) {
if (typeof error == 'string') { if (typeof error == 'string') {
setStatusMessage('⚠️ ' + error, kErrorColor); setStatusMessage('⚠️ ' + error, k_color_error);
} else { } else {
setStatusMessage('⚠️ ' + error.message + '\n' + error.stack, kErrorColor); setStatusMessage(
'⚠️ ' + error.message + '\n' + error.stack,
k_color_error
);
} }
} }
console.log('error', error); console.log('error', error);
@@ -1309,7 +1312,7 @@ function api_setHash(hash) {
*/ */
function _receive_websocket_message(message) { function _receive_websocket_message(message) {
if (message && message.action == 'session') { if (message && message.action == 'session') {
setStatusMessage('🟢 Executing...', kStatusColor); setStatusMessage('🟢 Executing...', k_color_status);
let navigation = document.getElementsByTagName('tf-navigation')[0]; let navigation = document.getElementsByTagName('tf-navigation')[0];
navigation.credentials = message.credentials; navigation.credentials = message.credentials;
navigation.identities = message.identities; navigation.identities = message.identities;
@@ -1409,7 +1412,7 @@ function setStatusMessage(message, color) {
document.getElementsByTagName('tf-navigation')[0].status = { document.getElementsByTagName('tf-navigation')[0].status = {
message: message, message: message,
color: color, color: color,
is_error: color == kErrorColor, is_error: color == k_color_error,
}; };
} }
@@ -1419,11 +1422,11 @@ function setStatusMessage(message, color) {
*/ */
function send(value) { function send(value) {
try { try {
if (gSocket && gSocket.readyState == gSocket.OPEN) { if (g_socket && g_socket.readyState == g_socket.OPEN) {
gSocket.send(JSON.stringify(value)); g_socket.send(JSON.stringify(value));
} }
} catch (error) { } catch (error) {
setStatusMessage('🤷 Send failed: ' + error.toString(), kErrorColor); setStatusMessage('🤷 Send failed: ' + error.toString(), k_color_error);
} }
} }
@@ -1438,7 +1441,7 @@ function hashChange() {
* Make sure the app is connected on window focus, and notify the app. * Make sure the app is connected on window focus, and notify the app.
*/ */
function focus() { function focus() {
if (gSocket && gSocket.readyState == gSocket.CLOSED) { if (g_socket && g_socket.readyState == g_socket.CLOSED) {
connectSocket(); connectSocket();
} else { } else {
send({event: 'focus'}); send({event: 'focus'});
@@ -1449,10 +1452,8 @@ function focus() {
* Notify the app of lost focus. * Notify the app of lost focus.
*/ */
function blur() { function blur() {
if (gSocket && gSocket.readyState == gSocket.OPEN) {
send({event: 'blur'}); send({event: 'blur'});
} }
}
/** /**
* Handle a message. * Handle a message.
@@ -1508,8 +1509,8 @@ function message(event) {
* @param path The path to which the WebSocket should be connected. * @param path The path to which the WebSocket should be connected.
*/ */
function reconnect(path) { function reconnect(path) {
let oldSocket = gSocket; let oldSocket = g_socket;
gSocket = null; g_socket = null;
if (oldSocket) { if (oldSocket) {
oldSocket.onopen = null; oldSocket.onopen = null;
oldSocket.onclose = null; oldSocket.onclose = null;
@@ -1524,24 +1525,24 @@ function reconnect(path) {
* @param path The path to which to connect. * @param path The path to which to connect.
*/ */
function connectSocket(path) { function connectSocket(path) {
if (!gSocket || gSocket.readyState != gSocket.OPEN) { if (!g_socket || g_socket.readyState != g_socket.OPEN) {
if (gSocket) { if (g_socket) {
gSocket.onopen = null; g_socket.onopen = null;
gSocket.onclose = null; g_socket.onclose = null;
gSocket.onmessage = null; g_socket.onmessage = null;
gSocket.close(); g_socket.close();
} }
setStatusMessage('⚪ Connecting...', kStatusColor); setStatusMessage('⚪ Connecting...', k_color_status);
gSocket = new WebSocket( g_socket = new WebSocket(
(window.location.protocol == 'https:' ? 'wss://' : 'ws://') + (window.location.protocol == 'https:' ? 'wss://' : 'ws://') +
window.location.hostname + window.location.hostname +
(window.location.port.length ? ':' + window.location.port : '') + (window.location.port.length ? ':' + window.location.port : '') +
'/app/socket' '/app/socket'
); );
gSocket.onopen = function () { g_socket.onopen = function () {
setStatusMessage('🟡 Authenticating...', kStatusColor); setStatusMessage('🟡 Authenticating...', k_color_status);
let connect_path = path ?? window.location.pathname; let connect_path = path ?? window.location.pathname;
gSocket.send( g_socket.send(
JSON.stringify({ JSON.stringify({
action: 'hello', action: 'hello',
path: connect_path, path: connect_path,
@@ -1553,12 +1554,12 @@ function connectSocket(path) {
}) })
); );
}; };
gSocket.onmessage = function (event) { g_socket.onmessage = function (event) {
_receive_websocket_message(JSON.parse(event.data)); _receive_websocket_message(JSON.parse(event.data));
}; };
gSocket.onclose = function (event) { g_socket.onclose = function (event) {
if (gUnloading) { if (g_unloading) {
setStatusMessage('⚪ Closing...', kStatusColor); setStatusMessage('⚪ Closing...', k_color_status);
} else { } else {
const k_codes = { const k_codes = {
1000: 'Normal closure', 1000: 'Normal closure',
@@ -1579,7 +1580,7 @@ function connectSocket(path) {
}; };
setStatusMessage( setStatusMessage(
'🔴 Closed: ' + (k_codes[event.code] || event.code), '🔴 Closed: ' + (k_codes[event.code] || event.code),
kDisconnectColor k_color_disconnect
); );
} }
}; };
@@ -1592,24 +1593,24 @@ function connectSocket(path) {
*/ */
function openFile(name) { function openFile(name) {
let newDoc = let newDoc =
name && gFiles[name] name && g_files[name]
? gFiles[name].doc ? g_files[name].doc
: cm6.EditorState.create({doc: '', extensions: cm6.extensions}); : cm6.EditorState.create({doc: '', extensions: cm6.extensions});
let oldDoc = gEditor.state; let oldDoc = g_editor.state;
gEditor.setState(newDoc); g_editor.setState(newDoc);
if (gFiles[gCurrentFile]) { if (g_files[g_current_file]) {
gFiles[gCurrentFile].doc = oldDoc; g_files[g_current_file].doc = oldDoc;
if ( if (
!gFiles[gCurrentFile].isNew && !g_files[g_current_file].isNew &&
gFiles[gCurrentFile].doc.doc.toString() == oldDoc.doc.toString() g_files[g_current_file].doc.doc.toString() == oldDoc.doc.toString()
) { ) {
delete gFiles[gCurrentFile].buffer; delete g_files[g_current_file].buffer;
} }
} }
gCurrentFile = name; g_current_file = name;
updateFiles(); updateFiles();
gEditor.focus(); g_editor.focus();
} }
/** /**
@@ -1619,19 +1620,19 @@ function updateFiles() {
let files = document.getElementsByTagName('tf-files-pane')[0]; let files = document.getElementsByTagName('tf-files-pane')[0];
if (files) { if (files) {
files.files = Object.fromEntries( files.files = Object.fromEntries(
Object.keys(gFiles).map((file) => [ Object.keys(g_files).map((file) => [
file, file,
{ {
clean: clean:
(file == gCurrentFile (file == g_current_file
? gEditor.state.doc.toString() ? g_editor.state.doc.toString()
: gFiles[file].doc.doc.toString()) == gFiles[file].original, : g_files[file].doc.doc.toString()) == g_files[file].original,
}, },
]) ])
); );
files.current = gCurrentFile; files.current = g_current_file;
} }
gEditor.focus(); g_editor.focus();
} }
/** /**
@@ -1639,7 +1640,7 @@ function updateFiles() {
* @param name The file's name. * @param name The file's name.
*/ */
function makeNewFile(name) { function makeNewFile(name) {
gFiles[name] = { g_files[name] = {
doc: cm6.EditorState.create({extensions: cm6.extensions}), doc: cm6.EditorState.create({extensions: cm6.extensions}),
}; };
openFile(name); openFile(name);
@@ -1650,7 +1651,7 @@ function makeNewFile(name) {
*/ */
function newFile() { function newFile() {
let name = prompt('Name of new file:', 'file.js'); let name = prompt('Name of new file:', 'file.js');
if (name && !gFiles[name]) { if (name && !g_files[name]) {
makeNewFile(name); makeNewFile(name);
} }
} }
@@ -1659,9 +1660,9 @@ function newFile() {
* Prompt to remove a file. * Prompt to remove a file.
*/ */
function removeFile() { function removeFile() {
if (confirm('Remove ' + gCurrentFile + '?')) { if (confirm('Remove ' + g_current_file + '?')) {
delete gFiles[gCurrentFile]; delete g_files[g_current_file];
openFile(Object.keys(gFiles)[0]); openFile(Object.keys(g_files)[0]);
} }
} }
@@ -1677,13 +1678,13 @@ async function appExport() {
`${name}.json`, `${name}.json`,
JSON.stringify({ JSON.stringify({
type: 'tildefriends-app', type: 'tildefriends-app',
emoji: gApp.emoji || '📦', emoji: g_app.emoji || '📦',
}) })
); );
for (let file of Object.keys(gFiles)) { for (let file of Object.keys(g_files)) {
zip.file( zip.file(
`${name}/${file}`, `${name}/${file}`,
gFiles[file].buffer ?? gFiles[file].doc.doc.toString() g_files[file].buffer ?? g_files[file].doc.doc.toString()
); );
} }
let content = await zip.generateAsync({ let content = await zip.generateAsync({
@@ -1802,9 +1803,9 @@ async function sourcePretty() {
let babel = (await import('/prettier/babel.mjs')).default; let babel = (await import('/prettier/babel.mjs')).default;
let estree = (await import('/prettier/estree.mjs')).default; let estree = (await import('/prettier/estree.mjs')).default;
let prettier_html = (await import('/prettier/html.mjs')).default; let prettier_html = (await import('/prettier/html.mjs')).default;
let source = gEditor.state.doc.toString(); let source = g_editor.state.doc.toString();
let formatted = await prettier.format(source, { let formatted = await prettier.format(source, {
parser: gCurrentFile?.toLowerCase()?.endsWith('.html') ? 'html' : 'babel', parser: g_current_file?.toLowerCase()?.endsWith('.html') ? 'html' : 'babel',
plugins: [babel, estree, prettier_html], plugins: [babel, estree, prettier_html],
trailingComma: 'es5', trailingComma: 'es5',
useTabs: true, useTabs: true,
@@ -1813,10 +1814,10 @@ async function sourcePretty() {
bracketSpacing: false, bracketSpacing: false,
}); });
if (source !== formatted) { if (source !== formatted) {
gEditor.dispatch({ g_editor.dispatch({
changes: { changes: {
from: 0, from: 0,
to: gEditor.state.doc.length, to: g_editor.state.doc.length,
insert: formatted, insert: formatted,
}, },
}); });
@@ -1861,7 +1862,7 @@ window.addEventListener('load', function () {
window.addEventListener('message', message, false); window.addEventListener('message', message, false);
window.addEventListener('online', connectSocket); window.addEventListener('online', connectSocket);
window.addEventListener('beforeunload', function () { window.addEventListener('beforeunload', function () {
gUnloading = true; g_unloading = true;
}); });
document.getElementById('name').value = window.location.pathname; document.getElementById('name').value = window.location.pathname;
document document