diff --git a/ic10emu/src/lib.rs b/ic10emu/src/lib.rs index 8b9238c..810f6c6 100644 --- a/ic10emu/src/lib.rs +++ b/ic10emu/src/lib.rs @@ -1645,6 +1645,25 @@ impl VM { .collect::, ICError>>()?; Ok(mode.apply(&samples)) } + + pub fn remove_device(&mut self, id: u32) -> Result<(), VMError> { + let Some(device) = self.devices.remove(&id) else { + return Err(VMError::UnknownId(id)); + }; + + for conn in device.borrow().connections.iter() { + if let Connection::CableNetwork { net: Some(net), .. } = conn { + if let Some(network) = self.networks.get(net) { + network.borrow_mut().remove_all(id); + } + } + } + if let Some(ic_id) = device.borrow().ic { + let _ = self.ics.remove(&ic_id); + } + self.id_space.free_id(id); + Ok(()) + } } impl BatchMode { diff --git a/ic10emu_wasm/src/lib.rs b/ic10emu_wasm/src/lib.rs index 60a6192..de859ea 100644 --- a/ic10emu_wasm/src/lib.rs +++ b/ic10emu_wasm/src/lib.rs @@ -465,6 +465,11 @@ impl VM { pub fn change_device_id(&self, old_id: u32, new_id: u32) -> Result<(), JsError> { Ok(self.vm.borrow_mut().change_device_id(old_id, new_id)?) } + + #[wasm_bindgen(js_name = "removeDevice")] + pub fn remove_device(&self, id: u32) -> Result<(), JsError> { + Ok(self.vm.borrow_mut().remove_device(id)?) + } } impl Default for VM { diff --git a/www/src/ts/components/base.ts b/www/src/ts/components/base.ts index 6f60273..65f410e 100644 --- a/www/src/ts/components/base.ts +++ b/www/src/ts/components/base.ts @@ -28,6 +28,9 @@ export const defaultCss = [ .mt-auto { margin-top: auto !important; } + .me-2 { + margin-right: 2rem !important; + } .hstack { display: flex; flex-direction: row; @@ -36,6 +39,9 @@ export const defaultCss = [ display: flex; flex-direction: column; } + .flex-g { + flex-grow: 1; + } `, ]; diff --git a/www/src/ts/virtual_machine/device.ts b/www/src/ts/virtual_machine/device.ts index 7fb62d7..1bdb3c8 100644 --- a/www/src/ts/virtual_machine/device.ts +++ b/www/src/ts/virtual_machine/device.ts @@ -44,6 +44,7 @@ import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.js" import SlDrawer from "@shoelace-style/shoelace/dist/components/drawer/drawer.js"; import { DeviceDB, DeviceDBEntry } from "./device_db"; import { connectionFromDeviceDBConnection } from "./utils"; +import { SlDialog } from "@shoelace-style/shoelace"; @customElement("vm-device-card") export class VMDeviceCard extends VMDeviceMixin(BaseElement) { @@ -74,6 +75,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { .header { display: flex; flex-direction: row; + flex-grow: 1; } .header-name { display: flex; @@ -130,6 +132,24 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { max-height: 20rem; overflow-y: auto; } + sl-icon-button.remove-button::part(base) { + color: var(--sl-color-danger-600); + } + sl-icon-button.remove-button::part(base):hover, + sl-icon-button.remove-button::part(base):focus { + color: var(--sl-color-danger-500); + } + sl-icon-button.remove-button::part(base):active { + color: var(--sl-color-danger-600); + } + .remove-dialog-body { + display: flex; + flex-direction: row; + } + .dialog-image { + width: 3rem; + height: 3rem; + } `, ]; @@ -163,6 +183,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { renderHeader(): HTMLTemplateResult { const activeIc = window.VM?.activeIC; + const thisIsActiveIc = activeIc.id === this.deviceID; const badges: HTMLTemplateResult[] = []; if (this.deviceID == activeIc?.id) { badges.push(html`db`); @@ -197,6 +218,11 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { ${badges.map((badge) => badge)} +
+ + + +
`; } @@ -354,14 +380,42 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { ${this.renderPins()} + +
+ +
+

Are you sure you want to remove this device?

+ Id ${this.deviceID} : ${this.name ?? this.prefabName} +
+
+
+ Close + Remove +
+
`; } + @query(".remove-device-dialog") removeDialog: SlDialog; + + _preventOverlayClose(event: CustomEvent) { + if (event.detail.source === 'overlay') { + event.preventDefault(); + } + } + + _closeRemoveDialog() { + this.removeDialog.hide() + } + _handleChangeID(e: CustomEvent) { const input = e.target as SlInput; const val = parseIntWithHexOrBinary(input.value); if (!isNaN(val)) { - window.VM.changeDeviceId(this.deviceID, val); + if (!window.VM.changeDeviceId(this.deviceID, val)) { + input.value = this.deviceID.toString(); + } } else { input.value = this.deviceID.toString(); } @@ -370,7 +424,9 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { _handleChangeName(e: CustomEvent) { const input = e.target as SlInput; const name = input.value.length === 0 ? undefined : input.value; - window.VM?.setDeviceName(this.deviceID, name); + if (!window.VM?.setDeviceName(this.deviceID, name)) { + input.value = this.name; + }; this.updateDevice(); } @@ -391,6 +447,15 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) { this.updateDevice(); } + _handleDeviceRemoveButton(_e: Event) { + this.removeDialog.show() + } + + _removeDialogRemove() { + this.removeDialog.hide() + window.VM.removeDevice(this.deviceID) + } + _handleChangeConnection(e: CustomEvent) { const select = e.target as SlSelect; const conn = parseInt(select.getAttribute("key")!); diff --git a/www/src/ts/virtual_machine/index.ts b/www/src/ts/virtual_machine/index.ts index be3917c..4116899 100644 --- a/www/src/ts/virtual_machine/index.ts +++ b/www/src/ts/virtual_machine/index.ts @@ -116,7 +116,7 @@ class VirtualMachine extends EventTarget { if (update_flag) { const ids = Array.from(device_ids); - ids.sort() + ids.sort(); this.dispatchEvent( new CustomEvent("vm-devices-update", { detail: ids, @@ -132,8 +132,8 @@ class VirtualMachine extends EventTarget { const ic = this._ics.get(id); const prog = progs.get(id); if (ic && prog) { - console.time(`CompileProgram_${id}_${attempt}`); try { + console.time(`CompileProgram_${id}_${attempt}`); this.ics.get(id)!.setCodeInvalid(progs.get(id)!); const compiled = this.ics.get(id)?.program!; window.App?.session.setProgramErrors(id, compiled.errors); @@ -142,8 +142,9 @@ class VirtualMachine extends EventTarget { ); } catch (err) { this.handleVmError(err); + } finally{ + console.timeEnd(`CompileProgram_${id}_${attempt}`); } - console.timeEnd(`CompileProgram_${id}_${attempt}`); } } this.update(); @@ -220,49 +221,59 @@ class VirtualMachine extends EventTarget { this.dispatchEvent(new CustomEvent("vm-message", { detail: message })); } - changeDeviceId(old_id: number, new_id: number) { + changeDeviceId(old_id: number, new_id: number): boolean { try { this.ic10vm.changeDeviceId(old_id, new_id); this.updateDevices(); if (window.App.session.activeIC === old_id) { window.App.session.activeIC = new_id; } + return true; } catch (err) { this.handleVmError(err); + return false; } } - setRegister(index: number, val: number) { + setRegister(index: number, val: number): boolean { const ic = this.activeIC!; try { ic.setRegister(index, val); this.updateDevice(ic); + return true; } catch (err) { this.handleVmError(err); + return false; } } - setStack(addr: number, val: number) { + setStack(addr: number, val: number): boolean { const ic = this.activeIC!; try { ic!.setStack(addr, val); this.updateDevice(ic); + return true; } catch (err) { this.handleVmError(err); + return false; } } setDeviceName(id: number, name: string): boolean { const device = this._devices.get(id); if (device) { - device.setName(name); - this.dispatchEvent(new CustomEvent("vm-device-modified", { detail: id })); - return true; + try { + device.setName(name); + this.dispatchEvent(new CustomEvent("vm-device-modified", { detail: id })); + return true; + } catch(e) { + this.handleVmError(e); + } } return false; } - setDeviceField(id: number, field: string, val: number) { + setDeviceField(id: number, field: string, val: number): boolean { const device = this._devices.get(id); if (device) { try { @@ -276,7 +287,7 @@ class VirtualMachine extends EventTarget { return false; } - setDeviceSlotField(id: number, slot: number, field: string, val: number) { + setDeviceSlotField(id: number, slot: number, field: string, val: number): boolean { const device = this._devices.get(id); if (device) { try { @@ -290,29 +301,32 @@ class VirtualMachine extends EventTarget { return false; } - setDeviceConnection(id: number, conn: number, val: number | undefined) { + setDeviceConnection(id: number, conn: number, val: number | undefined): boolean { const device = this._devices.get(id); if (typeof device !== "undefined") { try { this.ic10vm.setDeviceConnection(id, conn, val); this.updateDevice(device); + return true } catch (err) { this.handleVmError(err); } } + return false } - setDevicePin(id: number, pin: number, val: number | undefined) { + setDevicePin(id: number, pin: number, val: number | undefined): boolean { const device = this._devices.get(id); if (typeof device !== "undefined") { try { - this.ic10vm.setPin(id, pin, val) + this.ic10vm.setPin(id, pin, val); this.updateDevice(device); + return true; } catch (err) { this.handleVmError(err); } } - + return false; } setupDeviceDatabase(db: DeviceDB) { @@ -323,7 +337,7 @@ class VirtualMachine extends EventTarget { ); } - addDeviceFromTemplate(template: DeviceTemplate) { + addDeviceFromTemplate(template: DeviceTemplate): boolean { try { console.log("adding device", template); const id = this.ic10vm.addDeviceFromTemplate(template); @@ -334,8 +348,21 @@ class VirtualMachine extends EventTarget { detail: Array.from(device_ids), }), ); + return true; } catch (err) { this.handleVmError(err); + return false; + } + } + + removeDevice(id: number): boolean { + try { + this.ic10vm.removeDevice(id); + this.updateDevices(); + return true; + } catch (err) { + this.handleVmError(err); + return false; } } }