blob: 714e38384feabbd86f08f6d5a8af069d139a3d9d [file] [log] [blame]
import './ace-modes.js';
import './style.css';
import {Editors} from './editors';
import {Elm} from './elm/Main.elm';
import {Evaluator} from './evaluator';
// A message sent from Elm (see elm/Ports.elm).
interface ElmMessage {
command: string;
payload: any;
}
// Gets the user's light/dark preference (e.g. macOS Catalina Dark Mode).
function preferredColorScheme(): string {
if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
function save(key: string, value: object) {
localStorage.setItem(key, JSON.stringify(value));
}
function load(key: string): object | undefined {
const item = localStorage.getItem(key);
if (item == undefined) {
return undefined;
}
return JSON.parse(item);
}
function main() {
const app = Elm.Main.init({
node: document.getElementById('Elm'),
flags: {
model: load('model'),
preferredScheme: preferredColorScheme(),
},
});
const editors = Editors.fromJSON(load('editors') as any);
const evaluator = new Evaluator(editors, () => {
save('editors', editors.toJSON());
});
evaluator.onDeloymentUpdated(deployment => {
app.ports.fromJS.send({
command: 'deploymentUpdated',
payload: deployment,
});
});
evaluator.onSyntaxDetected(syntax => {
app.ports.fromJS.send({
command: 'syntaxDetected',
payload: syntax,
});
});
app.ports.toJS.subscribe(({command, payload}: ElmMessage) => {
switch (command) {
case 'persistModel': {
save('model', payload);
break;
}
case 'clearDataAndReload': {
// Stop the evaluator first, otherwise it will save to local storage
// before the browser refreshes.
evaluator.stop();
localStorage.clear();
window.location.reload();
break;
}
case 'applySettings': {
editors.applySettings(payload);
evaluator.setRefreshDelay(payload.refreshDelay);
const classes = document.getElementsByTagName('body')[0].classList;
if (payload.scheme === 'dark') {
classes.add('dark');
} else {
classes.remove('dark');
}
break;
}
case 'setMainTabEnabled': {
const elements = document
.getElementById('Main')
// Exclude .navbar-item since they always have tabindex -1 (we provide
// the keyboard shortcuts Ctrl-[ and Ctrl-] instead).
?.querySelectorAll('input,textarea,select,button:not(.navbar-item)');
const tabIndex = payload ? 0 : -1;
elements?.forEach(element => {
(element as HTMLElement).tabIndex = tabIndex;
});
break;
}
case 'updateEditors': {
editors.showSessions(payload.input, payload.output);
evaluator.setActive(payload.input, payload.output, payload.options);
evaluator.run();
break;
}
case 'resizeEditors': {
// Use requestAnimationFrame to give Elm time to render. The newly
// rendered DOM might affect the size of the container to which the Ace
// editors adjust when we call resize() on them.
requestAnimationFrame(() => {
editors.resize();
});
break;
}
case 'convertToNewSyntax': {
evaluator.convertToNewSyntax();
break;
}
}
});
evaluator.start();
}
// Load ace/mode/json first. Running main() eventually results in setting an
// editor mode to ace/mode/fidl, prompting Ace to execute the ace/mode/fidl
// definition function in ace-modes.js. This file imports some resources that
// are not exposed as separate files by the CDN, like ace/mode/behaviour/cstyle.
// By first loading ace/mode/json, these common resources get populated in Ace's
// internal map, so it won't attempt and fail to load them from the CDN.
(ace as any).config.loadModule('ace/mode/json', () => {
main();
});