refactor(frontend): fix slot occupant and device add components

This commit is contained in:
Rachel Powers
2024-05-30 20:47:17 -07:00
parent 10997d4185
commit 337ca50560
4 changed files with 189 additions and 133 deletions

View File

@@ -3,7 +3,7 @@ mod utils;
// mod types;
use ic10emu::{
errors::{ICError, VMError},
errors::{ICError, TemplateError, VMError},
network::FrozenCableNetwork,
vm::{
object::{
@@ -288,11 +288,42 @@ impl VMRef {
frozen: FrozenObject,
quantity: u32,
) -> Result<Option<ObjectID>, JsError> {
let Some(prefab) = frozen.obj_info.prefab.as_ref() else {
return Err(TemplateError::MissingPrefab.into());
};
let obj_id = if let Some(obj) = frozen.obj_info.id.and_then(|id| self.vm.get_object(id)) {
// TODO: we just assume if the ID is found that the frozen object passed is the same object..
obj.get_id()
} else {
self.vm.add_object_frozen(frozen)?
// check to see if frozen is using the same prefab as current occupant
let obj_id = if let Some(occupant_id) = {
let obj = self.vm.get_object(id).ok_or(VMError::UnknownId(id))?;
let obj_ref = obj.borrow();
let storage = obj_ref.as_storage().ok_or(VMError::NotStorage(id))?;
let slot = storage
.get_slot(index)
.ok_or(ICError::SlotIndexOutOfRange(index as f64))?;
slot.occupant.as_ref().map(|info| info.id)
} {
let occupant = self
.vm
.get_object(id)
.ok_or(VMError::UnknownId(occupant_id))?;
let occupant_ref = occupant.borrow();
let occupant_prefab = occupant_ref.get_prefab();
if prefab.as_str() == occupant_prefab.value.as_str() {
Some(*occupant_ref.get_id())
} else {
None
}
} else {
None
};
if let Some(obj_id) = obj_id {
obj_id
} else {
self.vm.add_object_frozen(frozen)?
}
};
Ok(self
.vm

View File

@@ -1,4 +1,4 @@
import type { ICError, FrozenVM, SlotType } from "ic10emu_wasm";
import type { ICError, FrozenVM, Class } from "ic10emu_wasm";
import { App } from "./app";
import { openDB, DBSchema } from "idb";
@@ -230,7 +230,7 @@ export class Session extends EventTarget {
async saveLocal(name: string) {
const state: VMState = {
vm: (await window.VM.get()).ic10vm.saveVMState(),
vm: await (await window.VM.get()).ic10vm.saveVMState(),
activeIC: this.activeIC,
};
const db = await this.openIndexDB();

View File

@@ -1,4 +1,3 @@
import { html, css } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { BaseElement, defaultCss } from "components";
@@ -6,14 +5,18 @@ import { BaseElement, defaultCss } from "components";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
import SlDrawer from "@shoelace-style/shoelace/dist/components/drawer/drawer.js";
import type { 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 { VMTemplateDBMixin } from "virtual_machine/base_device";
import { LogicInfo, ObjectTemplate, StructureInfo } from "ic10emu_wasm";
type LogicableStrucutureTemplate = Extract<
ObjectTemplate,
{ structure: StructureInfo; logic: LogicInfo }
>;
@customElement("vm-add-device-button")
export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
@@ -35,27 +38,30 @@ export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
@query("sl-drawer") drawer: SlDrawer;
@query(".device-search-input") searchInput: SlInput;
private _structures: Map<string, DeviceDBEntry> = new Map();
private _structures: Map<string, LogicableStrucutureTemplate> = new Map();
private _datapoints: [string, string][] = [];
private _haystack: string[] = [];
postDBSetUpdate(): void {
this._structures = new Map(
Object.values(this.templateDB.db)
.filter((entry) => this.templateDB.structures.includes(entry.name), this)
.filter(
(entry) => this.templateDB.logic_enabled.includes(entry.name),
this,
)
.map((entry) => [entry.name, entry]),
Array.from(this.templateDB.values()).flatMap((template) => {
if ("structure" in template && "logic" in template) {
return [[template.prefab.prefab_name, template]] as [
string,
LogicableStrucutureTemplate,
][];
} else {
return [] as [string, LogicableStrucutureTemplate][];
}
}),
);
const datapoints: [string, string][] = [];
for (const entry of this._structures.values()) {
datapoints.push(
[entry.title, entry.name],
[entry.name, entry.name],
[entry.desc, entry.name],
[entry.prefab.name, entry.prefab.prefab_name],
[entry.prefab.prefab_name, entry.prefab.prefab_name],
[entry.prefab.desc, entry.prefab.prefab_name],
);
}
const haystack: string[] = datapoints.map((data) => data[0]);
@@ -78,7 +84,7 @@ export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
}
private _searchResults: {
entry: DeviceDBEntry;
entry: LogicableStrucutureTemplate;
haystackEntry: string;
ranges: number[];
}[] = [];
@@ -116,7 +122,7 @@ export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
// return everything
this._searchResults = [...this._structures.values()].map((st) => ({
entry: st,
haystackEntry: st.title,
haystackEntry: st.prefab.prefab_name,
ranges: [],
}));
}
@@ -143,28 +149,28 @@ export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
const totalPages = Math.ceil((this._searchResults?.length ?? 0) / perPage);
let pageKeys = Array.from({ length: totalPages }, (_, index) => index);
const extra: {
entry: { title: string; name: string };
entry: { prefab: { name: string; prefab_name: string } };
haystackEntry: string;
ranges: number[];
}[] = [];
if (this.page < totalPages - 1) {
extra.push({
entry: { title: "", name: this.filter },
entry: { prefab: { name: "", prefab_name: this.filter } },
haystackEntry: "...",
ranges: [],
});
}
return when(
typeof this._searchResults !== "undefined" &&
this._searchResults.length < 20,
this._searchResults.length < 20,
() =>
repeat(
this._searchResults ?? [],
(result) => result.entry.name,
(result) => result.entry.prefab.prefab_name,
(result) =>
cache(html`
<vm-device-template
prefab_name=${result.entry.name}
prefab_name=${result.entry.prefab.prefab_name}
class="card"
@add-device-template=${this._handleDeviceAdd}
>
@@ -183,46 +189,46 @@ export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
<div class="p-2 ml-2">
Page:
${pageKeys.map(
(key, index) => html`
(key, index) => html`
<span
class="p-2 cursor-pointer hover:text-purple-400 ${index ===
this.page
? " text-purple-500"
: ""}"
this.page
? " text-purple-500"
: ""}"
key=${key}
@click=${this._handlePageChange}
>${key + 1}${index < totalPages - 1 ? "," : ""}</span
>
`,
)}
)}
</div>
</div>
<div class="flex flex-row flex-wrap">
${[
...this._searchResults.slice(
perPage * this.page,
perPage * this.page + perPage,
),
...extra,
].map((result) => {
let hay = result.haystackEntry.slice(0, 15);
if (result.haystackEntry.length > 15) hay += "...";
const ranges = result.ranges.filter((pos) => pos < 20);
const key = result.entry.name;
return html`
...this._searchResults.slice(
perPage * this.page,
perPage * this.page + perPage,
),
...extra,
].map((result) => {
let hay = result.haystackEntry.slice(0, 15);
if (result.haystackEntry.length > 15) hay += "...";
const ranges = result.ranges.filter((pos) => pos < 20);
const key = result.entry.prefab.prefab_name;
return html`
<div
class="m-2 text-neutral-200/90 italic cursor-pointer rounded bg-neutral-700 hover:bg-purple-500 px-1"
key=${key}
@click=${this._handleHaystackClick}
>
${result.entry.title} (<small class="text-sm">
${result.entry.prefab.name} (<small class="text-sm">
${ranges.length
? unsafeHTML(uFuzzy.highlight(hay, ranges))
: hay} </small
? unsafeHTML(uFuzzy.highlight(hay, ranges))
: hay} </small
>)
</div>
`;
})}
})}
</div>
</div>
`,
@@ -278,8 +284,8 @@ export class VMAddDeviceButton extends VMTemplateDBMixin(BaseElement) {
slot="footer"
variant="primary"
@click=${() => {
this.drawer.hide();
}}
this.drawer.hide();
}}
>
Close
</sl-button>

View File

@@ -2,13 +2,21 @@ import { html, css } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { BaseElement, defaultCss } from "components";
import { VMTemplateDBMixin } from "virtual_machine/base_device";
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.component.js";
import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.component.js";
import { VMDeviceCard } from "./card";
import { when } from "lit/directives/when.js";
import uFuzzy from "@leeoniya/ufuzzy";
import { LogicField, LogicSlotType, SlotOccupantTemplate } from "ic10emu_wasm";
import {
FrozenObject,
ItemInfo,
LogicField,
LogicSlotType,
ObjectInfo,
ObjectTemplate,
} from "ic10emu_wasm";
type SlotableItemTemplate = Extract<ObjectTemplate, { item: ItemInfo }>;
@customElement("vm-slot-add-dialog")
export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
@@ -30,8 +38,8 @@ export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
`,
];
private _items: Map<string, DeviceDBEntry> = new Map();
private _filteredItems: DeviceDBEntry[];
private _items: Map<string, SlotableItemTemplate> = new Map();
private _filteredItems: SlotableItemTemplate[];
private _datapoints: [string, string][] = [];
private _haystack: string[] = [];
@@ -47,42 +55,52 @@ export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
}
private _searchResults: {
entry: DeviceDBEntry;
entry: SlotableItemTemplate;
haystackEntry: string;
ranges: number[];
}[] = [];
postDBSetUpdate(): void {
this._items = new Map(
Object.values(this.templateDB.db)
.filter((entry) => this.templateDB.items.includes(entry.name), this)
.map((entry) => [entry.name, entry]),
Array.from(this.templateDB.values()).flatMap((template) => {
if ("item" in template) {
return [[template.prefab.prefab_name, template]] as [
string,
SlotableItemTemplate,
][];
} else {
return [] as [string, SlotableItemTemplate][];
}
}),
);
this.setupSearch();
this.performSearch();
}
setupSearch() {
let filteredItemss = Array.from(this._items.values());
if( typeof this.deviceID !== "undefined" && typeof this.slotIndex !== "undefined") {
const device = window.VM.vm.objects.get(this.deviceID);
const dbDevice = this.templateDB.db[device.prefabName]
const slot = dbDevice.slots[this.slotIndex]
let filteredItems = Array.from(this._items.values());
if (
typeof this.objectID !== "undefined" &&
typeof this.slotIndex !== "undefined"
) {
const obj = window.VM.vm.objects.get(this.objectID);
const template = obj.template;
const slot = "slots" in template ? template.slots[this.slotIndex] : null;
const typ = slot.typ;
if (typeof typ === "string" && typ !== "None") {
filteredItemss = Array.from(this._items.values()).filter(item => item.item.slotclass === typ);
filteredItems = Array.from(this._items.values()).filter(
(item) => item.item.slot_class === typ,
);
}
}
this._filteredItems= filteredItemss;
this._filteredItems = filteredItems;
const datapoints: [string, string][] = [];
for (const entry of this._filteredItems) {
datapoints.push(
[entry.title, entry.name],
[entry.name, entry.name],
[entry.desc, entry.name],
[entry.prefab.name, entry.prefab.prefab_name],
[entry.prefab.prefab_name, entry.prefab.prefab_name],
[entry.prefab.desc, entry.prefab.prefab_name],
);
}
@@ -93,7 +111,6 @@ export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
performSearch() {
if (this._filter) {
const uf = new uFuzzy({});
const [_idxs, info, order] = uf.search(
this._haystack,
@@ -102,18 +119,17 @@ export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
1e3,
);
const filtered = order?.map((infoIdx) => ({
name: this._datapoints[info.idx[infoIdx]][1],
haystackEntry: this._haystack[info.idx[infoIdx]],
ranges: info.ranges[infoIdx],
})) ?? [];
const filtered =
order?.map((infoIdx) => ({
name: this._datapoints[info.idx[infoIdx]][1],
haystackEntry: this._haystack[info.idx[infoIdx]],
ranges: info.ranges[infoIdx],
})) ?? [];
const uniqueNames = new Set(filtered.map((obj) => obj.name));
const unique = [...uniqueNames].map(
(result) => {
return filtered.find((obj) => obj.name === result);
},
);
const unique = [...uniqueNames].map((result) => {
return filtered.find((obj) => obj.name === result);
});
this._searchResults = unique.map(({ name, haystackEntry, ranges }) => ({
entry: this._items.get(name)!,
@@ -124,7 +140,7 @@ export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
// return everything
this._searchResults = [...this._filteredItems].map((st) => ({
entry: st,
haystackEntry: st.title,
haystackEntry: st.prefab.prefab_name,
ranges: [],
}));
}
@@ -133,63 +149,61 @@ export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
renderSearchResults() {
const enableNone = false;
const none = html`
<div class="cursor-pointer hover:bg-neutral-600 rounded px-2 py-1 me-1" @click=${this._handleClickNone}>
None
</div>
<div
class="cursor-pointer hover:bg-neutral-600 rounded px-2 py-1 me-1"
@click=${this._handleClickNone}
>
None
</div>
`;
return html`
<div class="mt-2 max-h-48 overflow-y-auto w-full">
${enableNone ? none : ""}
${this._searchResults.map((result) => {
const imgSrc = `img/stationpedia/${result.entry.name}.png`;
const img = html`
<img class="w-8 h-8 mr-2" src=${imgSrc} onerror="this.src = '${VMDeviceCard.transparentImg}'" />
`;
return html`
<div class="cursor-pointer hover:bg-neutral-600 rounded px-2 py-1 me-1 flex flex-row" key=${result.entry.name} @click=${this._handleClickItem}>
${img}
<div>${result.entry.title}</div>
</div>
`;
})}
const imgSrc = `img/stationpedia/${result.entry.prefab.prefab_name}.png`;
const img = html`
<img
class="w-8 h-8 mr-2"
src=${imgSrc}
onerror="this.src = '${VMDeviceCard.transparentImg}'"
/>
`;
return html`
<div
class="cursor-pointer hover:bg-neutral-600 rounded px-2 py-1 me-1 flex flex-row"
key=${result.entry.prefab.prefab_hash.toString()}
@click=${this._handleClickItem}
>
${img}
<div>${result.entry.prefab.name}</div>
</div>
`;
})}
</div>
`;
}
_handleClickNone() {
window.VM.vm.removeSlotOccupant(this.deviceID, this.slotIndex);
window.VM.vm.removeSlotOccupant(this.objectID, this.slotIndex);
this.hide();
}
_handleClickItem(e: Event) {
const div = e.currentTarget as HTMLDivElement;
const key = div.getAttribute("key");
const entry = this.templateDB.db[key];
const device = window.VM.vm.objects.get(this.deviceID);
const dbDevice = this.templateDB.db[device.prefabName]
const sorting = this.templateDB.enums["SortingClass"][entry.item.sorting ?? "Default"] ?? 0;
console.log("using entry", dbDevice);
const fields: { [key in LogicSlotType]?: LogicField } = Object.fromEntries(
Object.entries(dbDevice.slotlogic[this.slotIndex] ?? {})
.map(([slt_s, field_type]) => {
let slt = slt_s as LogicSlotType;
let value = 0.0
if (slt === "FilterType") {
value = this.templateDB.enums["GasType"][entry.item.filtertype]
}
const field: LogicField = { field_type, value};
return [slt, field];
})
);
fields["PrefabHash"] = { field_type: "Read", value: entry.hash };
fields["MaxQuantity"] = { field_type: "Read", value: entry.item.maxquantity ?? 1.0 };
fields["SortingClass"] = { field_type: "Read", value: sorting };
fields["Quantity"] = { field_type: "Read", value: 1 };
const key = parseInt(div.getAttribute("key"));
const entry = this.templateDB.get(key) as SlotableItemTemplate;
const obj = window.VM.vm.objects.get(this.objectID);
const dbTemplate = obj.template;
console.log("using entry", dbTemplate);
const template: SlotOccupantTemplate = {
fields
}
window.VM.vm.setSlotOccupant(this.deviceID, this.slotIndex, template);
const template: FrozenObject = {
obj_info: {
prefab: entry.prefab.prefab_name,
} as ObjectInfo,
database_template: true,
template: undefined,
};
window.VM.vm.setSlotOccupant(this.objectID, this.slotIndex, template, 1);
this.hide();
}
@@ -197,30 +211,35 @@ export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
@query(".device-search-input") searchInput: SlInput;
render() {
const device = window.VM.vm.objects.get(this.deviceID);
const name = device?.name ?? device?.prefabName ?? "";
const id = this.deviceID ?? 0;
const device = window.VM.vm.objects.get(this.objectID);
const name = device?.obj_info.name ?? device?.obj_info.prefab ?? "";
const id = this.objectID ?? 0;
return html`
<sl-dialog
label="Edit device ${id} : ${name} Slot ${this.slotIndex}"
class="slot-add-dialog"
@sl-hide=${this._handleDialogHide}
>
<sl-input class="device-search-input" autofocus placeholder="filter" clearable
@sl-input=${this._handleSearchInput}>
<sl-input
class="device-search-input"
autofocus
placeholder="filter"
clearable
@sl-input=${this._handleSearchInput}
>
<span slot="prefix">Search Items</span>
<sl-icon slot="suffix" name="search"></sl-icon>
</sl-input>
${when(
typeof this.deviceID !== "undefined" &&
typeof this.slotIndex !== "undefined",
() => html`
typeof this.objectID !== "undefined" &&
typeof this.slotIndex !== "undefined",
() => html`
<div class="flex flex-row overflow-x-auto">
${this.renderSearchResults()}
</div>
`,
() => html``,
)}
() => html``,
)}
</sl-dialog>
`;
}
@@ -239,15 +258,15 @@ export class VMSlotAddDialog extends VMTemplateDBMixin(BaseElement) {
}
_handleDialogHide() {
this.deviceID = undefined;
this.objectID = undefined;
this.slotIndex = undefined;
}
@state() private deviceID: number;
@state() private objectID: number;
@state() private slotIndex: number;
show(deviceID: number, slotIndex: number) {
this.deviceID = deviceID;
show(objectID: number, slotIndex: number) {
this.objectID = objectID;
this.slotIndex = slotIndex;
this.setupSearch();
this.performSearch();