Device Cards

brings the rework inline were last efforts left off

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers
2024-04-09 21:01:12 -07:00
parent 362695af4b
commit 3cdcc742b9
12 changed files with 766 additions and 529 deletions

View File

@@ -149,7 +149,6 @@ export class VMActiveIC extends VMBaseDevice {
"session-active-ic",
this._handleActiveIC.bind(this),
);
this.updateIC();
return root;
}

View File

@@ -10,6 +10,10 @@ 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";
import "@shoelace-style/shoelace/dist/components/select/select.js";
import "@shoelace-style/shoelace/dist/components/badge/badge.js";
import "@shoelace-style/shoelace/dist/components/option/option.js";
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.js";
@customElement("vm-ic-controls")
export class VMICControls extends VMActiveIC {
@@ -41,6 +45,19 @@ export class VMICControls extends VMActiveIC {
sl-divider {
--spacing: 0.25rem;
}
sl-button[variant="success"] {
/* Changes the success theme color to purple using primitives */
--sl-color-success-600: var(--sl-color-purple-700);
}
sl-button[variant="primary"] {
/* Changes the success theme color to purple using primitives */
--sl-color-primary-600: var(--sl-color-cyan-600);
}
sl-button[variant="warning"] {
/* Changes the success theme color to purple using primitives */
--sl-color-warning-600: var(--sl-color-amber-600);
}
`,
];
@@ -50,6 +67,7 @@ export class VMICControls extends VMActiveIC {
}
protected render() {
const ics = Array.from(window.VM!.ics);
return html`
<sl-card class="card">
<div class="controls" slot="header">
@@ -96,10 +114,19 @@ export class VMICControls extends VMActiveIC {
</sl-tooltip>
</sl-button-group>
<div class="device-id">
Device:
${this.deviceID}${this.name ?? this.prefabName
? ` : ${this.name ?? this.prefabName}`
: ""}
<sl-select
hoist
placement="bottom"
value="${this.deviceID}"
@sl-change=${this._handleChangeActiveIC}
>
${ics.map(
([id, device], _index) =>
html`<sl-option value=${id}>
Device:${id} ${device.name ?? device.prefabName}
</sl-option>`,
)}
</sl-select>
</div>
</div>
<div class="stats">
@@ -121,15 +148,15 @@ export class VMICControls extends VMActiveIC {
<div class="vstack">
<span>Errors</span>
${this.errors.map(
(err) =>
html`<div class="hstack">
(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>
@@ -145,4 +172,10 @@ export class VMICControls extends VMActiveIC {
_handleResetClick() {
window.VM?.reset();
}
_handleChangeActiveIC(e: CustomEvent) {
const select = e.target as SlSelect;
const icId = parseInt(select.value as string);
window.App!.session.activeIC = icId;
}
}

View File

@@ -1,398 +1,359 @@
import { Offcanvas } from "bootstrap";
import { VirtualMachine, VirtualMachineUI } from ".";
import { DeviceRef, VM } from "ic10emu_wasm";
import { Slot } from "ic10emu_wasm";
import { html, css, HTMLTemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import { BaseElement, defaultCss } from "../components";
import { VMBaseDevice } 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 "@shoelace-style/shoelace/dist/components/details/details.js";
import "@shoelace-style/shoelace/dist/components/tab/tab.js";
import "@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.js";
import "@shoelace-style/shoelace/dist/components/tab-group/tab-group.js";
import "@shoelace-style/shoelace/dist/components/copy-button/copy-button.js";
import "@shoelace-style/shoelace/dist/components/select/select.js";
import "@shoelace-style/shoelace/dist/components/badge/badge.js";
import "@shoelace-style/shoelace/dist/components/option/option.js";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
import { parseNumber, structuralEqual } from "../utils";
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.js";
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;
@customElement("vm-device-card")
export class VMDeviceCard extends VMBaseDevice {
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);
}
static styles = [
...defaultCss,
css`
:host {
display: block;
box-sizing: border-box;
}
.card {
width: 100%;
box-sizing: border-box;
}
.image {
width: 4rem;
height: 4rem;
}
.header {
display: flex;
flex-direction: row;
}
.header-name {
display: flex;
flex-direction: row;
width: 100%;
flex-grow: 1;
align-items: center;
flex-wrap: wrap;
}
// .device-name {
// box-sizing: border-box;
// width: 8rem;
// }
// .device-name-hash {
// box-sizing: border-box;
// width: 5rem;
// }
sl-divider {
--spacing: 0.25rem;
}
sl-button[variant="success"] {
/* Changes the success theme color to purple using primitives */
--sl-color-success-600: var(--sl-color-purple-700);
}
sl-button[variant="primary"] {
/* Changes the success theme color to purple using primitives */
--sl-color-primary-600: var(--sl-color-cyan-600);
}
sl-button[variant="warning"] {
/* Changes the success theme color to purple using primitives */
--sl-color-warning-600: var(--sl-color-amber-600);
}
sl-tab-group {
margin-left: 1rem;
margin-right: 1rem;
--indicator-color: var(--sl-color-purple-600);
--sl-color-primary-600: var(--sl-color-purple-600);
}
`,
];
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;
renderHeader(): HTMLTemplateResult {
const activeIc = window.VM?.activeIC;
const badges: HTMLTemplateResult[] = [];
if (this.deviceID == activeIc?.id) {
badges.push(html`<sl-badge variant="primary" pill pulse>db</sl-badge>`);
}
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);
activeIc?.pins?.forEach((id, index) => {
if (this.deviceID == id) {
badges.push(html`<sl-badge variant="success" pill></sl-badge>`);
}
}, this);
// TODO Reagents
return html`
<img
class="image"
src="img/stationpedia/${this.prefabName}.png"
@onerr=${this.onImageErr}
/>
<div class="header-name">
<sl-input
id="vmDeviceCard${this.deviceID}Name"
class="device-name"
size="small"
pill
placeholder="${this.prefabName}"
@sl-change=${this._handleChangeName}
>
<span slot="prefix">Device ${this.deviceID}</span>
<sl-copy-button
slot="suffix"
from="vmDeviceCard${this.deviceID}Name.value"
></sl-copy-button>
</sl-input>
<sl-input
id="vmDeviceCard${this.deviceID}NameHash"
size="small"
pill
class="device-name-hash"
value="${this.nameHash}"
disabled
>
<span slot="prefix">Name Hash</span>
<sl-copy-button
slot="suffix"
from="vmDeviceCard${this.deviceID}NameHash.value"
></sl-copy-button>
</sl-input>
${badges.map((badge) => badge)}
</div>
`;
}
destroy() {
this.root.remove();
renderFields(): HTMLTemplateResult {
const fields = Array.from(this.fields);
return html`
${fields.map(([name, field], _index, _fields) => {
return html` <sl-input
key="${name}"
value="${field.value}"
@sl-change=${this._handleChangeField}
>
<span slot="prefix">${name}</span>
<span slot="suffix">${field.field_type}</span>
</sl-input>`;
})}
`;
}
renderSlot(slot: Slot, slotIndex: number): HTMLTemplateResult {
const fields = Array.from(slot.fields);
return html`
<sl-card class="slot-card">
<span slot="header" class="slot-header"
>${slotIndex} : ${slot.typ}</span
>
<div class="slot-fields">
${fields.map(
([name, field], _index, _fields) => html`
<sl-input
slotIndex=${slotIndex}
key="${name}"
value="${field.value}"
@sl-change=${this._handleChangeSlotField}
>
<span slot="prefix">${name}</span>
<span slot="suffix">${field.field_type}</span>
</sl-input>
`,
)}
</div>
</sl-card>
`;
}
renderSlots(): HTMLTemplateResult {
return html`
<div clas="slots">
${this.slots.map((slot, index, _slots) => this.renderSlot(slot, index))}
</div>
`;
}
renderReagents(): HTMLTemplateResult {
return html``;
}
renderNetworks(): HTMLTemplateResult {
const vmNetworks = window.VM!.networks;
return html`
<div class="networks">
${this.connections.map((connection, index, _conns) => {
const conn =
typeof connection === "object" ? connection.CableNetwork : null;
return html`
<sl-select
hoist
placement="top"
clearable
key=${index}
value=${conn}
?disabled=${conn === null}
@sl-change=${this._handleChangeConnection}
>
<span slot="prefix">Connection:${index}</span>
${vmNetworks.map(
(net) =>
html`<sl-option value=${net}>Network ${net}</sl-option>`,
)}
</sl-select>
`;
})}
</div>
`;
}
renderPins(): HTMLTemplateResult {
const pins = this.pins;
const visibleDevices = window.VM!.visibleDevices(this.deviceID);
return html`
<div class="pins">
${pins?.map(
(pin, index) =>
html`<sl-select
hoist
placement="top"
clearable
key=${index}
value=${pin}
@sl-change=${this._handleChangePin}
>
<span slot="prefix">d${index}</span>
${visibleDevices.map(
(device, _index) =>
html`<sl-option value=${device.id}>
Device ${device.id} : ${device.name ?? device.prefabName}
</sl-option>`,
)}
</sl-select>`,
)}
</div>
`;
}
protected render(): HTMLTemplateResult {
return html`
<sl-card class="card">
<div class="header" slot="header">${this.renderHeader()}</div>
<sl-tab-group>
<sl-tab slot="nav" panel="fields">Fields</sl-tab>
<sl-tab slot="nav" panel="slots">Slots</sl-tab>
<sl-tab slot="nav" panel="reagents" disabled>Reagents</sl-tab>
<sl-tab slot="nav" panel="networks">Networks</sl-tab>
<sl-tab slot="nav" panel="pins" ?disabled=${!this.pins}>Pins</sl-tab>
<sl-tab-panel name="fields">${this.renderFields()}</sl-tab-panel>
<sl-tab-panel name="slots">${this.renderSlots()}</sl-tab-panel>
<sl-tab-panel name="reagents">${this.renderReagents()}</sl-tab-panel>
<sl-tab-panel name="networks">${this.renderNetworks()}</sl-tab-panel>
<sl-tab-panel name="pins">${this.renderPins()}</sl-tab-panel>
</sl-tab-group>
</sl-card>
`;
}
_handleChangeName(e: CustomEvent) {
const input = e.target as SlInput;
this.device.setName(input.value);
this.updateDevice();
}
_handleChangeField(e: CustomEvent) {
const input = e.target as SlInput;
const field = input.getAttribute("key")!;
const val = parseNumber(input.value);
this.device.setField(field, val);
this.updateDevice();
}
_handleChangeSlotField(e: CustomEvent) {
const input = e.target as SlInput;
const slot = parseInt(input.getAttribute("slotIndex")!);
const field = input.getAttribute("key")!;
const val = parseNumber(input.value);
this.device.setSlotField(slot, field, val);
this.updateDevice();
}
_handleChangeConnection(e: CustomEvent) {
const select = e.target as SlSelect;
const conn = parseInt(select.getAttribute("key")!);
const last = this.device.connections[conn];
const val = select.value ? parseInt(select.value as string) : undefined;
if (typeof last === "object" && typeof last.CableNetwork === "number") {
// is there no other connection to the previous network?
if (
!this.device.connections.some((other_conn, index) => {
structuralEqual(last, other_conn) && index !== conn;
})
) {
this.device.removeDeviceFromNetwork(last.CableNetwork);
}
}
if (typeof val !== "undefined") {
this.device.addDeviceToNetwork(conn, val);
} else {
this.device.setConnection(conn, val);
}
this.updateDevice();
}
_handleChangePin(e: CustomEvent) {
const select = e.target as SlSelect;
const pin = parseInt(select.getAttribute("key")!);
const val = select.value ? parseInt(select.value as string) : undefined;
this.device.setPin(pin, val);
this.updateDevice();
}
}
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);
@customElement("vm-device-list")
export class VMDeviceList extends BaseElement {
@state() accessor devices: number[];
this.container.appendChild(this.root);
constructor() {
super();
this.devices = window.VM!.deviceIds;
}
destroy () {
this.root.remove();
connectedCallback(): void {
const root = super.connectedCallback();
window.VM?.addEventListener(
"vm-devices-update",
this._handleDevicesUpdate.bind(this),
);
return root;
}
update(_active_ic: DeviceRef) {
this.input.value = this.device.fields.get(this.field)?.value.toString();
_handleDevicesUpdate(e: CustomEvent) {
const ids = e.detail;
if (!structuralEqual(this.devices, ids)) {
this.devices = ids;
}
}
protected render(): HTMLTemplateResult {
return html`
<div class="device-list">
${this.devices.map(
(id, _index, _ids) =>
html`<vm-device-card .deviceID=${id}></vm-device-card>`,
)}
</div>
`;
}
}
export { VMDeviceUI };

View File

@@ -51,25 +51,49 @@ class VirtualMachine extends EventTarget {
return this._devices;
}
get deviceIds() {
return Array.from(this.ic10vm.devices);
}
get ics() {
return this._ics;
}
get icIds() {
return Array.from(this.ic10vm.ics);
}
get networks() {
return Array.from(this.ic10vm.networks);
}
get defaultNetwork() {
return this.ic10vm.defaultNetwork;
}
get activeIC() {
return this._ics.get(window.App!.session.activeIC);
}
visibleDevices(source: number) {
const ids = Array.from(this.ic10vm.visibleDevices(source));
return ids.map((id, _index) => this._devices.get(id)!);
}
updateDevices() {
var update_flag = false;
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)!);
update_flag = true;
}
}
for (const id of this._devices.keys()) {
if (!device_ids.includes(id)) {
this._devices.get(id)!.free();
this._devices.delete(id);
update_flag = true;
}
}
@@ -77,14 +101,21 @@ class VirtualMachine extends EventTarget {
for (const id of ics) {
if (!this._ics.has(id)) {
this._ics.set(id, this._devices.get(id)!);
update_flag = true;
}
}
for (const id of this._ics.keys()) {
if (!ics.includes(id)) {
this._ics.get(id)!.free();
this._ics.delete(id);
update_flag = true;
}
}
if (update_flag) {
this.dispatchEvent(
new CustomEvent("vm-devices-update", { detail: device_ids }),
);
}
}
updateCode() {
@@ -190,6 +221,7 @@ class VirtualMachine extends EventTarget {
this.db = db;
console.log("Loaded Device Database", this.db);
}
}
class VirtualMachineUI {

View File

@@ -57,7 +57,7 @@ export class VMICRegisters extends VMActiveIC {
}
};
const validation =
"[-+]?(([0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?)|((\\.[0-9]+)([eE][+-]?[0-9]+)?)|([iI][nN][fF][iI][nN][iI][tT][yY]))";
"[\\-+]?(([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(
@@ -73,10 +73,10 @@ export class VMICRegisters extends VMActiveIC {
<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`
const aliases = registerAliases
.filter(([_alias, target]) => index === target)
.map(([alias, _target]) => alias);
return html`
<sl-tooltip placement="left" class="tooltip">
<div slot="content">
<strong>Regster r${index}</strong> Aliases:
@@ -96,7 +96,7 @@ export class VMICRegisters extends VMActiveIC {
</sl-input>
</sl-tooltip>
`;
})}
})}
</div>
</sl-card>
`;

View File

@@ -7,7 +7,6 @@ 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";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
@customElement("vm-ic-stack")
@@ -54,7 +53,7 @@ export class VMICStack extends VMActiveIC {
}
};
const validation =
"[-+]?(([0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?)|((\\.[0-9]+)([eE][+-]?[0-9]+)?)|([iI][nN][fF][iI][nN][iI][tT][yY]))";
"[\\-+]?(([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`

View File

@@ -2,19 +2,25 @@ 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 "@shoelace-style/shoelace/dist/components/tab/tab.js";
import "@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.js";
import "@shoelace-style/shoelace/dist/components/tab-group/tab-group.js";
import "./controls";
import "./registers";
import "./stack";
import "./device";
@customElement("vm-ui")
export class VMUI extends BaseElement {
static styles = [
...defaultCss,
css`
sl-details {
sl-tab-group {
margin-left: 1rem;
margin-right: 1rem;
--indicator-color: var(--sl-color-purple-600);
--sl-color-primary-600: var(--sl-color-purple-600);
}
sl-details::part(header) {
padding: 0.3rem;
@@ -42,12 +48,21 @@ export class VMUI extends BaseElement {
return html`
<div class="side-container">
<vm-ic-controls></vm-ic-controls>
<sl-details summary="Registers" open>
<vm-ic-registers></vm-ic-registers>
</sl-details>
<sl-details summary="Stack">
<vm-ic-stack></vm-ic-stack>
</sl-details>
<sl-tab-group>
<sl-tab slot="nav" panel="active-ic">Active IC</sl-tab>
<sl-tab slot="nav" panel="devices">Devices</sl-tab>
<sl-tab-panel name="active-ic">
<sl-details summary="Registers" open>
<vm-ic-registers></vm-ic-registers>
</sl-details>
<sl-details summary="Stack">
<vm-ic-stack></vm-ic-stack>
</sl-details>
</sl-tab-panel>
<sl-tab-panel name="devices">
<vm-device-list></vm-device-list>
</sl-tab-panel>
</sl-tab-group>
</div>
`;
}