feat(slots UI): better slot UI
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
device::{Device, DeviceTemplate},
|
||||
device::{Device, DeviceTemplate, SlotOccupant, SlotOccupantTemplate},
|
||||
grammar::{BatchMode, LogicType, SlotLogicType},
|
||||
interpreter::{self, FrozenIC, ICError, LineError},
|
||||
network::{CableConnectionType, Connection, FrozenNetwork, Network},
|
||||
@@ -747,6 +747,32 @@ impl VM {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_slot_occupant(
|
||||
&mut self,
|
||||
id: u32,
|
||||
index: usize,
|
||||
template: SlotOccupantTemplate,
|
||||
) -> Result<(), VMError> {
|
||||
let Some(device) = self.devices.get(&id) else {
|
||||
return Err(VMError::UnknownId(id));
|
||||
};
|
||||
|
||||
let mut device_ref = device.borrow_mut();
|
||||
let slot = device_ref
|
||||
.slots
|
||||
.get_mut(index)
|
||||
.ok_or(ICError::SlotIndexOutOfRange(index as f64))?;
|
||||
|
||||
if let Some(id) = template.id.as_ref() {
|
||||
self.id_space.use_id(*id)?;
|
||||
}
|
||||
|
||||
let occupant = SlotOccupant::from_template(template, || self.id_space.next());
|
||||
slot.occupant = Some(occupant);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn save_vm_state(&self) -> FrozenVM {
|
||||
FrozenVM {
|
||||
ics: self.ics.values().map(|ic| ic.borrow().into()).collect(),
|
||||
|
||||
@@ -3,7 +3,7 @@ mod utils;
|
||||
mod types;
|
||||
|
||||
use ic10emu::{
|
||||
device::{Device, DeviceTemplate},
|
||||
device::{Device, DeviceTemplate, SlotOccupantTemplate},
|
||||
grammar::{LogicType, SlotLogicType},
|
||||
vm::{FrozenVM, VMError, VM},
|
||||
};
|
||||
@@ -489,6 +489,12 @@ impl VMRef {
|
||||
Ok(self.vm.borrow_mut().remove_device(id)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "setSlotOccupant", skip_typescript)]
|
||||
pub fn set_slot_occupant(&self, id: u32, index: usize, template: JsValue) -> Result<(), JsError> {
|
||||
let template: SlotOccupantTemplate = serde_wasm_bindgen::from_value(template)?;
|
||||
Ok(self.vm.borrow_mut().set_slot_occupant(id, index, template)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = "saveVMState", skip_typescript)]
|
||||
pub fn save_vm_state(&self) -> JsValue {
|
||||
let state = self.vm.borrow().save_vm_state();
|
||||
|
||||
@@ -177,6 +177,7 @@ export interface FrozenVM {
|
||||
|
||||
export interface VMRef {
|
||||
addDeviceFromTemplate(template: DeviceTemplate): number;
|
||||
setSlotOccupant(id: number, index: number, template: SlotOccupantTemplate);
|
||||
saveVMState(): FrozenVM;
|
||||
restoreVMState(state: FrozenVM): void;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export class App extends BaseElement {
|
||||
window.App.set(this);
|
||||
}
|
||||
|
||||
protected createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
const root = super.createRenderRoot();
|
||||
root.addEventListener("app-share-session", this._handleShare.bind(this));
|
||||
root.addEventListener("app-open-file", this._handleOpenFile.bind(this));
|
||||
|
||||
@@ -94,6 +94,22 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
|
||||
return root;
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
window.VM.get().then((vm) =>
|
||||
vm.removeEventListener(
|
||||
"vm-device-modified",
|
||||
this._handleDeviceModified.bind(this),
|
||||
),
|
||||
);
|
||||
window.VM.get().then((vm) =>
|
||||
vm.removeEventListener(
|
||||
"vm-devices-update",
|
||||
this._handleDevicesModified.bind(this),
|
||||
),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
_handleDeviceModified(e: CustomEvent) {
|
||||
const id = e.detail;
|
||||
if (this.deviceID === id) {
|
||||
@@ -182,7 +198,6 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
|
||||
this.pins = pins;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return VMDeviceMixinClass as Constructor<VMDeviceMixinInterface> & T;
|
||||
};
|
||||
@@ -208,6 +223,17 @@ export const VMActiveICMixin = <T extends Constructor<LitElement>>(
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
disconnectedCallback(): void {
|
||||
window.VM.get().then((vm) =>
|
||||
vm.removeEventListener("vm-run-ic", this._handleDeviceModified.bind(this)),
|
||||
);
|
||||
window.App.app.session.removeEventListener(
|
||||
"session-active-ic",
|
||||
this._handleActiveIC.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
_handleActiveIC(e: CustomEvent) {
|
||||
const id = e.detail;
|
||||
if (this.deviceID !== id) {
|
||||
@@ -223,22 +249,33 @@ export const VMActiveICMixin = <T extends Constructor<LitElement>>(
|
||||
|
||||
export declare class VMDeviceDBMixinInterface {
|
||||
deviceDB: DeviceDB;
|
||||
_handleDeviceDBLoad(e: CustomEvent): void
|
||||
_handleDeviceDBLoad(e: CustomEvent): void;
|
||||
postDBSetUpdate(): void;
|
||||
}
|
||||
|
||||
export const VMDeviceDBMixin = <T extends Constructor<LitElement>>(superClass: T) => {
|
||||
export const VMDeviceDBMixin = <T extends Constructor<LitElement>>(
|
||||
superClass: T,
|
||||
) => {
|
||||
class VMDeviceDBMixinClass extends superClass {
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
window.VM.vm.addEventListener(
|
||||
"vm-device-db-loaded",
|
||||
this._handleDeviceDBLoad.bind(this),
|
||||
);
|
||||
this.deviceDB = window.VM.vm.db!;
|
||||
if (typeof window.VM.vm.db !== "undefined") {
|
||||
this.deviceDB = window.VM.vm.db!;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
window.VM.vm.removeEventListener(
|
||||
"vm-device-db-loaded",
|
||||
this._handleDeviceDBLoad.bind(this),
|
||||
)
|
||||
}
|
||||
|
||||
_handleDeviceDBLoad(e: CustomEvent) {
|
||||
this.deviceDB = e.detail;
|
||||
}
|
||||
@@ -249,11 +286,14 @@ export const VMDeviceDBMixin = <T extends Constructor<LitElement>>(superClass: T
|
||||
return this._deviceDB;
|
||||
}
|
||||
|
||||
postDBSetUpdate(): void { }
|
||||
|
||||
@state()
|
||||
set deviceDB(val: DeviceDB) {
|
||||
this._deviceDB = val;
|
||||
this.postDBSetUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
return VMDeviceDBMixinClass as Constructor<VMDeviceDBMixinInterface> & T
|
||||
}
|
||||
return VMDeviceDBMixinClass as Constructor<VMDeviceDBMixinInterface> & T;
|
||||
};
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { html, css, HTMLTemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { html, css, HTMLTemplateResult, PropertyValueMap } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
|
||||
import SlInput from "@shoelace-style/shoelace/dist/components/input/input.js";
|
||||
import { structuralEqual } from "../../utils";
|
||||
|
||||
import SlDrawer from "@shoelace-style/shoelace/dist/components/drawer/drawer.js";
|
||||
import type { DeviceDB, DeviceDBEntry } from "virtual_machine/device_db";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
import { cache } from "lit/directives/cache.js";
|
||||
import { default as uFuzzy } from "@leeoniya/ufuzzy";
|
||||
import { when } from "lit/directives/when.js";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { VMSlotAddDialog } from "./slot_add_dialog";
|
||||
import "./add_device"
|
||||
import { SlotModifyEvent } from "./slot";
|
||||
|
||||
@customElement("vm-device-list")
|
||||
export class VMDeviceList extends BaseElement {
|
||||
@@ -49,14 +47,20 @@ export class VMDeviceList extends BaseElement {
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
super.connectedCallback();
|
||||
window.VM.get().then((vm) =>
|
||||
vm.addEventListener(
|
||||
"vm-devices-update",
|
||||
this._handleDevicesUpdate.bind(this),
|
||||
),
|
||||
);
|
||||
return root;
|
||||
}
|
||||
|
||||
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
|
||||
this.renderRoot.querySelector(".device-list").addEventListener(
|
||||
"device-modify-slot",
|
||||
this._showDeviceSlotDialog.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
_handleDevicesUpdate(e: CustomEvent) {
|
||||
@@ -92,11 +96,20 @@ export class VMDeviceList extends BaseElement {
|
||||
<vm-add-device-button class="ms-auto"></vm-add-device-button>
|
||||
</div>
|
||||
<div class="device-list">${deviceCards}</div>
|
||||
<vm-slot-add-dialog></vm-slot-add-dialog>
|
||||
`;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@query("vm-slot-add-dialog") slotDialog: VMSlotAddDialog;
|
||||
|
||||
_showDeviceSlotDialog(
|
||||
e: CustomEvent<SlotModifyEvent>,
|
||||
) {
|
||||
this.slotDialog.show(e.detail.deviceID, e.detail.slotIndex);
|
||||
}
|
||||
|
||||
get filteredDeviceIds() {
|
||||
if (typeof this._filteredDeviceIds !== "undefined") {
|
||||
return this._filteredDeviceIds;
|
||||
@@ -162,239 +175,3 @@ export class VMDeviceList extends BaseElement {
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("vm-add-device-button")
|
||||
export class VMAddDeviceButton extends BaseElement {
|
||||
static styles = [
|
||||
...defaultCss,
|
||||
css`
|
||||
.add-device-drawer {
|
||||
--size: 36rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-top: var(--sl-spacing-small);
|
||||
margin-right: var(--sl-spacing-small);
|
||||
}
|
||||
|
||||
.card + .card {
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@query("sl-drawer") drawer: SlDrawer;
|
||||
@query(".device-search-input") searchInput: SlInput;
|
||||
|
||||
private _deviceDB: DeviceDB;
|
||||
private _strutures: Map<string, DeviceDBEntry> = new Map();
|
||||
private _datapoints: [string, string][] = [];
|
||||
private _haystack: string[] = [];
|
||||
get deviceDB() {
|
||||
return this._deviceDB;
|
||||
}
|
||||
|
||||
@state()
|
||||
set deviceDB(val: DeviceDB) {
|
||||
this._deviceDB = val;
|
||||
this._strutures = new Map(
|
||||
Object.values(this.deviceDB.db)
|
||||
.filter((entry) => this.deviceDB.structures.includes(entry.name), this)
|
||||
.filter(
|
||||
(entry) => this.deviceDB.logic_enabled.includes(entry.name),
|
||||
this,
|
||||
)
|
||||
.map((entry) => [entry.name, entry]),
|
||||
);
|
||||
|
||||
const datapoints: [string, string][] = [];
|
||||
for (const entry of this._strutures.values()) {
|
||||
datapoints.push(
|
||||
[entry.title, entry.name],
|
||||
[entry.name, entry.name],
|
||||
[entry.desc, entry.name],
|
||||
);
|
||||
}
|
||||
const haystack: string[] = datapoints.map((data) => data[0]);
|
||||
this._datapoints = datapoints;
|
||||
this._haystack = haystack;
|
||||
this.performSearch();
|
||||
}
|
||||
|
||||
private _filter: string = "";
|
||||
|
||||
get filter() {
|
||||
return this._filter;
|
||||
}
|
||||
|
||||
@state()
|
||||
set filter(val: string) {
|
||||
this._filter = val;
|
||||
this.performSearch();
|
||||
}
|
||||
|
||||
private _searchResults: {
|
||||
entry: DeviceDBEntry;
|
||||
haystackEntry: string;
|
||||
ranges: number[];
|
||||
}[];
|
||||
|
||||
private filterTimeout: number | undefined;
|
||||
|
||||
performSearch() {
|
||||
if (this._filter) {
|
||||
const uf = new uFuzzy({});
|
||||
const [_idxs, info, order] = uf.search(
|
||||
this._haystack,
|
||||
this._filter,
|
||||
0,
|
||||
1e3,
|
||||
);
|
||||
|
||||
const filtered = order?.map((infoIdx) => ({
|
||||
name: this._datapoints[info.idx[infoIdx]][1],
|
||||
haystackEntry: this._haystack[info.idx[infoIdx]],
|
||||
ranges: info.ranges[infoIdx],
|
||||
}));
|
||||
|
||||
const unique = [...new Set(filtered.map((obj) => obj.name))].map(
|
||||
(result) => {
|
||||
return filtered.find((obj) => obj.name === result);
|
||||
},
|
||||
);
|
||||
|
||||
this._searchResults = unique.map(({ name, haystackEntry, ranges }) => ({
|
||||
entry: this._strutures.get(name)!,
|
||||
haystackEntry,
|
||||
ranges,
|
||||
}));
|
||||
} else {
|
||||
// return everything
|
||||
this._searchResults = [...this._strutures.values()].map((st) => ({
|
||||
entry: st,
|
||||
haystackEntry: st.title,
|
||||
ranges: [],
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
const root = super.connectedCallback();
|
||||
window.VM.get().then((vm) =>
|
||||
vm.addEventListener(
|
||||
"vm-device-db-loaded",
|
||||
this._handleDeviceDBLoad.bind(this),
|
||||
),
|
||||
);
|
||||
return root;
|
||||
}
|
||||
|
||||
_handleDeviceDBLoad(e: CustomEvent) {
|
||||
this.deviceDB = e.detail;
|
||||
}
|
||||
|
||||
renderSearchResults() {
|
||||
return when(
|
||||
typeof this._searchResults !== "undefined" && this._searchResults.length < 20,
|
||||
() =>
|
||||
repeat(
|
||||
this._searchResults ?? [],
|
||||
(result) => result.entry.name,
|
||||
(result) =>
|
||||
cache(html`
|
||||
<vm-device-template
|
||||
prefab_name=${result.entry.name}
|
||||
class="card"
|
||||
@add-device-template=${this._handleDeviceAdd}
|
||||
>
|
||||
</vm-device-template>
|
||||
`),
|
||||
),
|
||||
() => html`
|
||||
<div class="p-2">
|
||||
<p class="p-2">
|
||||
<sl-format-number
|
||||
.value=${this._searchResults.length}
|
||||
></sl-format-number>
|
||||
results, filter more to get cards
|
||||
</p>
|
||||
<div class="flex flex-row flex-wrap">
|
||||
${[
|
||||
...this._searchResults.slice(0, 50),
|
||||
{ entry: { title: "", name: "" }, haystackEntry: "...", ranges: [] },
|
||||
].map((result) => {
|
||||
const hay = result.haystackEntry.slice(0, 15);
|
||||
const ranges = result.ranges.filter((pos) => pos < 20);
|
||||
const key = result.entry.name;
|
||||
return html`<div class="p-2 text-neutral-200/80 italic cursor-pointer" key=${key} @click=${this._handleHaystackClick}>
|
||||
${result.entry.title} (<small class="text-sm">
|
||||
${unsafeHTML(uFuzzy.highlight(hay, ranges))}
|
||||
</small>)
|
||||
</div>`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
_handleHaystackClick(e: Event) {
|
||||
const div = e.currentTarget as HTMLDivElement;
|
||||
const key = div.getAttribute("key");
|
||||
this.filter = key;
|
||||
this.searchInput.value = key;
|
||||
}
|
||||
|
||||
_handleDeviceAdd() {
|
||||
this.drawer.hide();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<sl-button
|
||||
variant="neutral"
|
||||
outline
|
||||
pill
|
||||
@click=${this._handleAddButtonClick}
|
||||
>
|
||||
Add Device
|
||||
</sl-button>
|
||||
<sl-drawer class="add-device-drawer" placement="bottom" no-header>
|
||||
<sl-input
|
||||
class="device-search-input"
|
||||
autofocus
|
||||
placeholder="Search For Device"
|
||||
clearable
|
||||
@sl-input=${this._handleSearchInput}
|
||||
>
|
||||
<span slot="prefix">Search Structures</span>
|
||||
<sl-icon slot="suffix" name="search"></sl-icon>
|
||||
</sl-input>
|
||||
<div class="flex flex-row overflow-auto">${this.renderSearchResults()}</div>
|
||||
<sl-button
|
||||
slot="footer"
|
||||
variant="primary"
|
||||
@click=${() => {
|
||||
this.drawer.hide();
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</sl-button>
|
||||
</sl-drawer>
|
||||
`;
|
||||
}
|
||||
|
||||
_handleSearchInput(e: CustomEvent) {
|
||||
if (this.filterTimeout) {
|
||||
clearTimeout(this.filterTimeout);
|
||||
}
|
||||
const that = this;
|
||||
this.filterTimeout = setTimeout(() => {
|
||||
that.filter = that.searchInput.value;
|
||||
that.filterTimeout = undefined;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
_handleAddButtonClick() {
|
||||
this.drawer.show();
|
||||
(this.drawer.querySelector(".device-search-input") as SlInput).select();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,20 @@ import "@shoelace-style/shoelace/dist/components/badge/badge.js";
|
||||
import "@shoelace-style/shoelace/dist/components/option/option.js";
|
||||
import "@shoelace-style/shoelace/dist/components/drawer/drawer.js";
|
||||
import "@shoelace-style/shoelace/dist/components/icon/icon.js";
|
||||
import "@shoelace-style/shoelace/dist/components/format-number/format-number.js";
|
||||
|
||||
import "./template"
|
||||
import "./card"
|
||||
import "./device_list"
|
||||
import "./add_device"
|
||||
import "./slot_add_dialog"
|
||||
import "./slot"
|
||||
|
||||
import { VmDeviceTemplate } from "./template";
|
||||
import { VMDeviceCard } from "./card";
|
||||
import { VMAddDeviceButton, VMDeviceList } from "./device_list";
|
||||
import { VMDeviceList } from "./device_list";
|
||||
import { VMAddDeviceButton } from "./add_device";
|
||||
import { VMSlotAddDialog } from "./slot_add_dialog";
|
||||
|
||||
export { VMDeviceCard, VmDeviceTemplate, VMDeviceList, VMAddDeviceButton };
|
||||
export { VMDeviceCard, VmDeviceTemplate, VMDeviceList, VMAddDeviceButton, VMSlotAddDialog };
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { html, css, HTMLTemplateResult } from "lit";
|
||||
import { html, css } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import { BaseElement, defaultCss } from "components";
|
||||
import { VMDeviceDBMixin, VMDeviceMixin } from "virtual_machine/base_device";
|
||||
@@ -17,6 +17,11 @@ import SlDialog from "@shoelace-style/shoelace/dist/components/dialog/dialog.com
|
||||
import { VMDeviceCard } from "./card";
|
||||
import { when } from "lit/directives/when.js";
|
||||
|
||||
export interface SlotModifyEvent {
|
||||
deviceID: number;
|
||||
slotIndex: number;
|
||||
}
|
||||
|
||||
@customElement("vm-device-slot")
|
||||
export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
@property({ type: Number }) slotIndex: number;
|
||||
@@ -65,9 +70,9 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
}
|
||||
}
|
||||
|
||||
slotOcccupantTemplate(): { name: string, typ: SlotType} | undefined {
|
||||
slotOcccupantTemplate(): { name: string; typ: SlotType } | undefined {
|
||||
if (this.deviceDB) {
|
||||
const entry = this.deviceDB.db[this.prefabName]
|
||||
const entry = this.deviceDB.db[this.prefabName];
|
||||
return entry?.slots[this.slotIndex];
|
||||
} else {
|
||||
return undefined;
|
||||
@@ -78,20 +83,24 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
const inputIdBase = `vmDeviceSlot${this.deviceID}Slot${this.slotIndex}Head`;
|
||||
const slot = this.slots[this.slotIndex];
|
||||
const slotImg = this.slotOccupantImg();
|
||||
const img = html`<img class="w-10 h-10" src="${slotImg}" onerror="this.src = '${VMDeviceCard.transparentImg}'" />`;
|
||||
const img = html`<img
|
||||
class="w-10 h-10"
|
||||
src="${slotImg}"
|
||||
onerror="this.src = '${VMDeviceCard.transparentImg}'"
|
||||
/>`;
|
||||
const template = this.slotOcccupantTemplate();
|
||||
|
||||
return html`
|
||||
<div class="flex flex-row me-2">
|
||||
<div
|
||||
class="relative shrink-0 border border-neutral-200/40 rounded-lg p-1
|
||||
hover:ring-2 hover:ring-purple-500 hover:ring-offset-1
|
||||
hover:ring-offset-purple-500 cursor-pointer me-2"
|
||||
hover:ring-2 hover:ring-purple-500 hover:ring-offset-1
|
||||
hover:ring-offset-purple-500 cursor-pointer me-2"
|
||||
@click=${this._handleSlotClick}
|
||||
>
|
||||
<div
|
||||
class="absolute top-0 left-0 ml-1 mt-1 text-xs
|
||||
text-neutral-200/90 font-mono bg-neutral-500/40 rounded pl-1 pr-1"
|
||||
class="absolute top-0 left-0 ml-1 mt-1 text-xs
|
||||
text-neutral-200/90 font-mono bg-neutral-500/40 rounded pl-1 pr-1"
|
||||
>
|
||||
<small>${this.slotIndex}</small>
|
||||
</div>
|
||||
@@ -100,33 +109,32 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
</sl-tooltip>
|
||||
${when(
|
||||
typeof slot.occupant !== "undefined",
|
||||
() => html`<div
|
||||
class="absolute bottom-0 right-0 mr-1 mb-1 text-xs
|
||||
text-neutral-200/90 font-mono bg-neutral-500/40 rounded pl-1 pr-1"
|
||||
>
|
||||
<small>${slot.occupant.quantity}/${slot.occupant.max_quantity}</small>
|
||||
</div>`
|
||||
() =>
|
||||
html`<div
|
||||
class="absolute bottom-0 right-0 mr-1 mb-1 text-xs
|
||||
text-neutral-200/90 font-mono bg-neutral-500/40 rounded pl-1 pr-1"
|
||||
>
|
||||
<small
|
||||
>${slot.occupant.quantity}/${slot.occupant
|
||||
.max_quantity}</small
|
||||
>
|
||||
</div>`,
|
||||
)}
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-end">
|
||||
<div class="text-sm mt-auto mb-auto">
|
||||
${when(
|
||||
typeof slot.occupant !== "undefined",
|
||||
() => html`
|
||||
<span>
|
||||
${this.slotOccupantPrefabName()}
|
||||
</span>
|
||||
`,
|
||||
() => html`
|
||||
<span>
|
||||
${template?.name}
|
||||
</span>
|
||||
`,
|
||||
)}
|
||||
${when(
|
||||
typeof slot.occupant !== "undefined",
|
||||
() => html` <span> ${this.slotOccupantPrefabName()} </span> `,
|
||||
() => html` <span> ${template?.name} </span> `,
|
||||
)}
|
||||
</div>
|
||||
<div class="text-neutral-400 text-xs mt-auto flex flex-col mb-1">
|
||||
<div><strong class="mt-auto mb-auto">Type:</strong><span class="p-1">${slot.typ}</span></div>
|
||||
<div>
|
||||
<strong class="mt-auto mb-auto">Type:</strong
|
||||
><span class="p-1">${slot.typ}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${when(
|
||||
@@ -146,15 +154,20 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
</sl-input>
|
||||
</div>
|
||||
`,
|
||||
() => html`
|
||||
`,
|
||||
() => html``,
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_handleSlotClick(e: Event) {
|
||||
console.log(e, e.currentTarget, e.target);
|
||||
_handleSlotClick(_e: Event) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent<SlotModifyEvent>("device-modify-slot", {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: { deviceID: this.deviceID, slotIndex: this.slotIndex },
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
renderFields() {
|
||||
@@ -165,19 +178,22 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
return html`
|
||||
<div class="slot-fields">
|
||||
${fields.map(
|
||||
([name, field], _index, _fields) => html`
|
||||
<sl-input
|
||||
id="${inputIdBase}${name}"
|
||||
key="${name}"
|
||||
value="${displayNumber(field.value)}"
|
||||
size="small"
|
||||
@sl-change=${this._handleChangeSlotField}
|
||||
>
|
||||
<span slot="prefix">${name}</span>
|
||||
<sl-copy-button slot="suffix" from="${inputIdBase}${name}.value"></sl-copy-button>
|
||||
<span slot="suffix">${field.field_type}</span>
|
||||
</sl-input>
|
||||
`,
|
||||
([name, field], _index, _fields) => html`
|
||||
<sl-input
|
||||
id="${inputIdBase}${name}"
|
||||
key="${name}"
|
||||
value="${displayNumber(field.value)}"
|
||||
size="small"
|
||||
@sl-change=${this._handleChangeSlotField}
|
||||
>
|
||||
<span slot="prefix">${name}</span>
|
||||
<sl-copy-button
|
||||
slot="suffix"
|
||||
from="${inputIdBase}${name}.value"
|
||||
></sl-copy-button>
|
||||
<span slot="suffix">${field.field_type}</span>
|
||||
</sl-input>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
@@ -188,8 +204,12 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
const field = input.getAttribute("key")! as SlotLogicType;
|
||||
const val = parseNumber(input.value);
|
||||
window.VM.get().then((vm) => {
|
||||
if (!vm.setDeviceSlotField(this.deviceID, this.slotIndex, field, val, true)) {
|
||||
input.value = this.device.getSlotField(this.slotIndex, field).toString();
|
||||
if (
|
||||
!vm.setDeviceSlotField(this.deviceID, this.slotIndex, field, val, true)
|
||||
) {
|
||||
input.value = this.device
|
||||
.getSlotField(this.slotIndex, field)
|
||||
.toString();
|
||||
}
|
||||
this.updateDevice();
|
||||
});
|
||||
@@ -198,10 +218,10 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
|
||||
render() {
|
||||
return html`
|
||||
<ic10-details class="slot-card">
|
||||
<div class="slot-header w-full" slot="summary">${this.renderHeader()}</div>
|
||||
<div class="slot-body">
|
||||
${this.renderFields()}
|
||||
<div class="slot-header w-full" slot="summary">
|
||||
${this.renderHeader()}
|
||||
</div>
|
||||
<div class="slot-body">${this.renderFields()}</div>
|
||||
</ic10-details>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
|
||||
padding: var(--sl-spacing-small) var(--sl-spacing-medium);
|
||||
}
|
||||
sl-tab-group::part(base) {
|
||||
height: 14rem;
|
||||
height: 18rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
`,
|
||||
@@ -223,7 +223,7 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
|
||||
const device = this.dbDevice;
|
||||
return html`
|
||||
<sl-card class="template-card">
|
||||
<div class="header" slot="header">
|
||||
<div class="header h-20 w-96" slot="header">
|
||||
<sl-tooltip content="${device?.name}">
|
||||
<img
|
||||
class="image me-2"
|
||||
|
||||
@@ -4,11 +4,13 @@ import {
|
||||
FrozenVM,
|
||||
LogicType,
|
||||
SlotLogicType,
|
||||
SlotOccupantTemplate,
|
||||
VMRef,
|
||||
init,
|
||||
} from "ic10emu_wasm";
|
||||
import { DeviceDB } from "./device_db";
|
||||
import "./base_device";
|
||||
import "./device";
|
||||
import { App } from "app";
|
||||
export interface ToastMessage {
|
||||
variant: "warning" | "danger" | "success" | "primary" | "neutral";
|
||||
@@ -394,6 +396,21 @@ class VirtualMachine extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
setDeviceSlotOccupant(id: number, index: number, template: SlotOccupantTemplate): boolean {
|
||||
const device = this._devices.get(id);
|
||||
if (typeof device !== "undefined") {
|
||||
try {
|
||||
console.log("setting slot occupant", template);
|
||||
this.ic10vm.setSlotOccupant(id, index, template);
|
||||
this.updateDevice(device);
|
||||
return true;
|
||||
} catch (err) {
|
||||
this.handleVmError(err);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
saveVMState(): FrozenVM {
|
||||
return this.ic10vm.saveVMState();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user