feat:(slots UI): functional slots
This commit is contained in:
@@ -152,13 +152,17 @@ impl SlotOccupant {
|
|||||||
copy
|
copy
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_field(
|
pub fn set_field(&mut self, typ: SlotLogicType, val: f64, force: bool) -> Result<(), ICError> {
|
||||||
&mut self,
|
if (typ == SlotLogicType::Quantity) && force {
|
||||||
field: SlotLogicType,
|
self.quantity = val as u32;
|
||||||
val: f64,
|
Ok(())
|
||||||
force: bool,
|
} else if (typ == SlotLogicType::MaxQuantity) && force {
|
||||||
) -> Result<(), ICError> {
|
self.max_quantity = val as u32;
|
||||||
if let Some(logic) = self.fields.get_mut(&field) {
|
Ok(())
|
||||||
|
} else if (typ == SlotLogicType::Damage) && force {
|
||||||
|
self.damage = val;
|
||||||
|
Ok(())
|
||||||
|
} else if let Some(logic) = self.fields.get_mut(&typ) {
|
||||||
match logic.field_type {
|
match logic.field_type {
|
||||||
FieldType::ReadWrite | FieldType::Write => {
|
FieldType::ReadWrite | FieldType::Write => {
|
||||||
logic.value = val;
|
logic.value = val;
|
||||||
@@ -169,13 +173,13 @@ impl SlotOccupant {
|
|||||||
logic.value = val;
|
logic.value = val;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ICError::ReadOnlyField(field.to_string()))
|
Err(ICError::ReadOnlyField(typ.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if force {
|
} else if force {
|
||||||
self.fields.insert(
|
self.fields.insert(
|
||||||
field,
|
typ,
|
||||||
LogicField {
|
LogicField {
|
||||||
field_type: FieldType::ReadWrite,
|
field_type: FieldType::ReadWrite,
|
||||||
value: val,
|
value: val,
|
||||||
@@ -183,7 +187,7 @@ impl SlotOccupant {
|
|||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ICError::ReadOnlyField(field.to_string()))
|
Err(ICError::ReadOnlyField(typ.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,27 +396,20 @@ impl Slot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_field(
|
pub fn set_field(&mut self, typ: SlotLogicType, val: f64, force: bool) -> Result<(), ICError> {
|
||||||
&mut self,
|
|
||||||
field: SlotLogicType,
|
|
||||||
val: f64,
|
|
||||||
force: bool,
|
|
||||||
) -> Result<(), ICError> {
|
|
||||||
if matches!(
|
if matches!(
|
||||||
field,
|
typ,
|
||||||
SlotLogicType::Occupied
|
SlotLogicType::Occupied
|
||||||
| SlotLogicType::OccupantHash
|
| SlotLogicType::OccupantHash
|
||||||
| SlotLogicType::Quantity
|
|
||||||
| SlotLogicType::MaxQuantity
|
|
||||||
| SlotLogicType::Class
|
| SlotLogicType::Class
|
||||||
| SlotLogicType::PrefabHash
|
| SlotLogicType::PrefabHash
|
||||||
| SlotLogicType::SortingClass
|
| SlotLogicType::SortingClass
|
||||||
| SlotLogicType::ReferenceId
|
| SlotLogicType::ReferenceId
|
||||||
) {
|
) {
|
||||||
return Err(ICError::ReadOnlyField(field.to_string()));
|
return Err(ICError::ReadOnlyField(typ.to_string()));
|
||||||
}
|
}
|
||||||
if let Some(occupant) = self.occupant.as_mut() {
|
if let Some(occupant) = self.occupant.as_mut() {
|
||||||
occupant.set_field(field, val, force)
|
occupant.set_field(typ, val, force)
|
||||||
} else {
|
} else {
|
||||||
Err(ICError::SlotNotOccupied)
|
Err(ICError::SlotNotOccupied)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -768,11 +768,31 @@ impl VM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let occupant = SlotOccupant::from_template(template, || self.id_space.next());
|
let occupant = SlotOccupant::from_template(template, || self.id_space.next());
|
||||||
|
if let Some(last) = slot.occupant.as_ref() {
|
||||||
|
self.id_space.free_id(last.id);
|
||||||
|
}
|
||||||
slot.occupant = Some(occupant);
|
slot.occupant = Some(occupant);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_slot_occupant(&mut self, id: u32, index: usize) -> 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(last) = slot.occupant.as_ref() {
|
||||||
|
self.id_space.free_id(last.id);
|
||||||
|
}
|
||||||
|
slot.occupant = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_vm_state(&self) -> FrozenVM {
|
pub fn save_vm_state(&self) -> FrozenVM {
|
||||||
FrozenVM {
|
FrozenVM {
|
||||||
ics: self.ics.values().map(|ic| ic.borrow().into()).collect(),
|
ics: self.ics.values().map(|ic| ic.borrow().into()).collect(),
|
||||||
|
|||||||
@@ -495,6 +495,11 @@ impl VMRef {
|
|||||||
Ok(self.vm.borrow_mut().set_slot_occupant(id, index, template)?)
|
Ok(self.vm.borrow_mut().set_slot_occupant(id, index, template)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = "removeSlotOccupant")]
|
||||||
|
pub fn remove_slot_occupant(&self, id: u32, index: usize) -> Result<(), JsError> {
|
||||||
|
Ok(self.vm.borrow_mut().remove_slot_occupant(id, index)?)
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = "saveVMState", skip_typescript)]
|
#[wasm_bindgen(js_name = "saveVMState", skip_typescript)]
|
||||||
pub fn save_vm_state(&self) -> JsValue {
|
pub fn save_vm_state(&self) -> JsValue {
|
||||||
let state = self.vm.borrow().save_vm_state();
|
let state = self.vm.borrow().save_vm_state();
|
||||||
|
|||||||
@@ -242,3 +242,7 @@ export function parseIntWithHexOrBinary(s: string): number {
|
|||||||
}
|
}
|
||||||
return parseInt(s);
|
return parseInt(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function clamp (val: number, min: number, max: number) {
|
||||||
|
return Math.min(Math.max(val, min), max);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { BaseElement, defaultCss } from "components";
|
|||||||
import { VMDeviceDBMixin, VMDeviceMixin } from "virtual_machine/base_device";
|
import { VMDeviceDBMixin, VMDeviceMixin } from "virtual_machine/base_device";
|
||||||
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
|
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
|
||||||
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
|
import SlSelect from "@shoelace-style/shoelace/dist/components/select/select.component.js";
|
||||||
import { displayNumber, parseIntWithHexOrBinary, parseNumber } from "utils";
|
import { clamp, displayNumber, parseIntWithHexOrBinary, parseNumber } from "utils";
|
||||||
import {
|
import {
|
||||||
LogicType,
|
LogicType,
|
||||||
Slot,
|
Slot,
|
||||||
@@ -147,6 +147,7 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
|||||||
.value=${slot.occupant.quantity.toString()}
|
.value=${slot.occupant.quantity.toString()}
|
||||||
.min=${1}
|
.min=${1}
|
||||||
.max=${slot.occupant.max_quantity}
|
.max=${slot.occupant.max_quantity}
|
||||||
|
@sl-change=${this._handleSlotQuantityChange}
|
||||||
>
|
>
|
||||||
<div slot="help-text">
|
<div slot="help-text">
|
||||||
<span>Max Quantity: ${slot.occupant.max_quantity}</span>
|
<span>Max Quantity: ${slot.occupant.max_quantity}</span>
|
||||||
@@ -170,6 +171,15 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_handleSlotQuantityChange(e: Event) {
|
||||||
|
const input = e.currentTarget as SlInput;
|
||||||
|
const slot = this.slots[this.slotIndex];
|
||||||
|
const val = clamp(input.valueAsNumber, 1, slot.occupant.max_quantity);
|
||||||
|
if (!window.VM.vm.setDeviceSlotField(this.deviceID, this.slotIndex, "Quantity", val, true)) {
|
||||||
|
input.value = this.device.getSlotField(this.slotIndex, "Quantity").toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderFields() {
|
renderFields() {
|
||||||
const inputIdBase = `vmDeviceSlot${this.deviceID}Slot${this.slotIndex}Field`;
|
const inputIdBase = `vmDeviceSlot${this.deviceID}Slot${this.slotIndex}Field`;
|
||||||
const _fields = this.device.getSlotFields(this.slotIndex);
|
const _fields = this.device.getSlotFields(this.slotIndex);
|
||||||
@@ -202,7 +212,11 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
|||||||
_handleChangeSlotField(e: CustomEvent) {
|
_handleChangeSlotField(e: CustomEvent) {
|
||||||
const input = e.target as SlInput;
|
const input = e.target as SlInput;
|
||||||
const field = input.getAttribute("key")! as SlotLogicType;
|
const field = input.getAttribute("key")! as SlotLogicType;
|
||||||
const val = parseNumber(input.value);
|
let val = parseNumber(input.value);
|
||||||
|
if (field === "Quantity") {
|
||||||
|
const slot = this.slots[this.slotIndex];
|
||||||
|
val = clamp(input.valueAsNumber, 1, slot.occupant.max_quantity);
|
||||||
|
}
|
||||||
window.VM.get().then((vm) => {
|
window.VM.get().then((vm) => {
|
||||||
if (
|
if (
|
||||||
!vm.setDeviceSlotField(this.deviceID, this.slotIndex, field, val, true)
|
!vm.setDeviceSlotField(this.deviceID, this.slotIndex, field, val, true)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
private _items: Map<string, DeviceDBEntry> = new Map();
|
private _items: Map<string, DeviceDBEntry> = new Map();
|
||||||
|
private _filteredItems: DeviceDBEntry[];
|
||||||
private _datapoints: [string, string][] = [];
|
private _datapoints: [string, string][] = [];
|
||||||
private _haystack: string[] = [];
|
private _haystack: string[] = [];
|
||||||
|
|
||||||
@@ -57,33 +58,41 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
|
|||||||
.filter((entry) => this.deviceDB.items.includes(entry.name), this)
|
.filter((entry) => this.deviceDB.items.includes(entry.name), this)
|
||||||
.map((entry) => [entry.name, entry]),
|
.map((entry) => [entry.name, entry]),
|
||||||
);
|
);
|
||||||
|
this.setupSearch();
|
||||||
this.performSearch();
|
this.performSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
performSearch() {
|
|
||||||
if (this._filter) {
|
setupSearch() {
|
||||||
|
let filteredItemss = Array.from(this._items.values());
|
||||||
|
if( typeof this.deviceID !== "undefined" && typeof this.slotIndex !== "undefined") {
|
||||||
const device = window.VM.vm.devices.get(this.deviceID);
|
const device = window.VM.vm.devices.get(this.deviceID);
|
||||||
const dbDevice = this.deviceDB.db[device.prefabName]
|
const dbDevice = this.deviceDB.db[device.prefabName]
|
||||||
const slot = dbDevice.slots[this.slotIndex]
|
const slot = dbDevice.slots[this.slotIndex]
|
||||||
const typ = slot.typ;
|
const typ = slot.typ;
|
||||||
|
|
||||||
let filterdItems = Array.from(this._items.values());
|
if (typeof typ === "string" && typ !== "None") {
|
||||||
if (typ !== "None") {
|
filteredItemss = Array.from(this._items.values()).filter(item => item.item.slotclass === typ);
|
||||||
filterdItems = Array.from(this._items.values()).filter(item => item.item.slotclass === typ);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const datapoints: [string, string][] = [];
|
}
|
||||||
for (const entry of filterdItems) {
|
this._filteredItems= filteredItemss;
|
||||||
datapoints.push(
|
const datapoints: [string, string][] = [];
|
||||||
[entry.title, entry.name],
|
for (const entry of this._filteredItems) {
|
||||||
[entry.name, entry.name],
|
datapoints.push(
|
||||||
[entry.desc, entry.name],
|
[entry.title, entry.name],
|
||||||
);
|
[entry.name, entry.name],
|
||||||
}
|
[entry.desc, entry.name],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const haystack: string[] = datapoints.map((data) => data[0]);
|
const haystack: string[] = datapoints.map((data) => data[0]);
|
||||||
this._datapoints = datapoints;
|
this._datapoints = datapoints;
|
||||||
this._haystack = haystack;
|
this._haystack = haystack;
|
||||||
|
}
|
||||||
|
|
||||||
|
performSearch() {
|
||||||
|
if (this._filter) {
|
||||||
|
|
||||||
const uf = new uFuzzy({});
|
const uf = new uFuzzy({});
|
||||||
const [_idxs, info, order] = uf.search(
|
const [_idxs, info, order] = uf.search(
|
||||||
@@ -113,7 +122,7 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
|
|||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
// return everything
|
// return everything
|
||||||
this._searchResults = [...this._items.values()].map((st) => ({
|
this._searchResults = [...this._filteredItems].map((st) => ({
|
||||||
entry: st,
|
entry: st,
|
||||||
haystackEntry: st.title,
|
haystackEntry: st.title,
|
||||||
ranges: [],
|
ranges: [],
|
||||||
@@ -130,7 +139,7 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
|
|||||||
${this._searchResults.map((result) => {
|
${this._searchResults.map((result) => {
|
||||||
const imgSrc = `img/stationpedia/${result.entry.name}.png`;
|
const imgSrc = `img/stationpedia/${result.entry.name}.png`;
|
||||||
const img = html`
|
const img = html`
|
||||||
<img class="w-8 h-8" src=${imgSrc} onerror="this.src = '${VMDeviceCard.transparentImg}'" />
|
<img class="w-8 h-8 mr-2" src=${imgSrc} onerror="this.src = '${VMDeviceCard.transparentImg}'" />
|
||||||
`;
|
`;
|
||||||
return html`
|
return html`
|
||||||
<div class="cursor-pointer hover:bg-neutral-600 rounded px-2 me-1 flex flex-row" key=${result.entry.name} @click=${this._handleClickItem}>
|
<div class="cursor-pointer hover:bg-neutral-600 rounded px-2 me-1 flex flex-row" key=${result.entry.name} @click=${this._handleClickItem}>
|
||||||
@@ -144,7 +153,8 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_handleClickNone() {
|
_handleClickNone() {
|
||||||
console.log("Clear Slot");
|
window.VM.vm.removeDeviceSlotOccupant(this.deviceID, this.slotIndex);
|
||||||
|
this.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleClickItem(e: Event) {
|
_handleClickItem(e: Event) {
|
||||||
@@ -170,11 +180,13 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
|
|||||||
fields["PrefabHash"] = { field_type: "Read", value: entry.hash };
|
fields["PrefabHash"] = { field_type: "Read", value: entry.hash };
|
||||||
fields["MaxQuantity"] = { field_type: "Read", value: entry.item.maxquantity ?? 1.0 };
|
fields["MaxQuantity"] = { field_type: "Read", value: entry.item.maxquantity ?? 1.0 };
|
||||||
fields["SortingClass"] = { field_type: "Read", value: sorting };
|
fields["SortingClass"] = { field_type: "Read", value: sorting };
|
||||||
|
fields["Quantity"] = { field_type: "Read", value: 1 };
|
||||||
|
|
||||||
const template: SlotOccupantTemplate = {
|
const template: SlotOccupantTemplate = {
|
||||||
fields
|
fields
|
||||||
}
|
}
|
||||||
window.VM.vm.setDeviceSlotOccupant(this.deviceID, this.slotIndex, template);
|
window.VM.vm.setDeviceSlotOccupant(this.deviceID, this.slotIndex, template);
|
||||||
|
this.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
@query("sl-dialog.slot-add-dialog") dialog: SlDialog;
|
@query("sl-dialog.slot-add-dialog") dialog: SlDialog;
|
||||||
@@ -233,6 +245,8 @@ export class VMSlotAddDialog extends VMDeviceDBMixin(BaseElement) {
|
|||||||
show(deviceID: number, slotIndex: number) {
|
show(deviceID: number, slotIndex: number) {
|
||||||
this.deviceID = deviceID;
|
this.deviceID = deviceID;
|
||||||
this.slotIndex = slotIndex;
|
this.slotIndex = slotIndex;
|
||||||
|
this.setupSearch();
|
||||||
|
this.performSearch();
|
||||||
this.dialog.show();
|
this.dialog.show();
|
||||||
this.searchInput.select();
|
this.searchInput.select();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ class VirtualMachine extends EventTarget {
|
|||||||
const device = this._devices.get(id);
|
const device = this._devices.get(id);
|
||||||
if (device) {
|
if (device) {
|
||||||
try {
|
try {
|
||||||
device.setSlotField(slot, field, val, false);
|
device.setSlotField(slot, field, val, force);
|
||||||
this.updateDevice(device);
|
this.updateDevice(device);
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -411,6 +411,20 @@ class VirtualMachine extends EventTarget {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeDeviceSlotOccupant(id: number, index: number): boolean {
|
||||||
|
const device = this._devices.get(id);
|
||||||
|
if (typeof device !== "undefined") {
|
||||||
|
try {
|
||||||
|
this.ic10vm.removeSlotOccupant(id, index);
|
||||||
|
this.updateDevice(device);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
this.handleVmError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
saveVMState(): FrozenVM {
|
saveVMState(): FrozenVM {
|
||||||
return this.ic10vm.saveVMState();
|
return this.ic10vm.saveVMState();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user