perf: improve slot UI + device search speedup

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers
2024-04-23 16:16:44 -07:00
parent c1a451ad47
commit eb4463c8ab
5 changed files with 158 additions and 70 deletions

View File

@@ -183,10 +183,6 @@ export const VMDeviceMixin = <T extends Constructor<LitElement>>(
}
}
update(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
super.update(changedProperties);
this.updateDevice();
}
}
return VMDeviceMixinClass as Constructor<VMDeviceMixinInterface> & T;
};
@@ -239,6 +235,7 @@ export const VMDeviceDBMixin = <T extends Constructor<LitElement>>(superClass: T
"vm-device-db-loaded",
this._handleDeviceDBLoad.bind(this),
);
this.deviceDB = window.VM.vm.db!;
return root;
}

View File

@@ -92,7 +92,7 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
padding: var(--sl-spacing-small) var(--sl-spacing-medium);
}
sl-tab-group::part(base) {
max-height: 20rem;
max-height: 30rem;
overflow-y: auto;
}
sl-icon-button.remove-button::part(base) {
@@ -116,6 +116,11 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
`,
];
_handleDeviceDBLoad(e: CustomEvent<any>): void {
super._handleDeviceDBLoad(e);
this.updateDevice();
}
onImageErr(e: Event) {
this.image_err = true;
console.log("Image load error", e);
@@ -137,21 +142,21 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
}, this);
return html`
<sl-tooltip content="${this.prefabName}">
<img class="image" src="img/stationpedia/${this.prefabName}.png"
<img class="image me-2" src="img/stationpedia/${this.prefabName}.png"
onerror="this.src = '${VMDeviceCard.transparentImg}'" />
</sl-tooltip>
<div class="header-name">
<sl-input id="vmDeviceCard${this.deviceID}Id" class="device-id" size="small" pill value=${this.deviceID}
<sl-input id="vmDeviceCard${this.deviceID}Id" class="device-id me-1" size="small" pill value=${this.deviceID}
@sl-change=${this._handleChangeID}>
<span slot="prefix">Id</span>
<sl-copy-button slot="suffix" value=${this.deviceID}></sl-copy-button>
<sl-copy-button slot="suffix" .value=${this.deviceID}></sl-copy-button>
</sl-input>
<sl-input id="vmDeviceCard${this.deviceID}Name" class="device-name" size="small" pill placeholder=${this.prefabName}
<sl-input id="vmDeviceCard${this.deviceID}Name" class="device-name me-1" size="small" pill placeholder=${this.prefabName}
value=${this.name} @sl-change=${this._handleChangeName}>
<span slot="prefix">Name</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"
<sl-input id="vmDeviceCard${this.deviceID}NameHash" size="small" pill class="device-name-hash me-1"
value="${this.nameHash}" readonly>
<span slot="prefix">Hash</span>
<sl-copy-button slot="suffix" from="vmDeviceCard${this.deviceID}NameHash.value"></sl-copy-button>
@@ -192,12 +197,12 @@ export class VMDeviceCard extends VMDeviceDBMixin(VMDeviceMixin(BaseElement)) {
renderSlots(): HTMLTemplateResult {
return html`
<div class="slots">
<div class="flex flex-row flex-wrap">
${this.slots.map((_slot, index, _slots) => html`
<vm-device-slot
.deviceID=${this.deviceID}
.slotIndex=${index}
class-"w-96"
class-"flex flex-row max-w-lg mr-2 mb-2"
>
</vm-device-slot>
` )}

View File

@@ -1,18 +1,17 @@
import { html, css, HTMLTemplateResult } from "lit";
import { customElement, property, 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 { 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";
@customElement("vm-device-list")
export class VMDeviceList extends BaseElement {
@@ -169,13 +168,7 @@ export class VMAddDeviceButton extends BaseElement {
...defaultCss,
css`
.add-device-drawer {
--size: 32rem;
}
.search-results {
display: flex;
flex-direction: row;
overflow-x: auto;
--size: 36rem;
}
.card {
@@ -238,7 +231,11 @@ export class VMAddDeviceButton extends BaseElement {
this.performSearch();
}
private _searchResults: DeviceDBEntry[];
private _searchResults: {
entry: DeviceDBEntry;
haystackEntry: string;
ranges: number[];
}[];
private filterTimeout: number | undefined;
@@ -252,18 +249,30 @@ export class VMAddDeviceButton extends BaseElement {
1e3,
);
const filtered = order?.map(
(infoIdx) => this._datapoints[info.idx[infoIdx]],
);
const names =
filtered
?.map((data) => data[1])
?.filter((val, index, arr) => arr.indexOf(val) === index) ?? [];
const filtered = order?.map((infoIdx) => ({
name: this._datapoints[info.idx[infoIdx]][1],
haystackEntry: this._haystack[info.idx[infoIdx]],
ranges: info.ranges[infoIdx],
}));
this._searchResults = names.map((name) => this._strutures.get(name)!);
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 {
// clear our results and prefilter if the filter is empty
this._searchResults = [];
// return everything
this._searchResults = [...this._strutures.values()].map((st) => ({
entry: st,
haystackEntry: st.title,
ranges: [],
}));
}
}
@@ -283,15 +292,55 @@ export class VMAddDeviceButton extends BaseElement {
}
renderSearchResults() {
return repeat(
this._searchResults ?? [],
(result) => result.name,
(result) => cache(html`
<vm-device-template prefab_name=${result.name} class="card" @add-device-template=${this._handleDeviceAdd}>
</vm-device-template>
`)
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() {
@@ -300,20 +349,33 @@ export class VMAddDeviceButton extends BaseElement {
render() {
return html`
<sl-button variant="neutral" outline pill @click=${this._handleAddButtonClick}>
<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}>
<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="search-results">${this.renderSearchResults()}</div>
<sl-button slot="footer" variant="primary" @click=${()=> {
this.drawer.hide();
<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>
@@ -336,4 +398,3 @@ export class VMAddDeviceButton extends BaseElement {
(this.drawer.querySelector(".device-search-input") as SlInput).select();
}
}

View File

@@ -2,7 +2,7 @@ import { html, css, HTMLTemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { BaseElement, defaultCss } from "components";
import { VMDeviceDBMixin, VMDeviceMixin } from "virtual_machine/base_device";
import type { DeviceDB } 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 { displayNumber, parseIntWithHexOrBinary, parseNumber } from "utils";
import {
@@ -29,11 +29,17 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
...defaultCss,
css`
.slot-card {
--padding: var(--sl-spacing-small);
--padding: var(--sl-spacing-x-small);
}
.slot-card::part(header) {
padding: var(--sl-spacing-x-small);
}
.slot-card::part(base) {
background-color: var(--sl-color-neutral-50);
}
.quantity-input sl-input::part(input) {
width: 3rem;
}
`,
];
@@ -59,16 +65,26 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
}
}
slotOcccupantTemplate(): { name: string, typ: SlotType} | undefined {
if (this.deviceDB) {
const entry = this.deviceDB.db[this.prefabName]
return entry?.slots[this.slotIndex];
} else {
return undefined;
}
}
renderHeader() {
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}" oanerror="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 border border-neutral-200/40 rounded-lg p-1
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"
@click=${this._handleSlotClick}
@@ -88,30 +104,35 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
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>
<small>${slot.occupant.quantity}/${slot.occupant.max_quantity}</small>
</div>`
)}
<div></div>
</div>
<div class="ms-4 mt-auto mb-auto">
<div class="flex flex-col justify-end">
<div class="text-sm mt-auto mb-auto">
${when(
typeof slot.occupant !== "undefined",
() => html`
<span class="">
${slot.occupant.id} : ${this.slotOccupantPrefabName()}
<span>
${this.slotOccupantPrefabName()}
</span>
`,
() => html`
<span class="">
${slot.typ}
<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>
</div>
<div class="ms-auto mt-auto mb-auto me-4">
${when(
typeof slot.occupant !== "undefined",
() => html`
${when(
typeof slot.occupant !== "undefined",
() => html`
<div class="quantity-input ms-auto pl-2 mt-auto mb-auto me-2">
<sl-input
type="number"
size="small"
@@ -120,14 +141,14 @@ export class VMDeviceSlot extends VMDeviceMixin(VMDeviceDBMixin(BaseElement)) {
.max=${slot.occupant.max_quantity}
>
<div slot="help-text">
<span><strong>Max Quantity:</strong>${slot.occupant.max_quantity}</span>
<span>Max Quantity: ${slot.occupant.max_quantity}</span>
</div>
</sl-input>
`,
() => html`
`,
)}
</div>
</div>
`,
() => html`
`,
)}
</div>
`;
}

View File

@@ -47,6 +47,10 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
// height: 18rem;
overflow-y: auto;
}
sl-tab-group {
--indicator-color: var(--sl-color-purple-600);
--sl-color-primary-600: var(--sl-color-purple-600);
}
sl-tab::part(base) {
padding: var(--sl-spacing-small) var(--sl-spacing-medium);
}
@@ -222,7 +226,7 @@ export class VmDeviceTemplate extends VMDeviceDBMixin(BaseElement) {
<div class="header" slot="header">
<sl-tooltip content="${device?.name}">
<img
class="image"
class="image me-2"
src="img/stationpedia/${device?.name}.png"
onerror="this.src = '${VMDeviceCard.transparentImg}'"
/>