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:
@@ -46,6 +46,10 @@ pub enum ICError {
|
||||
StackIndexOutOfRange(f64),
|
||||
#[error("slot index out of range: '{0}'")]
|
||||
SlotIndexOutOfRange(f64),
|
||||
#[error("pin index {0} out of range 0-6")]
|
||||
PinIndexOutOfRange(usize),
|
||||
#[error("Connection index {0} out of range {1}")]
|
||||
ConnectionIndexOutOfRange(usize, usize),
|
||||
#[error("Unknown device ID '{0}'")]
|
||||
UnknownDeviceID(f64),
|
||||
#[error("Too few operands!: provide: '{provided}', desired: '{desired}'")]
|
||||
@@ -98,14 +102,12 @@ pub enum ICError {
|
||||
TypeValueNotKnown,
|
||||
#[error("Empty Device List")]
|
||||
EmptyDeviceList,
|
||||
#[error("Connection index out of range: '{0}'")]
|
||||
ConnectionIndexOutOFRange(u32),
|
||||
#[error("Connection specifier missing")]
|
||||
MissingConnectionSpecifier,
|
||||
#[error("No data network on connection '{0}'")]
|
||||
NotDataConnection(u32),
|
||||
NotDataConnection(usize),
|
||||
#[error("Network not connected on connection '{0}'")]
|
||||
NetworkNotConnected(u32),
|
||||
NetworkNotConnected(usize),
|
||||
#[error("Bad Network Id '{0}'")]
|
||||
BadNetworkId(u32),
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ pub enum VMError {
|
||||
LineError(#[from] LineError),
|
||||
#[error("Invalid network ID {0}")]
|
||||
InvalidNetwork(u16),
|
||||
#[error("Device {0} not visible to device {1} (not on the same networks)")]
|
||||
DeviceNotVisible(u16, u16),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
@@ -169,7 +171,7 @@ pub struct Device {
|
||||
pub slots: Vec<Slot>,
|
||||
pub reagents: HashMap<ReagentMode, HashMap<i32, f64>>,
|
||||
pub ic: Option<u16>,
|
||||
pub connections: [Connection; 8],
|
||||
pub connections: Vec<Connection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -264,7 +266,7 @@ impl Device {
|
||||
slots: Vec::new(),
|
||||
reagents: HashMap::new(),
|
||||
ic: None,
|
||||
connections: [Connection::default(); 8],
|
||||
connections: vec![Connection::default()],
|
||||
};
|
||||
device.connections[0] = Connection::CableNetwork(None);
|
||||
device
|
||||
@@ -300,16 +302,19 @@ impl Device {
|
||||
}
|
||||
|
||||
pub fn get_network_id(&self, connection: usize) -> Result<u16, ICError> {
|
||||
if connection >= 8 {
|
||||
Err(ICError::ConnectionIndexOutOFRange(connection as u32))
|
||||
if connection >= self.connections.len() {
|
||||
Err(ICError::ConnectionIndexOutOfRange(
|
||||
connection,
|
||||
self.connections.len(),
|
||||
))
|
||||
} else if let Connection::CableNetwork(network_id) = self.connections[connection] {
|
||||
if let Some(network_id) = network_id {
|
||||
Ok(network_id)
|
||||
} else {
|
||||
Err(ICError::NetworkNotConnected(connection as u32))
|
||||
Err(ICError::NetworkNotConnected(connection))
|
||||
}
|
||||
} else {
|
||||
Err(ICError::NotDataConnection(connection as u32))
|
||||
Err(ICError::NotDataConnection(connection))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,6 +393,11 @@ impl Device {
|
||||
}
|
||||
0.0
|
||||
}
|
||||
|
||||
pub fn set_name(&mut self, name: &str) {
|
||||
self.name_hash = Some((const_crc32::crc32(name.as_bytes()) as i32).into());
|
||||
self.name = Some(name.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VM {
|
||||
@@ -451,15 +461,28 @@ impl VM {
|
||||
});
|
||||
}
|
||||
let id = device.id;
|
||||
|
||||
let first_data_network =
|
||||
device
|
||||
.connections
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(index, conn)| match conn {
|
||||
&Connection::CableNetwork(_) => Some(index),
|
||||
&Connection::Other => None,
|
||||
});
|
||||
self.devices.insert(id, Rc::new(RefCell::new(device)));
|
||||
let _ = self.add_device_to_network(
|
||||
id,
|
||||
if let Some(network) = network {
|
||||
network
|
||||
} else {
|
||||
self.default_network
|
||||
},
|
||||
);
|
||||
if let Some(first_data_network) = first_data_network {
|
||||
let _ = self.add_device_to_network(
|
||||
id,
|
||||
if let Some(network) = network {
|
||||
network
|
||||
} else {
|
||||
self.default_network
|
||||
},
|
||||
first_data_network,
|
||||
);
|
||||
}
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -485,16 +508,28 @@ impl VM {
|
||||
}
|
||||
let id = device.id;
|
||||
let ic_id = ic.id;
|
||||
let first_data_network =
|
||||
device
|
||||
.connections
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(index, conn)| match conn {
|
||||
&Connection::CableNetwork(_) => Some(index),
|
||||
&Connection::Other => None,
|
||||
});
|
||||
self.devices.insert(id, Rc::new(RefCell::new(device)));
|
||||
self.ics.insert(ic_id, Rc::new(RefCell::new(ic)));
|
||||
let _ = self.add_device_to_network(
|
||||
id,
|
||||
if let Some(network) = network {
|
||||
network
|
||||
} else {
|
||||
self.default_network
|
||||
},
|
||||
);
|
||||
if let Some(first_data_network) = first_data_network {
|
||||
let _ = self.add_device_to_network(
|
||||
id,
|
||||
if let Some(network) = network {
|
||||
network
|
||||
} else {
|
||||
self.default_network
|
||||
},
|
||||
first_data_network,
|
||||
);
|
||||
}
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
@@ -682,12 +717,71 @@ impl VM {
|
||||
false
|
||||
}
|
||||
|
||||
fn add_device_to_network(&self, id: u16, network_id: u16) -> Result<bool, VMError> {
|
||||
self.set_modified(id);
|
||||
if !self.devices.contains_key(&id) {
|
||||
/// return a vecter with the device ids the source id can see via it's connected netowrks
|
||||
pub fn visible_devices(&self, source: u16) -> Vec<u16> {
|
||||
self.networks
|
||||
.values()
|
||||
.filter_map(|net| {
|
||||
if net.borrow().contains(&[source]) {
|
||||
Some(
|
||||
net.borrow()
|
||||
.devices
|
||||
.iter()
|
||||
.filter(|id| id != &&source)
|
||||
.copied()
|
||||
.collect_vec(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.concat()
|
||||
}
|
||||
|
||||
pub fn set_pin(&self, id: u16, pin: usize, val: Option<u16>) -> Result<bool, VMError> {
|
||||
let Some(device) = self.devices.get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
if let Some(other_device) = val {
|
||||
if !self.devices.contains_key(&other_device) {
|
||||
return Err(VMError::UnknownId(other_device));
|
||||
}
|
||||
if !self.devices_on_same_network(&[id, other_device]) {
|
||||
return Err(VMError::DeviceNotVisible(other_device, id));
|
||||
}
|
||||
}
|
||||
if !(0..7).contains(&pin) {
|
||||
Err(ICError::PinIndexOutOfRange(pin).into())
|
||||
} else {
|
||||
let Some(ic_id) = device.borrow().ic else {
|
||||
return Err(VMError::NoIC(id));
|
||||
};
|
||||
self.ics.get(&ic_id).unwrap().borrow_mut().pins[pin] = val;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_device_to_network(
|
||||
&self,
|
||||
id: u16,
|
||||
network_id: u16,
|
||||
connection: usize,
|
||||
) -> Result<bool, VMError> {
|
||||
if let Some(network) = self.networks.get(&network_id) {
|
||||
let Some(device) = self.devices.get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
if connection >= device.borrow().connections.len() {
|
||||
let conn_len = device.borrow().connections.len();
|
||||
return Err(ICError::ConnectionIndexOutOfRange(connection, conn_len).into());
|
||||
}
|
||||
let Connection::CableNetwork(ref mut conn) =
|
||||
device.borrow_mut().connections[connection]
|
||||
else {
|
||||
return Err(ICError::NotDataConnection(connection).into());
|
||||
};
|
||||
*conn = Some(network_id);
|
||||
|
||||
network.borrow_mut().add(id);
|
||||
Ok(true)
|
||||
} else {
|
||||
@@ -695,6 +789,27 @@ impl VM {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_device_from_network(&self, id: u16, network_id: u16) -> Result<bool, VMError> {
|
||||
if let Some(network) = self.networks.get(&network_id) {
|
||||
let Some(device) = self.devices.get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
let mut device_ref = device.borrow_mut();
|
||||
|
||||
for conn in device_ref.connections.iter_mut() {
|
||||
if let Connection::CableNetwork(conn) = conn {
|
||||
if Some(network_id) == *conn {
|
||||
*conn = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
network.borrow_mut().remove(id);
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(VMError::InvalidNetwork(network_id))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_batch_device_field(
|
||||
&self,
|
||||
source: u16,
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
mod utils;
|
||||
mod types;
|
||||
|
||||
use types::{Stack, Registers};
|
||||
use ic10emu::{
|
||||
grammar::{LogicType, SlotLogicType},
|
||||
Connection,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use types::{Registers, Stack};
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell, rc::Rc, str::FromStr};
|
||||
|
||||
use itertools::Itertools;
|
||||
// use itertools::Itertools;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
fn alert(s: &str);
|
||||
@@ -22,6 +26,16 @@ pub struct DeviceRef {
|
||||
vm: Rc<RefCell<ic10emu::VM>>,
|
||||
}
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, Serialize, Deserialize)]
|
||||
pub enum BindingError {
|
||||
#[error("{0} is not a valid variant")]
|
||||
InvalidEnumVariant(String),
|
||||
#[error("Index {0} is out of range {1}")]
|
||||
OutOfBounds(usize, usize),
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceRef {
|
||||
fn from_device(device: Rc<RefCell<ic10emu::Device>>, vm: Rc<RefCell<ic10emu::VM>>) -> Self {
|
||||
@@ -75,118 +89,81 @@ impl DeviceRef {
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "ip")]
|
||||
pub fn ic_ip(&self) -> Option<u32> {
|
||||
self.device
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().ip)
|
||||
})
|
||||
self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().ip)
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "instructionCount")]
|
||||
pub fn ic_instruction_count(&self) -> Option<u16> {
|
||||
self.device
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().ic)
|
||||
})
|
||||
self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().ic)
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "stack")]
|
||||
pub fn ic_stack(&self) -> Option<Stack> {
|
||||
self.device
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| Stack(ic.as_ref().borrow().stack))
|
||||
})
|
||||
self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| Stack(ic.as_ref().borrow().stack))
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "registers")]
|
||||
pub fn ic_registers(&self) -> Option<Registers> {
|
||||
self.device
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| Registers(ic.as_ref().borrow().registers))
|
||||
})
|
||||
self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| Registers(ic.as_ref().borrow().registers))
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "aliases", skip_typescript)]
|
||||
pub fn ic_aliases(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(
|
||||
&self
|
||||
.device
|
||||
serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().aliases.clone())
|
||||
}),
|
||||
)
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().aliases.clone())
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "defines", skip_typescript)]
|
||||
pub fn ic_defines(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(
|
||||
&self
|
||||
.device
|
||||
serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().defines.clone())
|
||||
}),
|
||||
)
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().defines.clone())
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "pins", skip_typescript)]
|
||||
pub fn ic_pins(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(
|
||||
&self
|
||||
.device
|
||||
serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().pins)
|
||||
}),
|
||||
)
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.as_ref().borrow().pins)
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -208,20 +185,13 @@ impl DeviceRef {
|
||||
|
||||
#[wasm_bindgen(getter, js_name = "program", skip_typescript)]
|
||||
pub fn ic_program(&self) -> JsValue {
|
||||
serde_wasm_bindgen::to_value(
|
||||
&self
|
||||
.device
|
||||
serde_wasm_bindgen::to_value(&self.device.borrow().ic.as_ref().and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ic
|
||||
.as_ref()
|
||||
.and_then(|ic| {
|
||||
self.vm
|
||||
.borrow()
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.borrow().program.clone())
|
||||
}),
|
||||
)
|
||||
.ics
|
||||
.get(ic)
|
||||
.map(|ic| ic.borrow().program.clone())
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -275,7 +245,7 @@ impl DeviceRef {
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setStack")]
|
||||
pub fn ic_set_stack(&mut self, address: f64, val: f64) -> Result<f64, JsError> {
|
||||
pub fn ic_set_stack(&self, address: f64, val: f64) -> Result<f64, JsError> {
|
||||
let ic_id = *self
|
||||
.device
|
||||
.borrow()
|
||||
@@ -290,6 +260,77 @@ impl DeviceRef {
|
||||
let result = ic.borrow_mut().poke(address, val)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setName")]
|
||||
pub fn set_name(&self, name: &str) {
|
||||
self.device.borrow_mut().set_name(name)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setField")]
|
||||
pub fn set_field(&self, field: &str, value: f64) -> Result<f64, JsError> {
|
||||
let logic_typ = LogicType::from_str(field)?;
|
||||
let mut device_ref = self.device.borrow_mut();
|
||||
let logic_field = device_ref
|
||||
.fields
|
||||
.get_mut(&logic_typ)
|
||||
.ok_or_else(|| BindingError::InvalidEnumVariant(field.to_owned()))?;
|
||||
let last = logic_field.value;
|
||||
logic_field.value = value;
|
||||
Ok(last)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setSlotField")]
|
||||
pub fn set_slot_field(&self, slot: usize, field: &str, value: f64) -> Result<f64, JsError> {
|
||||
let logic_typ = SlotLogicType::from_str(field)?;
|
||||
let mut device_ref = self.device.borrow_mut();
|
||||
let slots_len = device_ref.slots.len();
|
||||
let slot = device_ref
|
||||
.slots
|
||||
.get_mut(slot)
|
||||
.ok_or(BindingError::OutOfBounds(slot, slots_len))?;
|
||||
let logic_field = slot
|
||||
.fields
|
||||
.get_mut(&logic_typ)
|
||||
.ok_or_else(|| BindingError::InvalidEnumVariant(field.to_owned()))?;
|
||||
let last = logic_field.value;
|
||||
logic_field.value = value;
|
||||
Ok(last)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setConnection")]
|
||||
pub fn set_connection(&self, conn: usize, net: Option<u16>) -> Result<(), JsError> {
|
||||
let mut device_ref = self.device.borrow_mut();
|
||||
let conn_len = device_ref.connections.len();
|
||||
let conn_ref = device_ref
|
||||
.connections
|
||||
.get_mut(conn)
|
||||
.ok_or(BindingError::OutOfBounds(conn, conn_len))?;
|
||||
match conn_ref {
|
||||
&mut Connection::CableNetwork(ref mut net_ref) => *net_ref = net,
|
||||
_ => {
|
||||
*conn_ref = Connection::CableNetwork(net);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "addDeviceToNetwork")]
|
||||
pub fn add_device_to_network(&self, network_id: u16, connection: usize) -> Result<bool, JsError> {
|
||||
let id = self.device.borrow().id;
|
||||
Ok(self.vm.borrow().add_device_to_network(id, network_id, connection)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "removeDeviceFromNetwork")]
|
||||
pub fn remove_device_from_network(&self, network_id: u16) -> Result<bool, JsError> {
|
||||
let id = self.device.borrow().id;
|
||||
Ok(self.vm.borrow().remove_device_from_network(id, network_id)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setPin")]
|
||||
pub fn set_pin(&self, pin: usize, val: Option<u16>) -> Result<bool, JsError> {
|
||||
let id = self.device.borrow().id;
|
||||
Ok(self.vm.borrow().set_pin(id, pin, val)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@@ -370,6 +411,27 @@ impl VM {
|
||||
self.vm.borrow().last_operation_modified()
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "visibleDevices")]
|
||||
pub fn visible_devices(&self, source: u16) -> Vec<u16> {
|
||||
self.vm.borrow().visible_devices(source)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "addDeviceToNetwork")]
|
||||
pub fn add_device_to_network(&self, id: u16, network_id: u16, connection: usize) -> Result<bool, JsError> {
|
||||
Ok(self.vm.borrow().add_device_to_network(id, network_id, connection)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "removeDeviceFromNetwork")]
|
||||
pub fn remove_device_from_network(&self, id: u16, network_id: u16) -> Result<bool, JsError> {
|
||||
Ok(self.vm.borrow().remove_device_from_network(id, network_id)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setPin")]
|
||||
pub fn set_pin(&self, id: u16, pin: usize, val: Option<u16>) -> Result<bool, JsError> {
|
||||
Ok(self.vm.borrow().set_pin(id, pin, val)?)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
impl Default for VM {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { CSSResultGroup, LitElement, css, unsafeCSS } from "lit";
|
||||
import shoelaceDark from "@shoelace-style/shoelace/dist/themes/dark.styles.js";
|
||||
|
||||
|
||||
export const defaultCss = [
|
||||
shoelaceDark,
|
||||
css`
|
||||
|
||||
@@ -157,3 +157,23 @@ export async function openFile(editor: Ace.Editor) {
|
||||
input.click();
|
||||
}
|
||||
}
|
||||
|
||||
export function parseNumber(s: string): number {
|
||||
switch (s.toLowerCase()) {
|
||||
case 'nan':
|
||||
return Number.NaN;
|
||||
case 'pinf':
|
||||
return Number.POSITIVE_INFINITY;
|
||||
case 'ninf':
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
case 'pi':
|
||||
return 3.141592653589793;
|
||||
case 'deg2rad':
|
||||
return 0.0174532923847437;
|
||||
case 'rad2deg':
|
||||
return 57.2957801818848;
|
||||
case 'epsilon':
|
||||
return Number.EPSILON;
|
||||
}
|
||||
return parseFloat(s);
|
||||
}
|
||||
|
||||
@@ -149,7 +149,6 @@ export class VMActiveIC extends VMBaseDevice {
|
||||
"session-active-ic",
|
||||
this._handleActiveIC.bind(this),
|
||||
);
|
||||
this.updateIC();
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user