| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import './ace-modes.js'; |
| import './style.css'; |
| |
| import {Editors} from './editors'; |
| import {Elm} from './elm/Main.elm'; |
| import {Evaluator} from './evaluator'; |
| import {LinkSharing} from './share'; |
| import {waitForElement} from './util'; |
| |
| // 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 savedModel = load('model') as any; |
| const savedEditors = load('editors') as any; |
| const linkSharing = new LinkSharing(); |
| linkSharing.fixupModel(savedModel); |
| const app = Elm.Main.init({ |
| node: document.getElementById('Elm'), |
| flags: { |
| model: savedModel, |
| preferredScheme: preferredColorScheme(), |
| }, |
| }); |
| const editors = Editors.fromJSON(savedEditors); |
| const evaluator = new Evaluator(editors, () => { |
| save('editors', editors.toJSON()); |
| }); |
| linkSharing.init(editors, evaluator); |
| |
| evaluator.onDeloymentUpdated(deployment => { |
| app.ports.fromJS.send({ |
| command: 'deploymentUpdated', |
| payload: deployment, |
| }); |
| }); |
| |
| 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'); |
| } |
| const extraStyle = document.getElementById('ExtraStyle'); |
| extraStyle!.innerHTML = ` |
| .${payload.themeClass} .ace_marker-layer .ace_selection { |
| background: ${payload.selectionBackground}; |
| } |
| `; |
| 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 'updateShareLink': { |
| app.ports.fromJS.send({ |
| command: 'shareLinkUpdated', |
| payload: linkSharing.createShareLink(), |
| }); |
| break; |
| } |
| case 'selectAndCopyShareLink': { |
| waitForElement('ShareLink').then(element => { |
| const input = element as HTMLInputElement; |
| input.select(); |
| // Not all browsers will allow this, so only tell the user we copied |
| // the URL if in fact we were able to. |
| if (document.execCommand('copy')) { |
| app.ports.fromJS.send({ |
| command: 'shareLinkCopied', |
| payload: null, |
| }); |
| } |
| }); |
| 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(); |
| }); |