diff --git a/ic10emu/src/vm.rs b/ic10emu/src/vm.rs index 00b7825..d07e3c8 100644 --- a/ic10emu/src/vm.rs +++ b/ic10emu/src/vm.rs @@ -1,5 +1,5 @@ use crate::{ - device::{Device, DeviceTemplate}, + device::{Device, DeviceTemplate, SlotOccupant, SlotOccupantTemplate}, grammar::{BatchMode, LogicType, SlotLogicType}, interpreter::{self, FrozenIC, ICError, LineError}, network::{CableConnectionType, Connection, FrozenNetwork, Network}, @@ -747,6 +747,32 @@ impl VM { Ok(()) } + pub fn set_slot_occupant( + &mut self, + id: u32, + index: usize, + template: SlotOccupantTemplate, + ) -> Result<(), VMError> { + let Some(device) = self.devices.get(&id) else { + return Err(VMError::UnknownId(id)); + }; + + let mut device_ref = device.borrow_mut(); + let slot = device_ref + .slots + .get_mut(index) + .ok_or(ICError::SlotIndexOutOfRange(index as f64))?; + + if let Some(id) = template.id.as_ref() { + self.id_space.use_id(*id)?; + } + + let occupant = SlotOccupant::from_template(template, || self.id_space.next()); + slot.occupant = Some(occupant); + + Ok(()) + } + pub fn save_vm_state(&self) -> FrozenVM { FrozenVM { ics: self.ics.values().map(|ic| ic.borrow().into()).collect(), diff --git a/ic10emu_wasm/src/lib.rs b/ic10emu_wasm/src/lib.rs index 5c851c4..7b5c173 100644 --- a/ic10emu_wasm/src/lib.rs +++ b/ic10emu_wasm/src/lib.rs @@ -3,7 +3,7 @@ mod utils; mod types; use ic10emu::{ - device::{Device, DeviceTemplate}, + device::{Device, DeviceTemplate, SlotOccupantTemplate}, grammar::{LogicType, SlotLogicType}, vm::{FrozenVM, VMError, VM}, }; @@ -489,6 +489,12 @@ impl VMRef { Ok(self.vm.borrow_mut().remove_device(id)?) } + #[wasm_bindgen(js_name = "setSlotOccupant", skip_typescript)] + pub fn set_slot_occupant(&self, id: u32, index: usize, template: JsValue) -> Result<(), JsError> { + let template: SlotOccupantTemplate = serde_wasm_bindgen::from_value(template)?; + Ok(self.vm.borrow_mut().set_slot_occupant(id, index, template)?) + } + #[wasm_bindgen(js_name = "saveVMState", skip_typescript)] pub fn save_vm_state(&self) -> JsValue { let state = self.vm.borrow().save_vm_state(); diff --git a/ic10emu_wasm/src/types.ts b/ic10emu_wasm/src/types.ts index 12c659b..96d0589 100644 --- a/ic10emu_wasm/src/types.ts +++ b/ic10emu_wasm/src/types.ts @@ -177,6 +177,7 @@ export interface FrozenVM { export interface VMRef { addDeviceFromTemplate(template: DeviceTemplate): number; + setSlotOccupant(id: number, index: number, template: SlotOccupantTemplate); saveVMState(): FrozenVM; restoreVMState(state: FrozenVM): void; } diff --git a/www/src/ts/app/app.ts b/www/src/ts/app/app.ts index f89ce51..45e82e1 100644 --- a/www/src/ts/app/app.ts +++ b/www/src/ts/app/app.ts @@ -79,7 +79,7 @@ export class App extends BaseElement { window.App.set(this); } - protected createRenderRoot(): HTMLElement | DocumentFragment { + createRenderRoot(): HTMLElement | DocumentFragment { const root = super.createRenderRoot(); root.addEventListener("app-share-session", this._handleShare.bind(this)); root.addEventListener("app-open-file", this._handleOpenFile.bind(this)); diff --git a/www/src/ts/virtual_machine/base_device.ts b/www/src/ts/virtual_machine/base_device.ts index 9b4861c..c87036b 100644 --- a/www/src/ts/virtual_machine/base_device.ts +++ b/www/src/ts/virtual_machine/base_device.ts @@ -94,6 +94,22 @@ export const VMDeviceMixin = >( return root; } + disconnectedCallback(): void { + window.VM.get().then((vm) => + vm.removeEventListener( + "vm-device-modified", + this._handleDeviceModified.bind(this), + ), + ); + window.VM.get().then((vm) => + vm.removeEventListener( + "vm-devices-update", + this._handleDevicesModified.bind(this), + ), + ); + + } + _handleDeviceModified(e: CustomEvent) { const id = e.detail; if (this.deviceID === id) { @@ -182,7 +198,6 @@ export const VMDeviceMixin = >( this.pins = pins; } } - } return VMDeviceMixinClass as Constructor & T; }; @@ -208,6 +223,17 @@ export const VMActiveICMixin = >( return root; } + + disconnectedCallback(): void { + window.VM.get().then((vm) => + vm.removeEventListener("vm-run-ic", this._handleDeviceModified.bind(this)), + ); + window.App.app.session.removeEventListener( + "session-active-ic", + this._handleActiveIC.bind(this), + ); + } + _handleActiveIC(e: CustomEvent) { const id = e.detail; if (this.deviceID !== id) { @@ -223,22 +249,33 @@ export const VMActiveICMixin = >( export declare class VMDeviceDBMixinInterface { deviceDB: DeviceDB; - _handleDeviceDBLoad(e: CustomEvent): void + _handleDeviceDBLoad(e: CustomEvent): void; + postDBSetUpdate(): void; } -export const VMDeviceDBMixin = >(superClass: T) => { +export const VMDeviceDBMixin = >( + superClass: T, +) => { class VMDeviceDBMixinClass extends superClass { - connectedCallback(): void { const root = super.connectedCallback(); window.VM.vm.addEventListener( "vm-device-db-loaded", this._handleDeviceDBLoad.bind(this), ); - this.deviceDB = window.VM.vm.db!; + if (typeof window.VM.vm.db !== "undefined") { + this.deviceDB = window.VM.vm.db!; + } return root; } + disconnectedCallback(): void { + window.VM.vm.removeEventListener( + "vm-device-db-loaded", + this._handleDeviceDBLoad.bind(this), + ) + } + _handleDeviceDBLoad(e: CustomEvent) { this.deviceDB = e.detail; } @@ -249,11 +286,14 @@ export const VMDeviceDBMixin = >(superClass: T return this._deviceDB; } + postDBSetUpdate(): void { } + @state() set deviceDB(val: DeviceDB) { this._deviceDB = val; + this.postDBSetUpdate(); } } - return VMDeviceDBMixinClass as Constructor & T -} + return VMDeviceDBMixinClass as Constructor & T; +}; diff --git a/www/src/ts/virtual_machine/device/device_list.ts b/www/src/ts/virtual_machine/device/device_list.ts index a837faf..82bd447 100644 --- a/www/src/ts/virtual_machine/device/device_list.ts +++ b/www/src/ts/virtual_machine/device/device_list.ts @@ -1,17 +1,15 @@ -import { html, css, HTMLTemplateResult } from "lit"; -import { customElement, property, query, state } from "lit/decorators.js"; +import { html, css, HTMLTemplateResult, PropertyValueMap } from "lit"; +import { customElement, query, state } from "lit/decorators.js"; import { BaseElement, defaultCss } from "components"; import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js"; import { structuralEqual } from "../../utils"; -import SlDrawer from "@shoelace-style/shoelace/dist/components/drawer/drawer.js"; -import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db"; import { repeat } from "lit/directives/repeat.js"; -import { cache } from "lit/directives/cache.js"; import { default as uFuzzy } from "@leeoniya/ufuzzy"; -import { when } from "lit/directives/when.js"; -import { unsafeHTML } from "lit/directives/unsafe-html.js"; +import { VMSlotAddDialog } from "./slot_add_dialog"; +import "./add_device" +import { SlotModifyEvent } from "./slot"; @customElement("vm-device-list") export class VMDeviceList extends BaseElement { @@ -49,14 +47,20 @@ export class VMDeviceList extends BaseElement { } connectedCallback(): void { - const root = super.connectedCallback(); + super.connectedCallback(); window.VM.get().then((vm) => vm.addEventListener( "vm-devices-update", this._handleDevicesUpdate.bind(this), ), ); - return root; + } + + protected firstUpdated(_changedProperties: PropertyValueMap | Map): void { + this.renderRoot.querySelector(".device-list").addEventListener( + "device-modify-slot", + this._showDeviceSlotDialog.bind(this), + ); } _handleDevicesUpdate(e: CustomEvent) { @@ -92,11 +96,20 @@ export class VMDeviceList extends BaseElement {
${deviceCards}
+ `; return result; } + @query("vm-slot-add-dialog") slotDialog: VMSlotAddDialog; + + _showDeviceSlotDialog( + e: CustomEvent, + ) { + this.slotDialog.show(e.detail.deviceID, e.detail.slotIndex); + } + get filteredDeviceIds() { if (typeof this._filteredDeviceIds !== "undefined") { return this._filteredDeviceIds; @@ -162,239 +175,3 @@ export class VMDeviceList extends BaseElement { } } -@customElement("vm-add-device-button") -export class VMAddDeviceButton extends BaseElement { - static styles = [ - ...defaultCss, - css` - .add-device-drawer { - --size: 36rem; - } - - .card { - margin-top: var(--sl-spacing-small); - margin-right: var(--sl-spacing-small); - } - - .card + .card { - } - `, - ]; - - @query("sl-drawer") drawer: SlDrawer; - @query(".device-search-input") searchInput: SlInput; - - private _deviceDB: DeviceDB; - private _strutures: Map = new Map(); - private _datapoints: [string, string][] = []; - private _haystack: string[] = []; - get deviceDB() { - return this._deviceDB; - } - - @state() - set deviceDB(val: DeviceDB) { - this._deviceDB = val; - this._strutures = new Map( - Object.values(this.deviceDB.db) - .filter((entry) => this.deviceDB.structures.includes(entry.name), this) - .filter( - (entry) => this.deviceDB.logic_enabled.includes(entry.name), - this, - ) - .map((entry) => [entry.name, entry]), - ); - - const datapoints: [string, string][] = []; - for (const entry of this._strutures.values()) { - datapoints.push( - [entry.title, entry.name], - [entry.name, entry.name], - [entry.desc, entry.name], - ); - } - const haystack: string[] = datapoints.map((data) => data[0]); - this._datapoints = datapoints; - this._haystack = haystack; - this.performSearch(); - } - - private _filter: string = ""; - - get filter() { - return this._filter; - } - - @state() - set filter(val: string) { - this._filter = val; - this.performSearch(); - } - - private _searchResults: { - entry: DeviceDBEntry; - haystackEntry: string; - ranges: number[]; - }[]; - - private filterTimeout: number | undefined; - - performSearch() { - if (this._filter) { - const uf = new uFuzzy({}); - const [_idxs, info, order] = uf.search( - this._haystack, - this._filter, - 0, - 1e3, - ); - - const filtered = order?.map((infoIdx) => ({ - name: this._datapoints[info.idx[infoIdx]][1], - haystackEntry: this._haystack[info.idx[infoIdx]], - ranges: info.ranges[infoIdx], - })); - - const unique = [...new Set(filtered.map((obj) => obj.name))].map( - (result) => { - return filtered.find((obj) => obj.name === result); - }, - ); - - this._searchResults = unique.map(({ name, haystackEntry, ranges }) => ({ - entry: this._strutures.get(name)!, - haystackEntry, - ranges, - })); - } else { - // return everything - this._searchResults = [...this._strutures.values()].map((st) => ({ - entry: st, - haystackEntry: st.title, - ranges: [], - })); - } - } - - connectedCallback(): void { - const root = super.connectedCallback(); - window.VM.get().then((vm) => - vm.addEventListener( - "vm-device-db-loaded", - this._handleDeviceDBLoad.bind(this), - ), - ); - return root; - } - - _handleDeviceDBLoad(e: CustomEvent) { - this.deviceDB = e.detail; - } - - renderSearchResults() { - return when( - typeof this._searchResults !== "undefined" && this._searchResults.length < 20, - () => - repeat( - this._searchResults ?? [], - (result) => result.entry.name, - (result) => - cache(html` - - - `), - ), - () => html` -
-

- - results, filter more to get cards -

-
- ${[ - ...this._searchResults.slice(0, 50), - { entry: { title: "", name: "" }, haystackEntry: "...", ranges: [] }, - ].map((result) => { - const hay = result.haystackEntry.slice(0, 15); - const ranges = result.ranges.filter((pos) => pos < 20); - const key = result.entry.name; - return html`
- ${result.entry.title} ( - ${unsafeHTML(uFuzzy.highlight(hay, ranges))} - ) -
`; - })} -
-
- `, - ); - } - - _handleHaystackClick(e: Event) { - const div = e.currentTarget as HTMLDivElement; - const key = div.getAttribute("key"); - this.filter = key; - this.searchInput.value = key; - } - - _handleDeviceAdd() { - this.drawer.hide(); - } - - render() { - return html` - - Add Device - - - - Search Structures - - -
${this.renderSearchResults()}
- { - this.drawer.hide(); - }} - > - Close - -
- `; - } - - _handleSearchInput(e: CustomEvent) { - if (this.filterTimeout) { - clearTimeout(this.filterTimeout); - } - const that = this; - this.filterTimeout = setTimeout(() => { - that.filter = that.searchInput.value; - that.filterTimeout = undefined; - }, 200); - } - - _handleAddButtonClick() { - this.drawer.show(); - (this.drawer.querySelector(".device-search-input") as SlInput).select(); - } -} diff --git a/www/src/ts/virtual_machine/device/index.ts b/www/src/ts/virtual_machine/device/index.ts index 9fb17b2..6b2fc94 100644 --- a/www/src/ts/virtual_machine/device/index.ts +++ b/www/src/ts/virtual_machine/device/index.ts @@ -13,11 +13,20 @@ import "@shoelace-style/shoelace/dist/components/badge/badge.js"; import "@shoelace-style/shoelace/dist/components/option/option.js"; import "@shoelace-style/shoelace/dist/components/drawer/drawer.js"; import "@shoelace-style/shoelace/dist/components/icon/icon.js"; +import "@shoelace-style/shoelace/dist/components/format-number/format-number.js"; +import "./template" +import "./card" +import "./device_list" +import "./add_device" +import "./slot_add_dialog" +import "./slot" import { VmDeviceTemplate } from "./template"; import { VMDeviceCard } from "./card"; -import { VMAddDeviceButton, VMDeviceList } from "./device_list"; +import { VMDeviceList } from "./device_list"; +import { VMAddDeviceButton } from "./add_device"; +import { VMSlotAddDialog } from "./slot_add_dialog"; -export { VMDeviceCard, VmDeviceTemplate, VMDeviceList, VMAddDeviceButton }; +export { VMDeviceCard, VmDeviceTemplate, VMDeviceList, VMAddDeviceButton, VMSlotAddDialog }; diff --git a/www/src/ts/virtual_machine/device/slot.ts b/www/src/ts/virtual_machine/device/slot.ts index 3373cd6..e200652 100644 --- a/www/src/ts/virtual_machine/device/slot.ts +++ b/www/src/ts/virtual_machine/device/slot.ts @@ -1,4 +1,4 @@ -import { html, css, HTMLTemplateResult } from "lit"; +import { html, css } from "lit"; import { customElement, property, query, state } from "lit/decorators.js"; import { BaseElement, defaultCss } from "components"; import { VMDeviceDBMixin, VMDeviceMixin } from "virtual_machine/base_device"; @@ -17,6 +17,11 @@ import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.com import { VMDeviceCard } from "./card"; import { when } from "lit/directives/when.js"; +export interface SlotModifyEvent { + deviceID: number; + slotIndex: number; +} + @customElement("vm-device-slot") export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { @property({ type: Number }) slotIndex: number; @@ -65,9 +70,9 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { } } - slotOcccupantTemplate(): { name: string, typ: SlotType} | undefined { + slotOcccupantTemplate(): { name: string; typ: SlotType } | undefined { if (this.deviceDB) { - const entry = this.deviceDB.db[this.prefabName] + const entry = this.deviceDB.db[this.prefabName]; return entry?.slots[this.slotIndex]; } else { return undefined; @@ -78,20 +83,24 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { const inputIdBase = `vmDeviceSlot${this.deviceID}Slot${this.slotIndex}Head`; const slot = this.slots[this.slotIndex]; const slotImg = this.slotOccupantImg(); - const img = html``; + const img = html``; const template = this.slotOcccupantTemplate(); return html`
${this.slotIndex}
@@ -100,33 +109,32 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { ${when( typeof slot.occupant !== "undefined", - () => html`
- ${slot.occupant.quantity}/${slot.occupant.max_quantity} -
` + () => + html`
+ ${slot.occupant.quantity}/${slot.occupant + .max_quantity} +
`, )}
- ${when( - typeof slot.occupant !== "undefined", - () => html` - - ${this.slotOccupantPrefabName()} - - `, - () => html` - - ${template?.name} - - `, - )} + ${when( + typeof slot.occupant !== "undefined", + () => html` ${this.slotOccupantPrefabName()} `, + () => html` ${template?.name} `, + )}
-
Type:${slot.typ}
+
+ Type:${slot.typ} +
${when( @@ -146,15 +154,20 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
`, - () => html` - `, + () => html``, )} `; } - _handleSlotClick(e: Event) { - console.log(e, e.currentTarget, e.target); + _handleSlotClick(_e: Event) { + this.dispatchEvent( + new CustomEvent("device-modify-slot", { + bubbles: true, + composed: true, + detail: { deviceID: this.deviceID, slotIndex: this.slotIndex }, + }), + ); } renderFields() { @@ -165,19 +178,22 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { return html`
${fields.map( - ([name, field], _index, _fields) => html` - - ${name} - - ${field.field_type} - - `, + ([name, field], _index, _fields) => html` + + ${name} + + ${field.field_type} + + `, )}
`; @@ -188,8 +204,12 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { const field = input.getAttribute("key")! as SlotLogicType; const val = parseNumber(input.value); window.VM.get().then((vm) => { - if (!vm.setDeviceSlotField(this.deviceID, this.slotIndex, field, val, true)) { - input.value = this.device.getSlotField(this.slotIndex, field).toString(); + if ( + !vm.setDeviceSlotField(this.deviceID, this.slotIndex, field, val, true) + ) { + input.value = this.device + .getSlotField(this.slotIndex, field) + .toString(); } this.updateDevice(); }); @@ -198,10 +218,10 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) { render() { return html` -
${this.renderHeader()}
-
- ${this.renderFields()} +
+ ${this.renderHeader()}
+
${this.renderFields()}
`; } diff --git a/www/src/ts/virtual_machine/device/template.ts b/www/src/ts/virtual_machine/device/template.ts index d044143..9cf6c7b 100644 --- a/www/src/ts/virtual_machine/device/template.ts +++ b/www/src/ts/virtual_machine/device/template.ts @@ -55,7 +55,7 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) { padding: var(--sl-spacing-small) var(--sl-spacing-medium); } sl-tab-group::part(base) { - height: 14rem; + height: 18rem; overflow-y: auto; } `, @@ -223,7 +223,7 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) { const device = this.dbDevice; return html` -
+