From 18095d0136fd252f40dbdaa7f9ff3399334206ac Mon Sep 17 00:00:00 2001 From: Rachel Powers <508861+Ryex@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:13:41 -0700 Subject: [PATCH] refine UI events with multiple devices in play --- www/src/ts/virtual_machine/base_device.ts | 25 +- www/src/ts/virtual_machine/device.ts | 372 +++++++++++++++------- www/src/ts/virtual_machine/index.ts | 43 ++- www/src/ts/virtual_machine/ui.ts | 33 +- www/src/ts/virtual_machine/utils.ts | 2 +- 5 files changed, 347 insertions(+), 128 deletions(-) diff --git a/www/src/ts/virtual_machine/base_device.ts b/www/src/ts/virtual_machine/base_device.ts index af2be7f..fd05fd3 100644 --- a/www/src/ts/virtual_machine/base_device.ts +++ b/www/src/ts/virtual_machine/base_device.ts @@ -74,6 +74,10 @@ export const VMDeviceMixin = >( "vm-device-modified", this._handleDeviceModified.bind(this), ); + window.VM?.addEventListener( + "vm-devices-update", + this._handleDevicesModified.bind(this), + ); this.updateDevice(); return root; } @@ -82,9 +86,16 @@ export const VMDeviceMixin = >( const id = e.detail; if (this.deviceID === id) { this.updateDevice(); + } else { + this.requestUpdate(); } } + _handleDevicesModified(e: CustomEvent) { + const ids = e.detail; + this.requestUpdate(); + } + updateDevice() { const name = this.device.name ?? null; if (this.name !== name) { @@ -114,7 +125,9 @@ export const VMDeviceMixin = >( if (!structuralEqual(this.connections, connections)) { this.connections = connections; } - this.updateIC(); + if (typeof this.device.ic !== "undefined") { + this.updateIC(); + } } updateIC() { @@ -130,7 +143,7 @@ export const VMDeviceMixin = >( if (this.icState !== state) { this.icState = state; } - const errors = this.device.program!.errors ?? null; + const errors = this.device.program?.errors ?? null; if (!structuralEqual(this.errors, errors)) { this.errors = errors; } @@ -159,7 +172,9 @@ export const VMDeviceMixin = >( return VMDeviceMixinClass as Constructor & T; }; -export const VMActiveICMixin = >(superClass: T) => { +export const VMActiveICMixin = >( + superClass: T, +) => { class VMActiveICMixinClass extends VMDeviceMixin(superClass) { constructor() { super(); @@ -187,6 +202,6 @@ export const VMActiveICMixin = >(superClass: T } this.updateDevice(); } - }; + } return VMActiveICMixinClass as Constructor & T; -} +}; diff --git a/www/src/ts/virtual_machine/device.ts b/www/src/ts/virtual_machine/device.ts index 47c7c60..a1b027f 100644 --- a/www/src/ts/virtual_machine/device.ts +++ b/www/src/ts/virtual_machine/device.ts @@ -44,6 +44,13 @@ import { connectionFromDeviceDBConnection } from "./utils"; export class VMDeviceCard extends VMDeviceMixin(BaseElement) { image_err: boolean; + @property({ type: Boolean }) open: boolean; + + constructor() { + super(); + this.open = false; + } + static styles = [ ...defaultCss, css` @@ -71,6 +78,9 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { align-items: center; flex-wrap: wrap; } + .device-card{ + --padding: var(--sl-spacing-small); + } .device-name::part(input) { width: 10rem; } @@ -122,30 +132,58 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { if (this.deviceID == activeIc?.id) { badges.push(html`db`); } - activeIc?.pins?.forEach((id, _index) => { + activeIc?.pins?.forEach((id, index) => { if (this.deviceID == id) { - badges.push(html``); + badges.push(html`d${index}`); } }, this); return html` - +
- + Id - + Name - + - + Hash - + ${badges.map((badge) => badge)}
@@ -157,12 +195,20 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { const inputIdBase = `vmDeviceCard${this.deviceID}Field`; return html` ${fields.map(([name, field], _index, _fields) => { - return html` - ${name} - - ${field.field_type} - `; + return html` + ${name} + + ${field.field_type} + `; })} `; } @@ -173,17 +219,28 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { const inputIdBase = `vmDeviceCard${this.deviceID}Slot${slotIndex}Field`; return html` - ${slotIndex} : ${slot.typ} + ${slotIndex} : ${slot.typ}
${fields.map( - ([name, field], _index, _fields) => html` - - ${name} - - ${field.field_type} - - `, + ([name, field], _index, _fields) => html` + + ${name} + + ${field.field_type} + + `, )}
@@ -207,19 +264,26 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { return html`
${this.connections.map((connection, index, _conns) => { - const conn = - typeof connection === "object" ? connection.CableNetwork : null; - return html` - - Connection:${index} - ${vmNetworks.map( - (net) => - html`Network ${net}`, - )} - ${conn?.typ} - - `; + const conn = + typeof connection === "object" ? connection.CableNetwork : null; + return html` + + Connection:${index} + ${vmNetworks.map( + (net) => + html`Network ${net}`, + )} + ${conn?.typ} + + `; })}
`; @@ -230,16 +294,23 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { return html`
${pins?.map( - (pin, index) => - html` - d${index} - ${visibleDevices.map( - (device, _index) => - html` - Device ${device.id} : ${device.name ?? device.prefabName} - `, - )} - `, + (pin, index) => + html` + d${index} + ${visibleDevices.map( + (device, _index) => + html` + Device ${device.id} : ${device.name ?? device.prefabName} + `, + )} + `, )}
`; @@ -247,7 +318,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { render(): HTMLTemplateResult { return html` - +
${this.renderHeader()}
Fields @@ -303,8 +374,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { const select = e.target as SlSelect; const conn = parseInt(select.getAttribute("key")!); const val = select.value ? parseInt(select.value as string) : undefined; - this.device.setConnection(conn, val); - + window.VM.setDeviceConnection(this.deviceID, conn, val); this.updateDevice(); } @@ -312,7 +382,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { 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); + window.VM.setDevicePin(this.deviceID, pin, val); this.updateDevice(); } } @@ -326,7 +396,6 @@ export class VMDeviceList extends BaseElement { css` .header { margin-bottom: 1rem; - margin-right: 2rem; padding: 0.25rem 0.25rem; align-items: center; display: flex; @@ -336,10 +405,11 @@ export class VMDeviceList extends BaseElement { } .device-list { display: flex; - flex-direction: row; - flex-wrap: wrap; + flex-direction: column; + box-sizing: border-box; } .device-list-card { + width: 100%; } `, ]; @@ -368,7 +438,10 @@ export class VMDeviceList extends BaseElement { protected render(): HTMLTemplateResult { const deviceCards: HTMLTemplateResult[] = this.devices.map( (id, _index, _ids) => - html``, + html``, ); const result = html`
@@ -473,8 +546,9 @@ export class VMAddDeviceButton extends BaseElement { this._searchResults = names.map((name) => this._strutures.get(name)!); } else { - this._searchResults = - [] ?? this._strutures ? [...this._strutures.values()] : []; + // this._searchResults = + // [] ?? this._strutures ? [...this._strutures.values()] : []; + this._searchResults = []; } } @@ -494,27 +568,48 @@ export class VMAddDeviceButton extends BaseElement { renderSearchResults(): HTMLTemplateResult { const renderedResults: HTMLTemplateResult[] = this._searchResults?.map( (result) => - html``, + html``, ); return html`${renderedResults}`; } + _handleDeviceAdd() { + this.drawer.hide(); + } + render() { return html` - + Add Device - + Search Structures "
${this.renderSearchResults()}
- { - this.drawer.hide(); + { + this.drawer.hide(); }} - > + > Close
@@ -540,8 +635,6 @@ export class VMAddDeviceButton extends BaseElement { @customElement("vm-device-template") export class VmDeviceTemplate extends BaseElement { - _prefab_name: string; - dbDevice: DeviceDBEntry; private _deviceDB: DeviceDB; private image_err: boolean = false; @@ -585,7 +678,7 @@ export class VmDeviceTemplate extends BaseElement { this.deviceDB = window.VM!.db; } - get deviceDB() { + get deviceDB(): DeviceDB { return this._deviceDB; } @@ -595,22 +688,26 @@ export class VmDeviceTemplate extends BaseElement { this.setupState(); } - get prefab_name() { + private _prefab_name: string; + + get prefab_name(): string { return this._prefab_name; } @property({ type: String }) set prefab_name(val: string) { this._prefab_name = val; - - this.dbDevice = this.deviceDB.db[this._prefab_name]; this.setupState(); } + get dbDevice(): DeviceDBEntry { + return this.deviceDB.db[this.prefab_name]; + } + setupState() { const slotlogicmap: { [key: number]: SlotLogicType[] } = {}; for (const [slt, slotIndexes] of Object.entries( - this.dbDevice.slotlogic ?? {}, + this.dbDevice?.slotlogic ?? {}, )) { for (const slotIndex of slotIndexes) { const list = slotlogicmap[slotIndex] ?? []; @@ -620,21 +717,20 @@ export class VmDeviceTemplate extends BaseElement { } this.fields = Object.fromEntries( - Object.entries(this.dbDevice.logic ?? {}).map(([lt, ft]) => [ - lt, - { field_type: ft, value: 0.0 } as LogicField, - ]), + Object.entries(this.dbDevice?.logic ?? {}).map(([lt, ft]) => { + const value = lt === "PrefabHash" ? this.dbDevice.hash : 0.0; + return [lt, { field_type: ft, value } as LogicField]; + }), ); - this.slots = - this.dbDevice.slots.map( - (slot, _index) => - ({ - typ: slot.typ, - }) as SlotTemplate, - ) ?? []; + this.slots = (this.dbDevice?.slots ?? []).map( + (slot, _index) => + ({ + typ: slot.typ, + }) as SlotTemplate, + ); - const connections = Object.entries(this.dbDevice.conn ?? {}).map( + const connections = Object.entries(this.dbDevice?.conn ?? {}).map( ([index, conn]) => [index, connectionFromDeviceDBConnection(conn)] as const, ); @@ -672,15 +768,33 @@ export class VmDeviceTemplate extends BaseElement { renderFields(): HTMLTemplateResult { const fields = Object.entries(this.fields); return html` - ${fields.map(([name, field_type], _index, _fields) => { - return html` - ${name} - ${field_type} - `; + ${fields.map(([name, field], _index, _fields) => { + return html` + + ${name} + ${field.field_type} + + `; })} `; } + _handleChangeField(e: CustomEvent) { + const input = e.target as SlInput; + const field = input.getAttribute("key")! as LogicType; + const val = parseNumber(input.value); + this.fields[field].value = val; + if (field === "ReferenceId" && val !== 0) { + this.device_id = val; + } + this.requestUpdate(); + } + renderSlot(slot: Slot, slotIndex: number): HTMLTemplateResult { return html` `; } @@ -699,19 +813,26 @@ export class VmDeviceTemplate extends BaseElement { return html`
${connections.map((connection, index, _conns) => { - const conn = - typeof connection === "object" ? connection.CableNetwork : null; - return html` - - Connection:${index} - ${vmNetworks.map( - (net) => - html`Network ${net}`, - )} - ${conn?.typ} - - `; + const conn = + typeof connection === "object" ? connection.CableNetwork : null; + return html` + + Connection:${index} + ${vmNetworks.map( + (net) => + html`Network ${net}`, + )} + ${conn?.typ} + + `; })}
`; @@ -722,6 +843,7 @@ export class VmDeviceTemplate extends BaseElement { const conn = parseInt(select.getAttribute("key")!); const val = select.value ? parseInt(select.value as string) : undefined; (this.connections[conn] as ConnectionCableNetwork).CableNetwork.net = val; + this.requestUpdate(); } renderPins(): HTMLTemplateResult { @@ -730,18 +852,27 @@ export class VmDeviceTemplate extends BaseElement { } render() { - const device = this.deviceDB.db[this.prefab_name]; + const device = this.dbDevice; return html`
- - + +
- ${device.name} - ${device.hash} + ${device?.name} + ${device?.hash}
- Add + Add
@@ -754,8 +885,12 @@ export class VmDeviceTemplate extends BaseElement { ${this.renderFields()} ${this.renderSlots()} - ${this.renderReagents()} - ${this.renderNetworks()} + ${this.renderReagents()} + ${this.renderNetworks()} ${this.renderPins()}
@@ -763,9 +898,20 @@ export class VmDeviceTemplate extends BaseElement { `; } _handleAddButtonClick() { + this.dispatchEvent( + new CustomEvent("add-device-template", { bubbles: true }), + ); + const template: DeviceTemplate = { + id: this.device_id, + name: this.device_name, + prefab_name: this.prefab_name, + slots: this.slots, + connections: this.connections, + fields: this.fields, + }; + window.VM.addDeviceFromTemplate(template); - - // TODO: Actualy add Device + // reset state for new device this.setupState(); } } diff --git a/www/src/ts/virtual_machine/index.ts b/www/src/ts/virtual_machine/index.ts index f60e02f..2cbab64 100644 --- a/www/src/ts/virtual_machine/index.ts +++ b/www/src/ts/virtual_machine/index.ts @@ -1,4 +1,4 @@ -import { DeviceRef, VM, init } from "ic10emu_wasm"; +import { DeviceRef, DeviceTemplate, VM, init } from "ic10emu_wasm"; import { DeviceDB } from "./device_db"; import "./base_device"; @@ -286,6 +286,31 @@ class VirtualMachine extends EventTarget { return false; } + setDeviceConnection(id: number, conn: number, val: number | undefined) { + const device = this._devices.get(id); + if (typeof device !== "undefined") { + try { + this.ic10vm.setDeviceConnection(id, conn, val); + this.updateDevice(device); + } catch (err) { + this.handleVmError(err); + } + } + } + + setDevicePin(id: number, pin: number, val: number | undefined) { + const device = this._devices.get(id); + if (typeof device !== "undefined") { + try { + this.ic10vm.setPin(id, pin, val) + this.updateDevice(device); + } catch (err) { + this.handleVmError(err); + } + } + + } + setupDeviceDatabase(db: DeviceDB) { this.db = db; console.log("Loaded Device Database", this.db); @@ -293,6 +318,22 @@ class VirtualMachine extends EventTarget { new CustomEvent("vm-device-db-loaded", { detail: this.db }), ); } + + addDeviceFromTemplate(template: DeviceTemplate) { + try { + console.log("adding device", template); + const id = this.ic10vm.addDeviceFromTemplate(template); + this._devices.set(id, this.ic10vm.getDevice(id)!); + const device_ids = this.ic10vm.devices; + this.dispatchEvent( + new CustomEvent("vm-devices-update", { + detail: Array.from(device_ids), + }), + ); + } catch (err) { + this.handleVmError(err); + } + } } export { VirtualMachine }; diff --git a/www/src/ts/virtual_machine/ui.ts b/www/src/ts/virtual_machine/ui.ts index 0a9b0fc..b0bd1c6 100644 --- a/www/src/ts/virtual_machine/ui.ts +++ b/www/src/ts/virtual_machine/ui.ts @@ -34,27 +34,44 @@ export class VMUI extends BaseElement { margin-left: 1rem; margin-right: 1rem; margin-top: 0.5rem; + flex: 0 0 auto; } .side-container { - height: 100%; + height: calc(100vh - 3.8rem); + display: flex; + flex-direction: column; + } + vm-device-list { + display: flex; + flex-direction: column; + flex: 1 1 auto; overflow-y: auto; } + .tab-panel { + height: calc(100vh - 19rem); + overflow-y: auto; + } + .tab-group { + flex: 1 1 auto; + } + sl-tab::part(base) { + padding: var(--sl-spacing-small) var(--sl-spacing-medium); + } `, ]; - constructor() { super(); } connectedCallback(): void { super.connectedCallback(); - window.VM.addEventListener("vm-message", this._handleVMMessage.bind(this) ) + window.VM.addEventListener("vm-message", this._handleVMMessage.bind(this)); } _handleVMMessage(e: CustomEvent) { const msg: ToastMessage = e.detail; - const alert = Object.assign(document.createElement('sl-alert'), { + const alert = Object.assign(document.createElement("sl-alert"), { variant: msg.variant, closable: true, // duration: 5000, @@ -62,7 +79,7 @@ export class VMUI extends BaseElement { ${msg.title}
${msg.msg} - ` + `, }); document.body.append(alert); @@ -73,10 +90,10 @@ export class VMUI extends BaseElement { return html`
- + Active IC Devices - + @@ -84,7 +101,7 @@ export class VMUI extends BaseElement { - + diff --git a/www/src/ts/virtual_machine/utils.ts b/www/src/ts/virtual_machine/utils.ts index 03ce621..d6d5f31 100644 --- a/www/src/ts/virtual_machine/utils.ts +++ b/www/src/ts/virtual_machine/utils.ts @@ -7,7 +7,7 @@ export function connectionFromDeviceDBConnection(conn: DeviceDBConnection): Conn return { CableNetwork: { net: undefined, - typ: conn.role + typ: conn.typ } }; } else {