mirror of
https://github.com/Asraelite/littlebigcomputer.git
synced 2025-07-17 16:16:51 +00:00
Init commit
This commit is contained in:
commit
c45ad79440
48 changed files with 6786 additions and 0 deletions
543
assembler/main.ts
Normal file
543
assembler/main.ts
Normal file
|
@ -0,0 +1,543 @@
|
|||
import * as bitUtils from './bit_utils.js';
|
||||
|
||||
import targetV8 from './targets/v8.js';
|
||||
import targetParva_0_1 from './targets/parva_0_1.js';
|
||||
import targetLodestar from './targets/lodestar.js';
|
||||
import * as targetTest from './targets/test/test.js';
|
||||
import { ArchName, ArchSpecification, AssemblyInput, AssemblyOutput, InstructionOutputLine } from './assembly.js';
|
||||
import { toNote } from './util.js';
|
||||
|
||||
const CONFIG_VERSION: string = '1';
|
||||
const EMULATOR_SPEED = 60;
|
||||
|
||||
window.addEventListener('load', init);
|
||||
|
||||
let assemblyInput: HTMLTextAreaElement;
|
||||
let machineCodeOutput: HTMLPreElement;
|
||||
let assemblyStatusOutput: HTMLSpanElement;
|
||||
|
||||
let emulatorOutput: HTMLDivElement;
|
||||
let emulatorResetButton: HTMLButtonElement;
|
||||
let emulatorStepButton: HTMLButtonElement;
|
||||
let emulatorStep10Button: HTMLButtonElement;
|
||||
let emulatorRunButton: HTMLButtonElement;
|
||||
|
||||
let controlSendButton: HTMLButtonElement;
|
||||
let controlSendCancelButton: HTMLButtonElement;
|
||||
let controlSendStatus: HTMLPreElement;
|
||||
let controlSendStartInput: HTMLInputElement;;
|
||||
|
||||
let textDecorator: any;
|
||||
let showDocs: boolean = false;
|
||||
let emulatorRunning: boolean = false;
|
||||
let emulatorBreakpoints: Set<number> = new Set();
|
||||
|
||||
let targetArch: ArchSpecification;
|
||||
let assemblyOutput: AssemblyOutput;
|
||||
|
||||
let lastInputReceivedTime = 0;
|
||||
|
||||
let configuration: Configuration;
|
||||
|
||||
type OutputFormat = 'binary' | 'hex' | 'note' | 'decimal' | 'none';
|
||||
|
||||
type ConfigurationSpecification = {
|
||||
version: typeof CONFIG_VERSION;
|
||||
|
||||
targetArch: ArchName;
|
||||
assemblyOutputFormat: OutputFormat;
|
||||
machineCodeOutputFormat: OutputFormat;
|
||||
machineCodeShowLabels: boolean;
|
||||
syntaxHighlighting: string;
|
||||
inlineSourceFormat: string;
|
||||
rawOutput: boolean;
|
||||
sendDelay: number;
|
||||
};
|
||||
|
||||
class Configuration {
|
||||
machineCodeOutputFormat: OutputFormat;
|
||||
addressOutputFormat: OutputFormat;
|
||||
machineCodeShowLabels: boolean;
|
||||
targetArch: ArchName;
|
||||
syntaxHighlighting: string;
|
||||
inlineSourceFormat: string;
|
||||
rawOutput: boolean;
|
||||
sendDelay: number;
|
||||
|
||||
private formElement: HTMLFormElement;
|
||||
private outputFormatSelect: HTMLSelectElement;
|
||||
private addressFormatSelect: HTMLSelectElement;
|
||||
private labelsCheckbox: HTMLInputElement;
|
||||
private inlineSourceSelect: HTMLSelectElement;
|
||||
private targetArchSelect: HTMLSelectElement;
|
||||
private targetArchDocsButton: HTMLButtonElement;
|
||||
private syntaxHighlightingSelect: HTMLSelectElement;
|
||||
private rawOutputCheckbox: HTMLInputElement;
|
||||
private sendDelayInput: HTMLInputElement;
|
||||
|
||||
constructor() {
|
||||
this.formElement = document.querySelector('#controls form') as HTMLFormElement;
|
||||
this.outputFormatSelect = document.getElementById('config-output-format-select') as HTMLSelectElement;
|
||||
this.addressFormatSelect = document.getElementById('config-address-format-select') as HTMLSelectElement;
|
||||
this.labelsCheckbox = document.getElementById('config-labels-input') as HTMLInputElement;
|
||||
this.inlineSourceSelect = document.getElementById('config-source-select') as HTMLSelectElement;
|
||||
this.targetArchSelect = document.getElementById('config-target-arch-select') as HTMLSelectElement;
|
||||
this.targetArchDocsButton = document.getElementById('config-target-arch-docs-button') as HTMLButtonElement;
|
||||
this.syntaxHighlightingSelect = document.getElementById('config-syntax-highlighting-select') as HTMLSelectElement;
|
||||
this.rawOutputCheckbox = document.getElementById('config-raw-output-input') as HTMLInputElement;
|
||||
this.sendDelayInput = document.getElementById('control-send-speed') as HTMLInputElement;
|
||||
|
||||
this.formElement.addEventListener('change', () => {
|
||||
this.update();
|
||||
updateOutput();
|
||||
});
|
||||
|
||||
this.sendDelayInput.addEventListener('change', () => this.update());
|
||||
|
||||
this.targetArchDocsButton.addEventListener('click', () => {
|
||||
showDocs = !showDocs;
|
||||
this.targetArchDocsButton.innerText = showDocs ? 'Show assembly' : 'Show docs';
|
||||
updateOutput();
|
||||
});
|
||||
|
||||
// TODO
|
||||
// Add change event listeners
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.targetArch !== this.targetArchSelect.value) {
|
||||
updateTargetArch(this.targetArchSelect.value as ArchName);
|
||||
}
|
||||
this.targetArch = this.targetArchSelect.value as ArchName;
|
||||
this.syntaxHighlighting = this.syntaxHighlightingSelect.value;
|
||||
this.machineCodeOutputFormat = this.outputFormatSelect.value as OutputFormat;
|
||||
this.addressOutputFormat = this.addressFormatSelect.value as OutputFormat;
|
||||
this.machineCodeShowLabels = this.labelsCheckbox.checked;
|
||||
this.inlineSourceFormat = this.inlineSourceSelect.value;
|
||||
this.rawOutput = this.rawOutputCheckbox.checked;
|
||||
this.sendDelay = parseInt(this.sendDelayInput.value);
|
||||
|
||||
document.getElementById('assembly').className = `theme-${this.syntaxHighlighting}`;
|
||||
|
||||
this.saveToLocalStorage();
|
||||
}
|
||||
|
||||
static fromLocalStorage() {
|
||||
const configuration = new Configuration();
|
||||
const configurationStorageItem = localStorage.getItem('configuration');
|
||||
|
||||
if (configurationStorageItem !== null) {
|
||||
const parsed: ConfigurationSpecification = JSON.parse(configurationStorageItem);
|
||||
|
||||
if (parsed.version !== CONFIG_VERSION) {
|
||||
console.warn(`Saved configuration version mismatch: found ${parsed.version}, expected ${CONFIG_VERSION}`);
|
||||
localStorage.removeItem('configuration');
|
||||
} else {
|
||||
configuration.addressOutputFormat = parsed.assemblyOutputFormat;
|
||||
configuration.machineCodeOutputFormat = parsed.machineCodeOutputFormat;
|
||||
configuration.machineCodeShowLabels = parsed.machineCodeShowLabels;
|
||||
configuration.targetArch = parsed.targetArch;
|
||||
configuration.syntaxHighlighting = parsed.syntaxHighlighting;
|
||||
configuration.inlineSourceFormat = parsed.inlineSourceFormat;
|
||||
configuration.rawOutput = parsed.rawOutput;
|
||||
configuration.sendDelay = parsed.sendDelay;
|
||||
|
||||
configuration.outputFormatSelect.value = configuration.machineCodeOutputFormat;
|
||||
configuration.addressFormatSelect.value = configuration.addressOutputFormat;
|
||||
configuration.labelsCheckbox.checked = configuration.machineCodeShowLabels;
|
||||
configuration.targetArchSelect.value = configuration.targetArch;
|
||||
configuration.syntaxHighlightingSelect.value = configuration.syntaxHighlighting;
|
||||
configuration.inlineSourceSelect.value = configuration.inlineSourceFormat;
|
||||
configuration.rawOutputCheckbox.checked = configuration.rawOutput;
|
||||
configuration.sendDelayInput.value = configuration.sendDelay.toString();
|
||||
}
|
||||
}
|
||||
|
||||
configuration.update();
|
||||
updateTargetArch(configuration.targetArch);
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
saveToLocalStorage() {
|
||||
const values: ConfigurationSpecification = {
|
||||
version: CONFIG_VERSION,
|
||||
targetArch: this.targetArch,
|
||||
assemblyOutputFormat: this.addressOutputFormat,
|
||||
machineCodeOutputFormat: this.machineCodeOutputFormat,
|
||||
machineCodeShowLabels: this.machineCodeShowLabels,
|
||||
inlineSourceFormat: this.inlineSourceFormat,
|
||||
syntaxHighlighting: this.syntaxHighlighting,
|
||||
rawOutput: this.rawOutput,
|
||||
sendDelay: this.sendDelay,
|
||||
};
|
||||
localStorage.setItem('configuration', JSON.stringify(values));
|
||||
}
|
||||
}
|
||||
|
||||
function getArch(name: string): ArchSpecification {
|
||||
if (name === 'v8') {
|
||||
return targetV8;
|
||||
} else if (name === 'parva_0_1') {
|
||||
return targetParva_0_1;
|
||||
} else if (name === 'lodestar') {
|
||||
return targetLodestar as any;
|
||||
} else {
|
||||
throw new Error('Unknown architecture \'' + name + '\'');
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
assemblyInput = document.getElementById('assembly-input') as HTMLTextAreaElement;
|
||||
assemblyStatusOutput = document.getElementById('assembly-status') as HTMLSpanElement;
|
||||
machineCodeOutput = document.getElementById('machine-code-text') as HTMLPreElement;
|
||||
|
||||
controlSendButton = document.getElementById('control-send-button') as HTMLButtonElement;
|
||||
controlSendCancelButton = document.getElementById('control-send-cancel-button') as HTMLButtonElement;
|
||||
controlSendStatus = document.getElementById('control-send-status') as HTMLPreElement;
|
||||
controlSendStartInput = document.getElementById('control-send-start') as HTMLInputElement;
|
||||
controlSendButton.addEventListener('click', sendToLbp);
|
||||
|
||||
emulatorOutput = document.getElementById('emulator-output') as HTMLDivElement;
|
||||
emulatorResetButton = document.getElementById('emulator-control-reset') as HTMLButtonElement;
|
||||
emulatorStepButton = document.getElementById('emulator-control-step') as HTMLButtonElement;
|
||||
emulatorStep10Button = document.getElementById('emulator-control-step-10') as HTMLButtonElement;
|
||||
emulatorRunButton = document.getElementById('emulator-control-run') as HTMLButtonElement;
|
||||
|
||||
emulatorResetButton.addEventListener('click', () => {
|
||||
const memory = [];
|
||||
for (const line of assemblyOutput.lines) {
|
||||
if (line.tag === 'instruction') {
|
||||
const bytes = line.bits.match(new RegExp(`.{1,${targetArch.wordSize}}`, 'g'))!;
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
memory[line.address + i] = parseInt(bytes[i], 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
targetArch.emulator?.init(memory);
|
||||
updateEmulatorOutput();
|
||||
});
|
||||
emulatorStepButton.addEventListener('click', () => {
|
||||
targetArch.emulator?.step();
|
||||
updateEmulatorOutput();
|
||||
});
|
||||
emulatorStep10Button.addEventListener('click', () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
targetArch.emulator?.step();
|
||||
}
|
||||
updateEmulatorOutput();
|
||||
});
|
||||
emulatorRunButton.addEventListener('click', () => {
|
||||
if (emulatorRunning) {
|
||||
emulatorRunning = false;
|
||||
emulatorRunButton.innerText = 'Run';
|
||||
return;
|
||||
}
|
||||
emulatorRunning = true;
|
||||
emulatorRunButton.innerText = 'Stop';
|
||||
const run = async () => {
|
||||
let nextStepTime = null;
|
||||
while (true) {
|
||||
if (!emulatorRunning) {
|
||||
nextStepTime = null;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
break;
|
||||
} else if (nextStepTime === null) {
|
||||
nextStepTime = Date.now();
|
||||
}
|
||||
|
||||
while (Date.now() >= nextStepTime) {
|
||||
targetArch.emulator?.step();
|
||||
nextStepTime += 1000 / EMULATOR_SPEED;
|
||||
|
||||
if (emulatorBreakpoints.has(targetArch.emulator?.pc)) {
|
||||
emulatorRunning = false;
|
||||
emulatorRunButton.innerText = 'Run';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
updateOutput();
|
||||
await new Promise(resolve => setTimeout(resolve, nextStepTime - Date.now()));
|
||||
}
|
||||
};
|
||||
run();
|
||||
});
|
||||
|
||||
bitUtils.expose();
|
||||
|
||||
assemblyInput.value = localStorage.getItem('assembly') ?? '';
|
||||
|
||||
var textarea = document.getElementById('assembly-input') as HTMLTextAreaElement;
|
||||
textDecorator = new window['TextareaDecorator'](textarea, new window['Parser']({}));
|
||||
|
||||
configuration = Configuration.fromLocalStorage();
|
||||
|
||||
updateOutput();
|
||||
|
||||
assemblyInput.addEventListener('input', () => {
|
||||
lastInputReceivedTime = Date.now();
|
||||
setTimeout(() => {
|
||||
if (Date.now() - lastInputReceivedTime > 300) {
|
||||
localStorage.setItem('assembly', assemblyInput.value);
|
||||
updateOutput();
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
assemblyInput.addEventListener('keydown', function (event) {
|
||||
if (event.key == 'Tab') {
|
||||
event.preventDefault();
|
||||
var start = this.selectionStart;
|
||||
var end = this.selectionEnd;
|
||||
|
||||
this.value = this.value.substring(0, start) +
|
||||
"\t" + this.value.substring(end);
|
||||
|
||||
this.selectionStart =
|
||||
this.selectionEnd = start + 1;
|
||||
textDecorator.update();
|
||||
}
|
||||
});
|
||||
|
||||
// ---
|
||||
// Tests
|
||||
// ---
|
||||
|
||||
targetTest.runTests();
|
||||
}
|
||||
|
||||
function updateTargetArch(name: ArchName) {
|
||||
targetArch = getArch(name);
|
||||
|
||||
window['emulator'] = targetArch?.emulator;
|
||||
|
||||
const parser = targetArch.syntaxHighlighting;
|
||||
textDecorator.parser = parser;
|
||||
textDecorator.update();
|
||||
// get the textarea
|
||||
// wait for the page to finish loading before accessing the DOM
|
||||
}
|
||||
|
||||
function updateEmulatorOutput() {
|
||||
emulatorOutput.innerHTML = targetArch.emulator?.printState() ?? '';
|
||||
|
||||
for (const line of Array.from(machineCodeOutput.querySelectorAll('pre.line'))) {
|
||||
const address = parseInt(line.getAttribute('data-address')!);
|
||||
if (targetArch.emulator?.pc === address) {
|
||||
line.classList.add('current');
|
||||
} else {
|
||||
line.classList.remove('current');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function updateOutput() {
|
||||
|
||||
if (showDocs) {
|
||||
machineCodeOutput.innerHTML = '';
|
||||
const element = document.createElement('div');
|
||||
element.innerHTML = window['marked'].parse(targetArch.documentation, { breaks: true });
|
||||
machineCodeOutput.appendChild(element);
|
||||
return;
|
||||
}
|
||||
|
||||
const input: AssemblyInput = {
|
||||
source: assemblyInput.value,
|
||||
};
|
||||
|
||||
assemblyOutput = targetArch.assemble(input);
|
||||
const errorMap = {};
|
||||
for (const error of assemblyOutput.errors) {
|
||||
errorMap[error.line] = [...(errorMap[error.line] ?? []), error.message];
|
||||
}
|
||||
textDecorator.setErrorMap(errorMap);
|
||||
|
||||
assemblyStatusOutput.innerText = assemblyOutput.errors.map(e => `Line ${e.line + 1}: ${e.message}`).join('\n');
|
||||
|
||||
const outputFormat = configuration.machineCodeOutputFormat
|
||||
const addressFormat = configuration.addressOutputFormat;
|
||||
const sourceFormat = configuration.inlineSourceFormat;
|
||||
|
||||
machineCodeOutput.innerHTML = '';
|
||||
|
||||
for (const line of assemblyOutput.lines) {
|
||||
if (line.tag === 'label') {
|
||||
if (configuration.machineCodeShowLabels) {
|
||||
if (configuration.rawOutput) {
|
||||
machineCodeOutput.innerText += `${line.name}:\n`;
|
||||
} else {
|
||||
const element = document.createElement('pre');
|
||||
element.classList.add('label');
|
||||
element.innerText = `${line.name}:`;
|
||||
machineCodeOutput.appendChild(element);
|
||||
}
|
||||
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const binary = line.bits;
|
||||
const binaryValue = parseInt(binary, 2);
|
||||
const address = line.address;
|
||||
const maximumBitCount = targetArch.maxWordsPerInstruction * targetArch.wordSize;
|
||||
|
||||
let lineRepresentation = '';
|
||||
if (outputFormat === 'binary') {
|
||||
const maximumStringLength = (maximumBitCount / 8) * 9;
|
||||
const binaryString = binary.match(/.{1,8}/g)!.map(s => parseInt(s, 2).toString(2).padStart(8, '0')).join(' ');
|
||||
lineRepresentation = binaryString.padEnd(maximumStringLength + 2, ' ');
|
||||
} else if (outputFormat === 'hex') {
|
||||
const maximumStringLength = (maximumBitCount / 8) * 3;
|
||||
const hexString = binary.match(/.{1,8}/g)!.map(s => parseInt(s, 2).toString(16).padStart(2, '0')).join(' ');
|
||||
lineRepresentation = hexString.padEnd(maximumStringLength + 2, ' ');
|
||||
} else if (outputFormat === 'note') {
|
||||
lineRepresentation = toNote(binaryValue, targetArch.wordSize).padStart(16, ' ');
|
||||
} else if (outputFormat === 'decimal') {
|
||||
const maximumStringLength = (maximumBitCount / 3);
|
||||
const decimalString = binary.match(new RegExp(`.{1,${targetArch.wordSize}}`, 'g'))!
|
||||
.map(s => parseInt(s, 2).toString(10).padStart(8, ' ')).join(' ');
|
||||
lineRepresentation = decimalString.padEnd(maximumStringLength, ' ');
|
||||
}
|
||||
|
||||
let addressRepresentation = '';
|
||||
if (addressFormat === 'binary') {
|
||||
addressRepresentation = address.toString(2).padStart(16, '0');
|
||||
} else if (addressFormat === 'hex') {
|
||||
addressRepresentation = address.toString(16).padStart(2, '0').padStart(4, ' ');
|
||||
} else if (addressFormat === 'note') {
|
||||
addressRepresentation = toNote(address, targetArch.wordSize).padStart(14, ' ');
|
||||
} else if (addressFormat === 'decimal') {
|
||||
addressRepresentation = address.toString(10).padStart(3, ' ');
|
||||
} else if (addressFormat === 'none') {
|
||||
addressRepresentation = '';
|
||||
}
|
||||
|
||||
if (addressFormat !== 'none') {
|
||||
addressRepresentation += ': ';
|
||||
}
|
||||
|
||||
let sourceRepresentation = '';
|
||||
if (sourceFormat === 'instruction') {
|
||||
sourceRepresentation = '; ' + line.source.realInstruction;
|
||||
} else if (sourceFormat === 'source') {
|
||||
sourceRepresentation = '; ' + line.source.sourceInstruction;
|
||||
} else if (sourceFormat === 'comments') {
|
||||
sourceRepresentation = '; ' + line.source.sourceInstructionCommented;
|
||||
}
|
||||
|
||||
if (configuration.rawOutput) {
|
||||
machineCodeOutput.innerText += `${addressRepresentation}${lineRepresentation} ${sourceRepresentation}\n`;
|
||||
} else {
|
||||
const lineElement = document.createElement('pre');
|
||||
lineElement.classList.add('line');
|
||||
const addressElement = document.createElement('span');
|
||||
const outputElement = document.createElement('span');
|
||||
const sourceElement = document.createElement('span');
|
||||
addressElement.innerText = `${addressRepresentation}`;
|
||||
addressElement.classList.add('address');
|
||||
outputElement.innerText = `${lineRepresentation}`;
|
||||
outputElement.classList.add('binary');
|
||||
sourceElement.innerText = `${sourceRepresentation}`;
|
||||
sourceElement.classList.add('source');
|
||||
|
||||
lineElement.appendChild(addressElement);
|
||||
lineElement.appendChild(outputElement);
|
||||
lineElement.appendChild(sourceElement);
|
||||
|
||||
addressElement.addEventListener('click', () => {
|
||||
if (emulatorBreakpoints.has(address)) {
|
||||
emulatorBreakpoints.delete(address);
|
||||
lineElement.classList.remove('breakpoint');
|
||||
} else {
|
||||
emulatorBreakpoints.add(address);
|
||||
lineElement.classList.add('breakpoint');
|
||||
}
|
||||
});
|
||||
|
||||
lineElement.setAttribute('data-address', address.toString());
|
||||
|
||||
if (emulatorBreakpoints.has(address)) {
|
||||
lineElement.classList.add('breakpoint');
|
||||
}
|
||||
|
||||
machineCodeOutput.appendChild(lineElement);
|
||||
}
|
||||
}
|
||||
|
||||
updateEmulatorOutput();
|
||||
}
|
||||
|
||||
function sendToLbp() {
|
||||
const startAddress = parseInt(controlSendStartInput.value) ?? 0;
|
||||
|
||||
const lbpData = [];
|
||||
for (const line of assemblyOutput.lines) {
|
||||
if (line.tag === 'label') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const binary = line.bits;
|
||||
const wordSize = targetArch.wordSize;
|
||||
const words = binary.match(new RegExp(`.{1,${wordSize}}`, 'g'))!;
|
||||
let address = line.address;
|
||||
if (address < startAddress) {
|
||||
continue;
|
||||
}
|
||||
for (const word of words) {
|
||||
const binaryValue = parseInt(word, 2);
|
||||
// lbpData.push([address & 0xfff, address >> 12, binaryValue & 0xfff, binaryValue >> 12]);
|
||||
lbpData.push([address, binaryValue]);
|
||||
address += 1;
|
||||
}
|
||||
}
|
||||
const payload = JSON.stringify(lbpData);
|
||||
const delay = configuration.sendDelay;
|
||||
|
||||
// send to websocket
|
||||
const socket = new WebSocket('ws://localhost:8090');
|
||||
controlSendStatus.innerText = 'Connecting...';
|
||||
|
||||
const cancel = () => {
|
||||
controlSendCancelButton.innerText = 'Cancelling...';
|
||||
socket.send(JSON.stringify({ type: 'cancel' }));
|
||||
};
|
||||
let complete = false;
|
||||
|
||||
socket.addEventListener('open', () => {
|
||||
socket.send(JSON.stringify({ type: 'data', data: payload, speed: delay }));
|
||||
|
||||
controlSendCancelButton.disabled = false;
|
||||
controlSendCancelButton.addEventListener('click', cancel);
|
||||
|
||||
socket.addEventListener('message', (event) => {
|
||||
const payload = JSON.parse(event.data);
|
||||
controlSendStatus.innerText = `${payload.message}\nChecksum: ${toNote(payload.checksum, 24)}`;
|
||||
if (payload.status === 'complete') {
|
||||
controlSendCancelButton.disabled = true;
|
||||
complete = true;
|
||||
socket.close();
|
||||
} else if (payload.status === 'cancelled') {
|
||||
controlSendCancelButton.innerText = 'Cancel';
|
||||
controlSendCancelButton.disabled = true;
|
||||
complete = true;
|
||||
controlSendCancelButton.removeEventListener('click', cancel);
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
socket.addEventListener('error', () => {
|
||||
controlSendStatus.innerText = 'Error connecting';
|
||||
controlSendCancelButton.disabled = true;
|
||||
});
|
||||
socket.addEventListener('close', () => {
|
||||
if (!complete) {
|
||||
controlSendStatus.innerText = 'Disconnected';
|
||||
}
|
||||
controlSendCancelButton.disabled = true;
|
||||
controlSendCancelButton.removeEventListener('click', cancel);
|
||||
controlSendCancelButton.innerText = 'Cancel';
|
||||
});
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue