diff --git a/www/src/ts/components/details.ts b/www/src/ts/components/details.ts new file mode 100644 index 0000000..6f34838 --- /dev/null +++ b/www/src/ts/components/details.ts @@ -0,0 +1,94 @@ +import { html, css, HTMLTemplateResult, PropertyValueMap } from "lit"; +import { customElement, query, state } from "lit/decorators.js"; +import { classMap } from "lit/directives/class-map.js"; +import SlDetails from "@shoelace-style/shoelace/dist/components/details/details.js"; + +@customElement("ic10-details") +export class IC10Details extends SlDetails { + @query(".details__summary-icon") accessor summaryIcon: HTMLSpanElement; + + constructor() { + super(); + } + + private handleSummaryIconClick(event: MouseEvent) { + event.preventDefault(); + + if (!this.disabled) { + if (this.open) { + this.hide(); + } else { + this.show(); + } + this.header.focus(); + } + } + + private handleSummaryIconKeyDown(event: KeyboardEvent) { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + + if (this.open) { + this.hide(); + } else { + this.show(); + } + } + + if (event.key === "ArrowUp" || event.key === "ArrowLeft") { + event.preventDefault(); + this.hide(); + } + + if (event.key === "ArrowDown" || event.key === "ArrowRight") { + event.preventDefault(); + this.show(); + } + } + + render() { + return html` +
+ + ${this.summary} + + + + + + + + + + + +
+ +
+
+ `; + } +} diff --git a/www/src/ts/components/index.ts b/www/src/ts/components/index.ts index ed07c34..e846885 100644 --- a/www/src/ts/components/index.ts +++ b/www/src/ts/components/index.ts @@ -1,3 +1,3 @@ import { BaseElement, defaultCss } from './base'; - -export { BaseElement, defaultCss } +import { IC10Details } from './details'; +export { BaseElement, defaultCss, IC10Details } diff --git a/www/src/ts/virtual_machine/base_device.ts b/www/src/ts/virtual_machine/base_device.ts index d4a931a..6cd1dbc 100644 --- a/www/src/ts/virtual_machine/base_device.ts +++ b/www/src/ts/virtual_machine/base_device.ts @@ -1,5 +1,5 @@ import { property, state } from "lit/decorators.js"; -import { BaseElement } from "../components"; + import { DeviceRef, Fields, @@ -14,151 +14,179 @@ import { Pins, } from "ic10emu_wasm"; import { structuralEqual } from "../utils"; +import { LitElement } from "lit"; +import { BaseElement } from "../components/base"; -export class VMBaseDevice extends BaseElement { - @property({ type: Number }) accessor deviceID: number; - @state() protected accessor device: DeviceRef; +type Constructor = new (...args: any[]) => T; - @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; +export declare class VMDeviceMixinInterface { + deviceID: number; + device: DeviceRef; + name: string | null; + nameHash: number | null; + prefabName: string | null; + fields: Fields; + slots: Slot[]; + reagents: Reagents; + connections: Connection[]; + icIP: number; + icOpCount: number; + icState: string; + errors: ICError[]; + registers: Registers | null; + stack: Stack | null; + aliases: Aliases | null; + defines: Defines | null; + pins: Pins | null; + _handleDeviceModified(e: CustomEvent): void; + updateDevice(): void; + updateIC(): void; +} - constructor() { - super(); - this.name = null; - this.nameHash = null; +export const VMDeviceMixin = >( + superClass: T, +) => { + class VMDeviceMixinClass extends superClass { + @property({ type: Number }) accessor deviceID: number; + @state() accessor device: DeviceRef; + + @state() accessor name: string | null = null; + @state() accessor nameHash: number | null = 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; + + 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; + } + } } + return VMDeviceMixinClass as Constructor & T; +}; - 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; - } +export const VMActiveICMixin = >(superClass: T) => { + class VMActiveICMixinClass extends VMDeviceMixin(superClass) { + constructor() { + super(); + this.deviceID = window.App!.session.activeIC; + } - _handleDeviceModified(e: CustomEvent) { - const id = e.detail; - if (this.deviceID === id) { + 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), + ); + 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(); } - } - - 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), - ); - 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(); - } - + }; + return VMActiveICMixinClass as Constructor & T; } diff --git a/www/src/ts/virtual_machine/controls.ts b/www/src/ts/virtual_machine/controls.ts index 29806e9..6ce2257 100644 --- a/www/src/ts/virtual_machine/controls.ts +++ b/www/src/ts/virtual_machine/controls.ts @@ -1,7 +1,7 @@ import { html, css } from "lit"; import { customElement } from "lit/decorators.js"; -import { defaultCss } from "../components"; -import { VMActiveIC } from "./base_device"; +import { BaseElement, defaultCss } from "../components"; +import { VMActiveICMixin } from "./base_device"; import { structuralEqual } from "../utils"; import "@shoelace-style/shoelace/dist/components/card/card.js"; @@ -16,7 +16,7 @@ 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 { +export class VMICControls extends VMActiveICMixin(BaseElement) { static styles = [ ...defaultCss, css` @@ -121,11 +121,11 @@ export class VMICControls extends VMActiveIC { @sl-change=${this._handleChangeActiveIC} > ${ics.map( - ([id, device], _index) => - html` + ([id, device], _index) => + html` Device:${id} ${device.name ?? device.prefabName} `, - )} + )} @@ -148,15 +148,15 @@ export class VMICControls extends VMActiveIC {
Errors ${this.errors.map( - (err) => - html`
+ (err) => + html`
Line: ${err.ParseError.line} - ${err.ParseError.start}:${err.ParseError.end} ${err.ParseError.msg}
`, - )} + )}
diff --git a/www/src/ts/virtual_machine/device.ts b/www/src/ts/virtual_machine/device.ts index c420bc3..93138fd 100644 --- a/www/src/ts/virtual_machine/device.ts +++ b/www/src/ts/virtual_machine/device.ts @@ -1,8 +1,8 @@ import { Slot } from "ic10emu_wasm"; -import { html, css, HTMLTemplateResult } from "lit"; +import { html, css, HTMLTemplateResult, PropertyValueMap } from "lit"; import { customElement, state } from "lit/decorators.js"; -import { BaseElement, defaultCss } from "../components"; -import { VMBaseDevice } from "./base_device"; +import { BaseElement, defaultCss, IC10Details } from "../components"; +import { VMDeviceMixin } from "./base_device"; import "@shoelace-style/shoelace/dist/components/card/card.js"; import "@shoelace-style/shoelace/dist/components/icon/icon.js"; @@ -19,9 +19,10 @@ 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"; +import SlDetails from "@shoelace-style/shoelace/dist/components/details/details.js"; @customElement("vm-device-card") -export class VMDeviceCard extends VMBaseDevice { +export class VMDeviceCard extends VMDeviceMixin(BaseElement) { image_err: boolean; static styles = [ @@ -51,14 +52,12 @@ export class VMDeviceCard extends VMBaseDevice { align-items: center; flex-wrap: wrap; } - // .device-name { - // box-sizing: border-box; - // width: 8rem; - // } - // .device-name-hash { - // box-sizing: border-box; - // width: 5rem; - // } + .device-name::part(input) { + width: 10rem; + } + .device-name-hash::part(input) { + width: 7rem; + } sl-divider { --spacing: 0.25rem; } @@ -129,7 +128,7 @@ export class VMDeviceCard extends VMBaseDevice { value="${this.nameHash}" disabled > - Name Hash + Hash `; } + renderSlots(): HTMLTemplateResult { return html`
@@ -202,9 +202,11 @@ export class VMDeviceCard extends VMBaseDevice {
`; } + renderReagents(): HTMLTemplateResult { return html``; } + renderNetworks(): HTMLTemplateResult { const vmNetworks = window.VM!.networks; return html` @@ -260,10 +262,11 @@ export class VMDeviceCard extends VMBaseDevice { `; } - protected render(): HTMLTemplateResult { + + render(): HTMLTemplateResult { return html` - -
${this.renderHeader()}
+ +
${this.renderHeader()}
Fields Slots @@ -277,7 +280,7 @@ export class VMDeviceCard extends VMBaseDevice { ${this.renderNetworks()} ${this.renderPins()} -
+ `; } @@ -341,6 +344,19 @@ export class VMDeviceCard extends VMBaseDevice { export class VMDeviceList extends BaseElement { @state() accessor devices: number[]; + static styles = [ + ...defaultCss, + css` + .device-list { + display: flex; + flex-direction: row; + flex-wrap: wrap; + } + .device-list-card { + } + `, + ]; + constructor() { super(); this.devices = window.VM!.deviceIds; @@ -367,7 +383,10 @@ export class VMDeviceList extends BaseElement {
${this.devices.map( (id, _index, _ids) => - html``, + html``, )}
`; diff --git a/www/src/ts/virtual_machine/registers.ts b/www/src/ts/virtual_machine/registers.ts index a6d1a95..29c4586 100644 --- a/www/src/ts/virtual_machine/registers.ts +++ b/www/src/ts/virtual_machine/registers.ts @@ -1,7 +1,7 @@ import { html, css } from "lit"; import { customElement } from "lit/decorators.js"; -import { defaultCss } from "../components"; -import { VMActiveIC } from "./base_device"; +import { BaseElement, defaultCss } from "../components"; +import { VMActiveICMixin } from "./base_device"; import "@shoelace-style/shoelace/dist/components/card/card.js"; import "@shoelace-style/shoelace/dist/components/icon/icon.js"; @@ -12,7 +12,7 @@ import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js"; import { parseNumber } from "../utils"; @customElement("vm-ic-registers") -export class VMICRegisters extends VMActiveIC { +export class VMICRegisters extends VMActiveICMixin(BaseElement) { static styles = [ ...defaultCss, css` @@ -57,8 +57,6 @@ export class VMICRegisters extends VMActiveIC { 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( @@ -74,19 +72,18 @@ export class VMICRegisters extends VMActiveIC {
${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`
- Regster r${index} Aliases: + Register r${index} Aliases: ${aliases.join(", ") || "None"}
`; - })} + })}
`; @@ -106,7 +103,7 @@ export class VMICRegisters extends VMActiveIC { _handleCellChange(e: Event) { const input = e.target as SlInput; const index = parseInt(input.getAttribute("key")!); - const val = parseNumber(input.value) + const val = parseNumber(input.value); window.VM!.setRegister(index, val); } } diff --git a/www/src/ts/virtual_machine/stack.ts b/www/src/ts/virtual_machine/stack.ts index bff6900..ed795c4 100644 --- a/www/src/ts/virtual_machine/stack.ts +++ b/www/src/ts/virtual_machine/stack.ts @@ -1,7 +1,7 @@ import { html, css } from "lit"; import { customElement } from "lit/decorators.js"; -import { defaultCss } from "../components"; -import { VMActiveIC } from "./base_device"; +import { BaseElement, defaultCss } from "../components"; +import { VMActiveICMixin } from "./base_device"; import "@shoelace-style/shoelace/dist/components/card/card.js"; import "@shoelace-style/shoelace/dist/components/icon/icon.js"; @@ -11,7 +11,7 @@ import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js"; import { parseNumber } from "../utils"; @customElement("vm-ic-stack") -export class VMICStack extends VMActiveIC { +export class VMICStack extends VMActiveICMixin(BaseElement) { static styles = [ ...defaultCss, css` @@ -53,8 +53,6 @@ export class VMICStack extends VMActiveIC { 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` @@ -62,17 +60,14 @@ export class VMICStack extends VMActiveIC {
${this.stack?.map((val, index) => { return html` - +
- ${sp === index ? html`Stack Pointer` : ""} - Address ${index} + ${sp === index ? html`Stack Pointer` : ""} + Address ${index}