rename js folder to ts
This commit is contained in:
165
www/src/ts/virtual_machine/base_device.ts
Normal file
165
www/src/ts/virtual_machine/base_device.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { BaseElement } from "../components";
|
||||
import {
|
||||
DeviceRef,
|
||||
Fields,
|
||||
Reagents,
|
||||
Slot,
|
||||
Connection,
|
||||
ICError,
|
||||
Registers,
|
||||
Stack,
|
||||
Aliases,
|
||||
Defines,
|
||||
Pins,
|
||||
} from "ic10emu_wasm";
|
||||
import { structuralEqual } from "../utils";
|
||||
|
||||
export class VMBaseDevice extends BaseElement {
|
||||
@property({ type: Number }) accessor deviceID: number;
|
||||
@state() protected accessor device: DeviceRef;
|
||||
|
||||
@state() accessor name: string | null;
|
||||
@state() accessor nameHash: number | null;
|
||||
@state() accessor prefabName: string | null;
|
||||
@state() accessor fields: Fields;
|
||||
@state() accessor slots: Slot[];
|
||||
@state() accessor reagents: Reagents;
|
||||
@state() accessor connections: Connection[];
|
||||
@state() accessor icIP: number;
|
||||
@state() accessor icOpCount: number;
|
||||
@state() accessor icState: string;
|
||||
@state() accessor errors: ICError[];
|
||||
@state() accessor registers: Registers | null;
|
||||
@state() accessor stack: Stack | null;
|
||||
@state() accessor aliases: Aliases | null;
|
||||
@state() accessor defines: Defines | null;
|
||||
@state() accessor pins: Pins | null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.name = null;
|
||||
this.nameHash = null;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
this.device = window.VM!.devices.get(this.deviceID)!;
|
||||
window.VM?.addEventListener(
|
||||
"vm-device-modified",
|
||||
this._handleDeviceModified.bind(this),
|
||||
);
|
||||
this.updateDevice();
|
||||
return root;
|
||||
}
|
||||
|
||||
_handleDeviceModified(e: CustomEvent) {
|
||||
const id = e.detail;
|
||||
if (this.deviceID === id) {
|
||||
this.updateDevice();
|
||||
}
|
||||
}
|
||||
|
||||
updateDevice() {
|
||||
const name = this.device.name ?? null;
|
||||
if (this.name !== name) {
|
||||
this.name = name;
|
||||
}
|
||||
const nameHash = this.device.nameHash ?? null;
|
||||
if (this.nameHash !== nameHash) {
|
||||
this.nameHash = nameHash;
|
||||
}
|
||||
const prefabName = this.device.prefabName ?? null;
|
||||
if (this.prefabName !== prefabName) {
|
||||
this.prefabName = prefabName;
|
||||
}
|
||||
const fields = this.device.fields;
|
||||
if (!structuralEqual(this.fields, fields)) {
|
||||
this.fields = fields;
|
||||
}
|
||||
const slots = this.device.slots;
|
||||
if (!structuralEqual(this.slots, slots)) {
|
||||
this.slots = slots;
|
||||
}
|
||||
const reagents = this.device.reagents;
|
||||
if (!structuralEqual(this.reagents, reagents)) {
|
||||
this.reagents = reagents;
|
||||
}
|
||||
const connections = this.device.connections;
|
||||
if (!structuralEqual(this.connections, connections)) {
|
||||
this.connections = connections;
|
||||
}
|
||||
this.updateIC();
|
||||
}
|
||||
|
||||
updateIC() {
|
||||
const ip = this.device.ip!;
|
||||
if (this.icIP !== ip) {
|
||||
this.icIP = ip;
|
||||
}
|
||||
const opCount = this.device.instructionCount!;
|
||||
if (this.icOpCount !== opCount) {
|
||||
this.icOpCount = opCount;
|
||||
}
|
||||
const state = this.device.state!;
|
||||
if (this.icState !== state) {
|
||||
this.icState = state;
|
||||
}
|
||||
const errors = this.device.program!.errors ?? null;
|
||||
if (!structuralEqual(this.errors, errors)) {
|
||||
this.errors = errors;
|
||||
}
|
||||
const registers = this.device.registers ?? null;
|
||||
if (!structuralEqual(this.registers, registers)) {
|
||||
this.registers = registers;
|
||||
}
|
||||
const stack = this.device.stack ?? null;
|
||||
if (!structuralEqual(this.stack, stack)) {
|
||||
this.stack = stack;
|
||||
}
|
||||
const aliases = this.device.aliases ?? null;
|
||||
if (!structuralEqual(this.aliases, aliases)) {
|
||||
this.aliases = aliases;
|
||||
}
|
||||
const defines = this.device.defines ?? null;
|
||||
if (!structuralEqual(this.defines, defines)) {
|
||||
this.defines = defines;
|
||||
}
|
||||
const pins = this.device.pins ?? null;
|
||||
if(!structuralEqual(this.pins, pins)) {
|
||||
this.pins = pins;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class VMActiveIC extends VMBaseDevice {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.deviceID = window.App!.session.activeIC;
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
window.VM?.addEventListener(
|
||||
"vm-run-ic",
|
||||
this._handleDeviceModified.bind(this),
|
||||
);
|
||||
window.App?.session.addEventListener(
|
||||
"session-active-ic",
|
||||
this._handleActiveIC.bind(this),
|
||||
);
|
||||
this.updateIC();
|
||||
return root;
|
||||
}
|
||||
|
||||
_handleActiveIC(e: CustomEvent) {
|
||||
const id = e.detail;
|
||||
if (this.deviceID !== id) {
|
||||
this.deviceID = id;
|
||||
this.device = window.VM!.devices.get(this.deviceID)!;
|
||||
}
|
||||
this.updateDevice();
|
||||
}
|
||||
|
||||
}
|
||||
147
www/src/ts/virtual_machine/controls.ts
Normal file
147
www/src/ts/virtual_machine/controls.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { html, css } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { defaultCss } from "../components";
|
||||
import { VMActiveIC } from "./base_device";
|
||||
import { structuralEqual } from "../utils";
|
||||
|
||||
import "@shoelace-style/shoelace/dist/components/card/card.js";
|
||||
import "@shoelace-style/shoelace/dist/components/button-group/button-group.js";
|
||||
import "@shoelace-style/shoelace/dist/components/button/button.js";
|
||||
import "@shoelace-style/shoelace/dist/components/icon/icon.js";
|
||||
import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js";
|
||||
import "@shoelace-style/shoelace/dist/components/divider/divider.js";
|
||||
|
||||
@customElement("vm-ic-controls")
|
||||
export class VMICControls extends VMActiveIC {
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
:host {
|
||||
}
|
||||
.card {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: var(--sl-font-size-small);
|
||||
}
|
||||
.stats {
|
||||
font-size: var(--sl-font-size-x-small);
|
||||
}
|
||||
.device-id {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
.button-group-toolbar sl-button-group:not(:last-of-type) {
|
||||
margin-right: var(--sl-spacing-x-small);
|
||||
}
|
||||
sl-divider {
|
||||
--spacing: 0.25rem;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.deviceID = window.App!.session.activeIC;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<sl-card class="card">
|
||||
<div class="controls" slot="header">
|
||||
<sl-button-group>
|
||||
<sl-tooltip
|
||||
content="Run the active IC through one tick (128 operations)"
|
||||
>
|
||||
<sl-button
|
||||
size="small"
|
||||
variant="primary"
|
||||
@click=${this._handleRunClick}
|
||||
>
|
||||
<span>Run</span>
|
||||
<sl-icon name="play" label="Run" slot="prefix"></sl-icon>
|
||||
</sl-button>
|
||||
</sl-tooltip>
|
||||
<sl-tooltip content="Run the active IC through a single operations">
|
||||
<sl-button
|
||||
size="small"
|
||||
variant="success"
|
||||
@click=${this._handleStepClick}
|
||||
>
|
||||
<span>Step</span>
|
||||
<sl-icon
|
||||
name="chevron-bar-right"
|
||||
label="Step"
|
||||
slot="prefix"
|
||||
></sl-icon>
|
||||
</sl-button>
|
||||
</sl-tooltip>
|
||||
<sl-tooltip content="Reset the active IC">
|
||||
<sl-button
|
||||
size="small"
|
||||
variant="warning"
|
||||
@click=${this._handleResetClick}
|
||||
>
|
||||
<span>Reset</span>
|
||||
<sl-icon
|
||||
name="arrow-clockwise"
|
||||
label="Reset"
|
||||
slot="prefix"
|
||||
></sl-icon>
|
||||
</sl-button>
|
||||
</sl-tooltip>
|
||||
</sl-button-group>
|
||||
<div class="device-id">
|
||||
Device:
|
||||
${this.deviceID}${this.name ?? this.prefabName
|
||||
? ` : ${this.name ?? this.prefabName}`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="hstack">
|
||||
<span>Instruction Pointer</span>
|
||||
<span class="ms-auto">${this.icIP}</span>
|
||||
</div>
|
||||
<sl-divider></sl-divider>
|
||||
<div class="hstack">
|
||||
<span>Last Run Operations Count</span>
|
||||
<span class="ms-auto">${this.icOpCount}</span>
|
||||
</div>
|
||||
<sl-divider></sl-divider>
|
||||
<div class="hstack">
|
||||
<span>Last State</span>
|
||||
<span class="ms-auto">${this.icState}</span>
|
||||
</div>
|
||||
<sl-divider></sl-divider>
|
||||
<div class="vstack">
|
||||
<span>Errors</span>
|
||||
${this.errors.map(
|
||||
(err) =>
|
||||
html`<div class="hstack">
|
||||
<span>
|
||||
Line: ${err.ParseError.line} -
|
||||
${err.ParseError.start}:${err.ParseError.end}
|
||||
</span>
|
||||
<span class="ms-auto">${err.ParseError.msg}</span>
|
||||
</div>`,
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</sl-card>
|
||||
`;
|
||||
}
|
||||
|
||||
_handleRunClick() {
|
||||
window.VM?.run();
|
||||
}
|
||||
_handleStepClick() {
|
||||
window.VM?.step();
|
||||
}
|
||||
_handleResetClick() {
|
||||
window.VM?.reset();
|
||||
}
|
||||
}
|
||||
398
www/src/ts/virtual_machine/device.ts
Normal file
398
www/src/ts/virtual_machine/device.ts
Normal file
@@ -0,0 +1,398 @@
|
||||
import { Offcanvas } from "bootstrap";
|
||||
import { VirtualMachine, VirtualMachineUI } from ".";
|
||||
import { DeviceRef, VM } from "ic10emu_wasm";
|
||||
|
||||
|
||||
|
||||
|
||||
class VMDeviceUI {
|
||||
ui: VirtualMachineUI;
|
||||
summary: HTMLDivElement;
|
||||
canvasEl: HTMLDivElement;
|
||||
deviceCountEl: HTMLElement;
|
||||
canvas: Offcanvas;
|
||||
private _deviceSummaryCards: Map<number, VMDeviceSummaryCard>;
|
||||
private _offCanvaseCards: Map<
|
||||
number,
|
||||
{ col: HTMLElement; card: VMDeviceCard }
|
||||
>;
|
||||
|
||||
constructor(ui: VirtualMachineUI) {
|
||||
const that = this;
|
||||
that.ui = ui;
|
||||
this.summary = document.getElementById("vmDeviceSummary") as HTMLDivElement;
|
||||
this.canvasEl = document.getElementById(
|
||||
"vmDevicesOCBody",
|
||||
) as HTMLDivElement;
|
||||
this.deviceCountEl = document.getElementById("vmViewDeviceCount");
|
||||
this.canvas = new Offcanvas(this.canvasEl);
|
||||
this._deviceSummaryCards = new Map();
|
||||
this._offCanvaseCards = new Map();
|
||||
}
|
||||
|
||||
update(active_ic: DeviceRef) {
|
||||
const devices = window.VM.devices;
|
||||
this.deviceCountEl.textContent = `(${devices.size})`;
|
||||
for (const [id, device] of devices) {
|
||||
if (!this._deviceSummaryCards.has(id)) {
|
||||
this._deviceSummaryCards.set(id, new VMDeviceSummaryCard(this, device));
|
||||
}
|
||||
if (!this._offCanvaseCards.has(id)) {
|
||||
const col = document.createElement("div");
|
||||
col.classList.add("col");
|
||||
col.id = `${this.canvasEl.id}_col${id}`
|
||||
this.canvasEl.appendChild(col);
|
||||
this._offCanvaseCards.set(id, {
|
||||
col,
|
||||
card: new VMDeviceCard(this, col, device),
|
||||
});
|
||||
}
|
||||
}
|
||||
this._deviceSummaryCards.forEach((card, id, cards) => {
|
||||
if (!devices.has(id)) {
|
||||
card.destroy();
|
||||
cards.delete(id);
|
||||
} else {
|
||||
card.update(active_ic);
|
||||
}
|
||||
}, this);
|
||||
this._offCanvaseCards.forEach((card, id, cards) => {
|
||||
if (!devices.has(id)) {
|
||||
card.card.destroy();
|
||||
card.col.remove();
|
||||
cards.delete(id);
|
||||
} else {
|
||||
card.card.update(active_ic);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
class VMDeviceSummaryCard {
|
||||
root: HTMLDivElement;
|
||||
viewBtn: HTMLButtonElement;
|
||||
deviceUI: VMDeviceUI;
|
||||
device: DeviceRef;
|
||||
badges: HTMLSpanElement[];
|
||||
constructor(deviceUI: VMDeviceUI, device: DeviceRef) {
|
||||
// const that = this;
|
||||
this.deviceUI = deviceUI;
|
||||
this.device = device;
|
||||
this.root = document.createElement("div");
|
||||
this.root.classList.add(
|
||||
"hstack",
|
||||
"gap-2",
|
||||
"bg-light-subtle",
|
||||
"border",
|
||||
"border-secondary-subtle",
|
||||
"rounded",
|
||||
);
|
||||
this.viewBtn = document.createElement("button");
|
||||
this.viewBtn.type = "button";
|
||||
this.viewBtn.classList.add("btn", "btn-success");
|
||||
this.root.appendChild(this.viewBtn);
|
||||
this.deviceUI.summary.appendChild(this.root);
|
||||
this.badges = [];
|
||||
|
||||
this.update(window.VM.activeIC);
|
||||
}
|
||||
|
||||
update(active_ic: DeviceRef) {
|
||||
const that = this;
|
||||
// clear previous badges
|
||||
this.badges.forEach((badge) => badge.remove());
|
||||
this.badges = [];
|
||||
|
||||
//update name
|
||||
var deviceName = this.device.name ?? this.device.prefabName ?? "";
|
||||
if (deviceName) {
|
||||
deviceName = `: ${deviceName}`;
|
||||
}
|
||||
const btnTxt = `Device ${this.device.id}${deviceName}`;
|
||||
this.viewBtn.textContent = btnTxt;
|
||||
|
||||
// regenerate badges
|
||||
this.device.connections.forEach((conn, index) => {
|
||||
if (typeof conn === "object") {
|
||||
var badge = document.createElement("span");
|
||||
badge.classList.add("badge", "text-bg-light");
|
||||
badge.textContent = `Net ${index}:${conn.CableNetwork}`;
|
||||
that.badges.push(badge);
|
||||
that.root.appendChild(badge);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.device.id === active_ic.id) {
|
||||
var badge = document.createElement("span");
|
||||
badge.classList.add("badge", "text-bg-success");
|
||||
badge.textContent = "db";
|
||||
that.badges.push(badge);
|
||||
that.root.appendChild(badge);
|
||||
}
|
||||
|
||||
active_ic.pins?.forEach((id, index) => {
|
||||
if (that.device.id === id) {
|
||||
var badge = document.createElement("span");
|
||||
badge.classList.add("badge", "text-bg-success");
|
||||
badge.textContent = `d${index}`;
|
||||
that.badges.push(badge);
|
||||
that.root.appendChild(badge);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.root.remove();
|
||||
}
|
||||
}
|
||||
|
||||
class VMDeviceCard {
|
||||
ui: VMDeviceUI;
|
||||
container: HTMLElement;
|
||||
device: DeviceRef;
|
||||
root: HTMLDivElement;
|
||||
nav: HTMLUListElement;
|
||||
|
||||
header: HTMLDivElement;
|
||||
nameInput: HTMLInputElement;
|
||||
nameHash: HTMLSpanElement;
|
||||
body: HTMLDivElement;
|
||||
badges: HTMLSpanElement[];
|
||||
fieldsContainer: HTMLDivElement;
|
||||
slotsContainer: HTMLDivElement;
|
||||
pinsContainer: HTMLDivElement;
|
||||
networksContainer: HTMLDivElement;
|
||||
reagentsContainer: HTMLDivElement;
|
||||
nav_id: string;
|
||||
navTabs: { [key: string]: { li: HTMLLIElement; button: HTMLButtonElement } };
|
||||
paneContainer: HTMLDivElement;
|
||||
tabPanes: { [key: string]: HTMLElement };
|
||||
image: HTMLImageElement;
|
||||
image_err: boolean;
|
||||
title: HTMLHeadingElement;
|
||||
fieldEls: Map<string, VMDeviceField>;
|
||||
|
||||
constructor(ui: VMDeviceUI, container: HTMLElement, device: DeviceRef) {
|
||||
this.ui = ui;
|
||||
this.container = container;
|
||||
this.device = device;
|
||||
this.nav_id = `${this.container.id}_vmDeviceCard${this.device.id}`;
|
||||
|
||||
this.root = document.createElement("div");
|
||||
this.root.classList.add("card");
|
||||
|
||||
this.header = document.createElement("div");
|
||||
this.header.classList.add("card-header", "hstack");
|
||||
this.image = document.createElement("img");
|
||||
this.image_err = false;
|
||||
this.image.src = `/img/stationpedia/${this.device.prefabName}.png`;
|
||||
this.image.onerror = this.onImageErr;
|
||||
this.image.width = 48;
|
||||
this.image.classList.add("me-2");
|
||||
this.header.appendChild(this.image);
|
||||
|
||||
this.title = document.createElement("h5");
|
||||
this.title.textContent = `Device ${this.device.id} : ${this.device.prefabName ?? ""}`;
|
||||
this.header.appendChild(this.title);
|
||||
|
||||
this.nameInput = document.createElement("input");
|
||||
this.nameHash = document.createElement("span");
|
||||
|
||||
this.root.appendChild(this.header);
|
||||
|
||||
this.body = document.createElement("div");
|
||||
this.body.classList.add("card-body");
|
||||
this.root.appendChild(this.body);
|
||||
|
||||
this.nav = document.createElement("ul");
|
||||
this.nav.classList.add("nav", "nav-tabs");
|
||||
this.nav.role = "tablist";
|
||||
this.nav.id = this.nav_id;
|
||||
this.navTabs = {};
|
||||
this.tabPanes = {};
|
||||
|
||||
this.body.appendChild(this.nav);
|
||||
|
||||
this.paneContainer = document.createElement("div");
|
||||
this.paneContainer.id = `${this.nav_id}_tabs`;
|
||||
|
||||
this.body.appendChild(this.paneContainer);
|
||||
|
||||
this.badges = [];
|
||||
this.fieldsContainer = document.createElement("div");
|
||||
this.fieldsContainer.id = `${this.nav_id}_fields`;
|
||||
this.fieldsContainer.classList.add("vstack");
|
||||
this.fieldEls = new Map();
|
||||
this.slotsContainer = document.createElement("div");
|
||||
this.slotsContainer.id = `${this.nav_id}_slots`;
|
||||
this.slotsContainer.classList.add("vstack");
|
||||
this.reagentsContainer = document.createElement("div");
|
||||
this.reagentsContainer.id = `${this.nav_id}_reagents`;
|
||||
this.reagentsContainer.classList.add("vstack");
|
||||
this.networksContainer = document.createElement("div");
|
||||
this.networksContainer.id = `${this.nav_id}_networks`;
|
||||
this.networksContainer.classList.add("vstack");
|
||||
this.pinsContainer = document.createElement("div");
|
||||
this.pinsContainer.id = `${this.nav_id}_pins`;
|
||||
this.pinsContainer.classList.add("vstack");
|
||||
|
||||
this.addTab("Fields", this.fieldsContainer);
|
||||
this.addTab("Slots", this.slotsContainer);
|
||||
this.addTab("Networks", this.networksContainer);
|
||||
|
||||
this.update(window.VM.activeIC);
|
||||
|
||||
// do last to minimise reflows
|
||||
this.container.appendChild(this.root);
|
||||
}
|
||||
|
||||
onImageErr(e: Event) {
|
||||
this.image_err = true;
|
||||
console.log("Image load error", e);
|
||||
}
|
||||
|
||||
addNav(name: string, target: string) {
|
||||
if (!(name in this.navTabs)) {
|
||||
var li = document.createElement("li");
|
||||
li.classList.add("nav-item");
|
||||
li.role = "presentation";
|
||||
var button = document.createElement("button");
|
||||
button.classList.add("nav-link");
|
||||
if (!(Object.keys(this.navTabs).length > 0)) {
|
||||
button.classList.add("active");
|
||||
button.tabIndex = 0;
|
||||
} else {
|
||||
button.tabIndex = -1;
|
||||
}
|
||||
button.id = `${this.nav_id}_tab_${name}`;
|
||||
button.setAttribute("data-bs-toggle", "tab");
|
||||
button.setAttribute("data-bs-target", `#${target}`);
|
||||
button.type = "button";
|
||||
button.role = "tab";
|
||||
button.setAttribute("aria-controls", target);
|
||||
button.setAttribute(
|
||||
"aria-selected",
|
||||
Object.keys(this.navTabs).length > 0 ? "false" : "true",
|
||||
);
|
||||
button.textContent = name;
|
||||
li.appendChild(button);
|
||||
this.nav.appendChild(li);
|
||||
this.navTabs[name] = { li, button };
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
removeNav(name: string) {
|
||||
if (name in this.navTabs) {
|
||||
this.navTabs[name].li.remove();
|
||||
delete this.navTabs[name];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addTab(name: string, tab: HTMLElement) {
|
||||
const paneName = `${this.nav_id}_pane_${name}`;
|
||||
if (this.addNav(name, paneName)) {
|
||||
if (name in this.tabPanes) {
|
||||
this.tabPanes[name].remove();
|
||||
}
|
||||
const pane = document.createElement("div");
|
||||
pane.classList.add("tap-pane", "fade");
|
||||
if (!(Object.keys(this.tabPanes).length > 0)) {
|
||||
pane.classList.add("show", "active");
|
||||
}
|
||||
pane.id = paneName;
|
||||
pane.role = "tabpanel";
|
||||
pane.setAttribute("aria-labelledby", `${this.nav_id}_tab_${name}`);
|
||||
pane.tabIndex = 0;
|
||||
|
||||
this.paneContainer.appendChild(pane);
|
||||
pane.appendChild(tab);
|
||||
this.tabPanes[name] = tab;
|
||||
}
|
||||
}
|
||||
|
||||
removeTab(name: string) {
|
||||
let result = this.removeNav(name);
|
||||
if (name in this.tabPanes) {
|
||||
this.tabPanes[name].remove();
|
||||
delete this.tabPanes[name];
|
||||
return true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
update(active_ic: DeviceRef) {
|
||||
if (this.device.pins) {
|
||||
this.addTab("Pins", this.pinsContainer);
|
||||
} else {
|
||||
this.removeTab("Pins");
|
||||
}
|
||||
|
||||
// fields
|
||||
for (const [name, _field] of this.device.fields) {
|
||||
if (!this.fieldEls.has(name)) {
|
||||
const field = new VMDeviceField(this.device, name, this, this.fieldsContainer);
|
||||
this.fieldEls.set(name, field);
|
||||
}
|
||||
}
|
||||
this.fieldEls.forEach((field, name, map) => {
|
||||
if(!this.device.fields.has(name)) {
|
||||
field.destroy();
|
||||
map.delete(name);
|
||||
} else {
|
||||
field.update(active_ic);
|
||||
}
|
||||
}, this);
|
||||
|
||||
|
||||
// TODO Reagents
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.root.remove();
|
||||
}
|
||||
}
|
||||
|
||||
class VMDeviceField {
|
||||
container: HTMLElement;
|
||||
card: VMDeviceCard;
|
||||
device: DeviceRef;
|
||||
field: string;
|
||||
root: HTMLDivElement;
|
||||
name: HTMLSpanElement;
|
||||
fieldType: HTMLSpanElement;
|
||||
input: HTMLInputElement;
|
||||
constructor(device: DeviceRef, field: string, card: VMDeviceCard, container: HTMLElement) {
|
||||
this.device = device;
|
||||
this.field = field;
|
||||
this.card = card;
|
||||
this.container = container;
|
||||
this.root = document.createElement('div');
|
||||
this.root.classList.add("input-group", "input-group-sm");
|
||||
this.name = document.createElement('span');
|
||||
this.name.classList.add("input-group-text", "field_name");
|
||||
this.name.textContent = this.field;
|
||||
this.root.appendChild(this.name);
|
||||
this.fieldType = document.createElement('span');
|
||||
this.fieldType.classList.add("input-group-text", "field_type");
|
||||
this.fieldType.textContent = device.fields.get(this.field)?.field_type;
|
||||
this.root.appendChild(this.fieldType);
|
||||
this.input = document.createElement('input');
|
||||
this.input.type = "text";
|
||||
this.input.value = this.device.fields.get(this.field)?.value.toString();
|
||||
this.root.appendChild(this.input);
|
||||
|
||||
this.container.appendChild(this.root);
|
||||
}
|
||||
destroy () {
|
||||
this.root.remove();
|
||||
}
|
||||
update(_active_ic: DeviceRef) {
|
||||
this.input.value = this.device.fields.get(this.field)?.value.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export { VMDeviceUI };
|
||||
464
www/src/ts/virtual_machine/index.ts
Normal file
464
www/src/ts/virtual_machine/index.ts
Normal file
@@ -0,0 +1,464 @@
|
||||
import { DeviceRef, VM, init } from "ic10emu_wasm";
|
||||
import "./base_device";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
VM?: VirtualMachine;
|
||||
}
|
||||
}
|
||||
|
||||
type DeviceDB = {
|
||||
logic_enabled: string[];
|
||||
slot_logic_enabled: string[];
|
||||
devices: string[];
|
||||
items: {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
hash: number;
|
||||
desc: string;
|
||||
logic?: { [key: string]: string };
|
||||
slots?: { name: string; type: string }[];
|
||||
modes?: { [key: string]: string };
|
||||
conn?: { [key: string]: string[] };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
class VirtualMachine extends EventTarget {
|
||||
ic10vm: VM;
|
||||
ui: VirtualMachineUI;
|
||||
_devices: Map<number, DeviceRef>;
|
||||
_ics: Map<number, DeviceRef>;
|
||||
db: DeviceDB;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const vm = init();
|
||||
|
||||
window.VM = this;
|
||||
|
||||
this.ic10vm = vm;
|
||||
|
||||
this._devices = new Map();
|
||||
this._ics = new Map();
|
||||
|
||||
this.updateDevices();
|
||||
|
||||
this.updateCode();
|
||||
}
|
||||
|
||||
get devices() {
|
||||
return this._devices;
|
||||
}
|
||||
|
||||
get ics() {
|
||||
return this._ics;
|
||||
}
|
||||
|
||||
get activeIC() {
|
||||
return this._ics.get(window.App!.session.activeIC);
|
||||
}
|
||||
|
||||
updateDevices() {
|
||||
const device_ids = this.ic10vm.devices;
|
||||
for (const id of device_ids) {
|
||||
if (!this._devices.has(id)) {
|
||||
this._devices.set(id, this.ic10vm.getDevice(id)!);
|
||||
}
|
||||
}
|
||||
for (const id of this._devices.keys()) {
|
||||
if (!device_ids.includes(id)) {
|
||||
this._devices.get(id)!.free();
|
||||
this._devices.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
const ics = this.ic10vm.ics;
|
||||
for (const id of ics) {
|
||||
if (!this._ics.has(id)) {
|
||||
this._ics.set(id, this._devices.get(id)!);
|
||||
}
|
||||
}
|
||||
for (const id of this._ics.keys()) {
|
||||
if (!ics.includes(id)) {
|
||||
this._ics.get(id)!.free();
|
||||
this._ics.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateCode() {
|
||||
const progs = window.App!.session.programs;
|
||||
for (const id of progs.keys()) {
|
||||
const attempt = Date.now().toString(16);
|
||||
const ic = this._ics.get(id);
|
||||
const prog = progs.get(id);
|
||||
if (ic && prog) {
|
||||
console.time(`CompileProgram_${id}_${attempt}`);
|
||||
try {
|
||||
this.ics.get(id)!.setCodeInvalid(progs.get(id)!);
|
||||
const compiled = this.ics.get(id)?.program!;
|
||||
window.App?.session.setProgramErrors(id, compiled.errors);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: id }),
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
console.timeEnd(`CompileProgram_${id}_${attempt}`);
|
||||
}
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
|
||||
step() {
|
||||
const ic = this.activeIC;
|
||||
if (ic) {
|
||||
try {
|
||||
ic.step(false);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
this.update();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-run-ic", { detail: this.activeIC!.id }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
run() {
|
||||
const ic = this.activeIC;
|
||||
if (ic) {
|
||||
try {
|
||||
ic.run(false);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
this.update();
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-run-ic", { detail: this.activeIC!.id }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
const ic = this.activeIC;
|
||||
if (ic) {
|
||||
ic.reset();
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
this.updateDevices();
|
||||
this.ic10vm.lastOperationModified.forEach((id, _index, _modifiedIds) => {
|
||||
if (this.devices.has(id)) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: id }),
|
||||
);
|
||||
}
|
||||
}, this);
|
||||
const ic = this.activeIC!;
|
||||
window.App!.session.setActiveLine(window.App!.session.activeIC, ic.ip!);
|
||||
}
|
||||
|
||||
setRegister(index: number, val: number) {
|
||||
const ic = this.activeIC!;
|
||||
try {
|
||||
ic.setRegister(index, val);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: ic.id }),
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
setStack(addr: number, val: number) {
|
||||
const ic = this.activeIC!;
|
||||
try {
|
||||
ic!.setStack(addr, val);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("vm-device-modified", { detail: ic.id }),
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
setupDeviceDatabase(db: DeviceDB) {
|
||||
this.db = db;
|
||||
console.log("Loaded Device Database", this.db);
|
||||
}
|
||||
}
|
||||
|
||||
class VirtualMachineUI {
|
||||
vm: VirtualMachine;
|
||||
state: VMStateUI;
|
||||
registers: VMRegistersUI;
|
||||
stack: VMStackUI;
|
||||
|
||||
constructor(vm: VirtualMachine) {
|
||||
this.vm = vm;
|
||||
this.state = new VMStateUI(this);
|
||||
this.registers = new VMRegistersUI(this);
|
||||
this.stack = new VMStackUI(this);
|
||||
|
||||
const that = this;
|
||||
|
||||
document.getElementById("vmControlRun")!.addEventListener(
|
||||
"click",
|
||||
(_event) => {
|
||||
that.vm.run();
|
||||
},
|
||||
{ capture: true },
|
||||
);
|
||||
document.getElementById("vmControlStep")!.addEventListener(
|
||||
"click",
|
||||
(_event) => {
|
||||
that.vm.step();
|
||||
},
|
||||
{ capture: true },
|
||||
);
|
||||
document.getElementById("vmControlReset")!.addEventListener(
|
||||
"click",
|
||||
(_event) => {
|
||||
that.vm.reset();
|
||||
},
|
||||
{ capture: true },
|
||||
);
|
||||
}
|
||||
|
||||
update(ic: DeviceRef) {
|
||||
this.state.update(ic);
|
||||
this.registers.update(ic);
|
||||
this.stack.update(ic);
|
||||
}
|
||||
}
|
||||
|
||||
class VMStateUI {
|
||||
ui: VirtualMachineUI;
|
||||
instructionPointer: HTMLElement;
|
||||
instructionCounter: HTMLElement;
|
||||
lastState: HTMLElement;
|
||||
constructor(ui: VirtualMachineUI) {
|
||||
this.ui = ui;
|
||||
|
||||
this.instructionPointer = document.getElementById("vmActiveICStateIP")!;
|
||||
this.instructionCounter = document.getElementById("vmActiveICStateICount")!;
|
||||
this.lastState = document.getElementById("vmActiveICStateLastRun")!;
|
||||
}
|
||||
|
||||
update(ic: DeviceRef) {
|
||||
if (ic) {
|
||||
this.instructionPointer.innerText = ic.ip!.toString();
|
||||
this.instructionCounter.innerText = ic.instructionCount!.toString();
|
||||
this.lastState.innerText = ic.state!.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VMRegistersUI {
|
||||
ui: VirtualMachineUI;
|
||||
tbl: HTMLDivElement;
|
||||
regCells: {
|
||||
cell: HTMLDivElement;
|
||||
nameLabel: HTMLSpanElement;
|
||||
aliasesLabel: HTMLSpanElement;
|
||||
input: HTMLInputElement;
|
||||
}[];
|
||||
default_aliases: Map<string, number>;
|
||||
ic_aliases: Map<string, number>;
|
||||
constructor(ui: VirtualMachineUI) {
|
||||
const that = this;
|
||||
this.ui = ui;
|
||||
const regDom = document.getElementById("vmActiveRegisters")!;
|
||||
this.tbl = document.createElement("div");
|
||||
this.tbl.classList.add(
|
||||
"d-flex",
|
||||
"flex-wrap",
|
||||
"justify-content-start",
|
||||
"align-items-end",
|
||||
);
|
||||
this.regCells = [];
|
||||
for (var i = 0; i < 18; i++) {
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("vm_reg_cel", "align-that-stretch");
|
||||
const cell = document.createElement("div");
|
||||
cell.classList.add("input-group", "input-group-sm");
|
||||
// cell.style.width = "30%";
|
||||
const nameLabel = document.createElement("span");
|
||||
nameLabel.innerText = `r${i}`;
|
||||
nameLabel.classList.add("input-group-text");
|
||||
cell.appendChild(nameLabel);
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.value = (0).toString();
|
||||
input.dataset.index = i.toString();
|
||||
cell.appendChild(input);
|
||||
const aliasesLabel = document.createElement("span");
|
||||
aliasesLabel.classList.add("input-group-text", "reg_label");
|
||||
cell.appendChild(aliasesLabel);
|
||||
this.regCells.push({
|
||||
cell,
|
||||
nameLabel,
|
||||
aliasesLabel,
|
||||
input,
|
||||
});
|
||||
container.appendChild(cell);
|
||||
this.tbl.appendChild(container);
|
||||
}
|
||||
this.regCells.forEach((cell) => {
|
||||
cell.input.addEventListener("change", that.onCellUpdate);
|
||||
});
|
||||
this.default_aliases = new Map([
|
||||
["sp", 16],
|
||||
["ra", 17],
|
||||
]);
|
||||
this.ic_aliases = new Map();
|
||||
regDom.appendChild(this.tbl);
|
||||
}
|
||||
|
||||
onCellUpdate(e: Event) {
|
||||
let index: number;
|
||||
let val: number;
|
||||
let target = e.target as HTMLInputElement;
|
||||
try {
|
||||
index = parseInt(target.dataset.index!);
|
||||
val = parseFloat(target.value);
|
||||
} catch (e) {
|
||||
// reset the edit
|
||||
console.log(e);
|
||||
window.VM!.update();
|
||||
return;
|
||||
}
|
||||
window.VM!.setRegister(index, val);
|
||||
}
|
||||
|
||||
update(ic: DeviceRef) {
|
||||
if (ic) {
|
||||
const registers = ic.registers;
|
||||
if (registers) {
|
||||
for (var i = 0; i < registers.length; i++) {
|
||||
this.regCells[i].input.value = registers[i].toString();
|
||||
}
|
||||
}
|
||||
const aliases = ic.aliases;
|
||||
if (aliases) {
|
||||
this.ic_aliases = new Map();
|
||||
aliases.forEach((target, alias, _map) => {
|
||||
if (
|
||||
"RegisterSpec" in target &&
|
||||
target.RegisterSpec.indirection == 0
|
||||
) {
|
||||
const index = target.RegisterSpec.target;
|
||||
this.ic_aliases.set(alias, index);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// this.updateAliases();
|
||||
}
|
||||
|
||||
updateAliases() {
|
||||
const aliases = new Map([
|
||||
...Array.from(this.default_aliases),
|
||||
...Array.from(this.ic_aliases),
|
||||
]);
|
||||
const labels = new Map<number, string[]>();
|
||||
for (const [alias, target] of aliases) {
|
||||
if (labels.hasOwnProperty(target)) {
|
||||
labels.get(target)!.push(alias);
|
||||
} else {
|
||||
labels.set(target, [alias]);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [index, label_list] of labels) {
|
||||
this.regCells[index].aliasesLabel.innerText = label_list.join(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VMStackUI {
|
||||
ui: VirtualMachineUI;
|
||||
tbl: HTMLDivElement;
|
||||
stackCells: {
|
||||
cell: HTMLDivElement;
|
||||
nameLabel: HTMLSpanElement;
|
||||
input: HTMLInputElement;
|
||||
}[];
|
||||
constructor(ui: VirtualMachineUI) {
|
||||
this.ui = ui;
|
||||
const stackDom = document.getElementById("vmActiveStack")!;
|
||||
this.tbl = document.createElement("div");
|
||||
this.tbl.classList.add(
|
||||
"d-flex",
|
||||
"flex-wrap",
|
||||
"justify-content-start",
|
||||
"align-items-end",
|
||||
);
|
||||
this.stackCells = [];
|
||||
for (var i = 0; i < 512; i++) {
|
||||
const container = document.createElement("div");
|
||||
container.classList.add("vm_stack_cel", "align-that-stretch");
|
||||
const cell = document.createElement("div");
|
||||
cell.classList.add("input-group", "input-group-sm");
|
||||
const nameLabel = document.createElement("span");
|
||||
nameLabel.innerText = `${i}`;
|
||||
nameLabel.classList.add("input-group-text");
|
||||
cell.appendChild(nameLabel);
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.value = (0).toString();
|
||||
input.dataset.index = i.toString();
|
||||
cell.appendChild(input);
|
||||
|
||||
this.stackCells.push({
|
||||
cell,
|
||||
nameLabel,
|
||||
input,
|
||||
});
|
||||
container.appendChild(cell);
|
||||
this.tbl.appendChild(container);
|
||||
}
|
||||
this.stackCells.forEach((cell) => {
|
||||
cell.input.addEventListener("change", this.onCellUpdate);
|
||||
});
|
||||
stackDom.appendChild(this.tbl);
|
||||
}
|
||||
|
||||
onCellUpdate(e: Event) {
|
||||
let index: number;
|
||||
let val: number;
|
||||
let target = e.target as HTMLInputElement;
|
||||
try {
|
||||
index = parseInt(target.dataset.index!);
|
||||
val = parseFloat(target.value);
|
||||
} catch (e) {
|
||||
// reset the edit
|
||||
window.VM!.update();
|
||||
return;
|
||||
}
|
||||
window.VM!.setStack(index, val);
|
||||
}
|
||||
|
||||
update(ic: DeviceRef) {
|
||||
if (ic) {
|
||||
const stack = ic.stack;
|
||||
const sp = ic.registers![16];
|
||||
if (stack) {
|
||||
for (var i = 0; i < stack.length; i++) {
|
||||
this.stackCells[i].input.value = stack[i].toString();
|
||||
if (i == sp) {
|
||||
this.stackCells[i].nameLabel.classList.add("stack_pointer");
|
||||
} else {
|
||||
this.stackCells[i].nameLabel.classList.remove("stack_pointer");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { VirtualMachine, VirtualMachineUI, DeviceDB };
|
||||
90
www/src/ts/virtual_machine/registers.ts
Normal file
90
www/src/ts/virtual_machine/registers.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { html, css } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { defaultCss } from "../components";
|
||||
import { VMActiveIC } from "./base_device";
|
||||
|
||||
import "@shoelace-style/shoelace/dist/components/card/card.js";
|
||||
import "@shoelace-style/shoelace/dist/components/icon/icon.js";
|
||||
import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js";
|
||||
import "@shoelace-style/shoelace/dist/components/input/input.js";
|
||||
import { RegisterSpec } from "ic10emu_wasm";
|
||||
|
||||
@customElement("vm-ic-registers")
|
||||
export class VMICRegisters extends VMActiveIC {
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
:host {
|
||||
}
|
||||
.card {
|
||||
--padding: 0.5rem;
|
||||
--sl-input-font-size-small: 0.75em
|
||||
}
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
max-height: 8rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.reg-input {
|
||||
width: 10rem;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
static defaultAliases: [string, number][] = [
|
||||
["sp", 16],
|
||||
["ra", 17],
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
// const inputTypeFromVal = (val: number) => { if (val === Number.NEGATIVE_INFINITY || val === Number.POSITIVE_INFINITY || Number.isNaN(val)) { return "text"; } else { return "number"; } };
|
||||
const displayVal = (val: number) => {
|
||||
if (Number.POSITIVE_INFINITY === val) {
|
||||
return "∞";
|
||||
} else if (Number.NEGATIVE_INFINITY === val) {
|
||||
return "-∞";
|
||||
} else {
|
||||
return val.toString();
|
||||
}
|
||||
};
|
||||
const validation =
|
||||
"[-+]?(([0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?)|((\\.[0-9]+)([eE][+-]?[0-9]+)?)|([iI][nN][fF][iI][nN][iI][tT][yY]))";
|
||||
const registerAliases: [string, number][] = (
|
||||
(
|
||||
[...(this.aliases ?? [])].filter(
|
||||
([_alias, target]) =>
|
||||
"RegisterSpec" in target && target.RegisterSpec.indirection === 0,
|
||||
) as [string, RegisterSpec][]
|
||||
).map(([alias, target]) => [alias, target.RegisterSpec.target]) as [
|
||||
string,
|
||||
number,
|
||||
][]
|
||||
).concat(VMICRegisters.defaultAliases);
|
||||
return html`
|
||||
<sl-card class="card">
|
||||
<div class="card-body">
|
||||
${this.registers?.map((val, index) => {
|
||||
const aliases = registerAliases
|
||||
.filter(([_alias, target]) => index === target)
|
||||
.map(([alias, _target]) => alias);
|
||||
return html` <sl-input
|
||||
type="text"
|
||||
value="${displayVal(val)}"
|
||||
pattern="${validation}"
|
||||
size="small"
|
||||
class="reg-input"
|
||||
>
|
||||
<span slot="prefix">r${index}</span>
|
||||
<span slot="suffix">${aliases.join(", ")}</span>
|
||||
</sl-input>`;
|
||||
})}
|
||||
</div>
|
||||
</sl-card>
|
||||
`;
|
||||
}
|
||||
}
|
||||
82
www/src/ts/virtual_machine/stack.ts
Normal file
82
www/src/ts/virtual_machine/stack.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { html, css } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { defaultCss } from "../components";
|
||||
import { VMActiveIC } from "./base_device";
|
||||
|
||||
import "@shoelace-style/shoelace/dist/components/card/card.js";
|
||||
import "@shoelace-style/shoelace/dist/components/icon/icon.js";
|
||||
import "@shoelace-style/shoelace/dist/components/tooltip/tooltip.js";
|
||||
import "@shoelace-style/shoelace/dist/components/input/input.js";
|
||||
import { RegisterSpec } from "ic10emu_wasm";
|
||||
|
||||
@customElement("vm-ic-stack")
|
||||
export class VMICStack extends VMActiveIC {
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
:host {
|
||||
}
|
||||
.card {
|
||||
--padding: 0.5rem;
|
||||
--sl-input-font-size-small: 0.75em;
|
||||
}
|
||||
.card-body {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
max-height: 15rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.stack-input {
|
||||
width: 8rem;
|
||||
}
|
||||
.stack-pointer::part(prefix) {
|
||||
background: rgb(121, 82, 179);
|
||||
}
|
||||
sl-input::part(prefix) {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const displayVal = (val: number) => {
|
||||
if (Number.POSITIVE_INFINITY === val) {
|
||||
return "∞";
|
||||
} else if (Number.NEGATIVE_INFINITY === val) {
|
||||
return "-∞";
|
||||
} else {
|
||||
return val.toString();
|
||||
}
|
||||
};
|
||||
const validation =
|
||||
"[-+]?(([0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?)|((\\.[0-9]+)([eE][+-]?[0-9]+)?)|([iI][nN][fF][iI][nN][iI][tT][yY]))";
|
||||
const sp = this.registers![16];
|
||||
|
||||
|
||||
return html`
|
||||
<sl-card class="card">
|
||||
<div class="card-body">
|
||||
${this.stack?.map((val, index) => {
|
||||
return html` <sl-input
|
||||
type="text"
|
||||
value="${displayVal(val)}"
|
||||
pattern="${validation}"
|
||||
size="small"
|
||||
class="stack-input ${sp === index ? "stack-pointer" : "" }"
|
||||
>
|
||||
<span
|
||||
slot="prefix"
|
||||
>
|
||||
${index}
|
||||
</span>
|
||||
</sl-input>`;
|
||||
})}
|
||||
</div>
|
||||
</sl-card>
|
||||
`;
|
||||
}
|
||||
}
|
||||
43
www/src/ts/virtual_machine/ui.ts
Normal file
43
www/src/ts/virtual_machine/ui.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { HTMLTemplateResult, html, css } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "../components";
|
||||
import "@shoelace-style/shoelace/dist/components/details/details.js";
|
||||
|
||||
import "./controls";
|
||||
import "./registers";
|
||||
import "./stack";
|
||||
|
||||
@customElement("vm-ui")
|
||||
export class VMUI extends BaseElement {
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
sl-details {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
sl-details::part(header) {
|
||||
padding: 0.3rem;
|
||||
}
|
||||
sl-details::part(content) {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<vm-ic-controls></vm-ic-controls>
|
||||
<sl-details summary="Registers">
|
||||
<vm-ic-registers></vm-ic-registers>
|
||||
</sl-details>
|
||||
<sl-details summary="Stack">
|
||||
<vm-ic-stack></vm-ic-stack>
|
||||
</sl-details>
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user