Device db fuzy search!

Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
This commit is contained in:
Rachel Powers
2024-04-11 14:48:10 -07:00
parent c85e2f6612
commit d63f8dff36
1226 changed files with 668 additions and 89 deletions

View File

@@ -1,9 +1,11 @@
import { Slot } from "ic10emu_wasm";
import { html, css, HTMLTemplateResult, PropertyValueMap } from "lit";
import { customElement, state } from "lit/decorators.js";
import { BaseElement, defaultCss, IC10Details } from "../components";
import { html, css, HTMLTemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { BaseElement, defaultCss } from "../components";
import { VMDeviceMixin } from "./base_device";
import { default as uFuzzy } from "@leeoniya/ufuzzy";
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";
@@ -16,10 +18,15 @@ 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 "@shoelace-style/shoelace/dist/components/drawer/drawer.js";
import "@shoelace-style/shoelace/dist/components/icon/icon.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";
import SlDetails from "@shoelace-style/shoelace/dist/components/details/details.js";
import SlDrawer from "@shoelace-style/shoelace/dist/components/drawer/drawer.js";
import { DeviceDB, DeviceDBEntry } from "./device_db";
@customElement("vm-device-card")
export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
@@ -89,6 +96,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
this.image_err = true;
console.log("Image load error", e);
}
renderHeader(): HTMLTemplateResult {
const activeIc = window.VM?.activeIC;
const badges: HTMLTemplateResult[] = [];
@@ -300,7 +308,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
_handleChangeName(e: CustomEvent) {
const input = e.target as SlInput;
window.VM?.setDeviceName(this.deviceID, input.value)
window.VM?.setDeviceName(this.deviceID, input.value);
this.updateDevice();
}
@@ -308,7 +316,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
const input = e.target as SlInput;
const field = input.getAttribute("key")!;
const val = parseNumber(input.value);
window.VM?.setDeviceField(this.deviceID, field, val)
window.VM?.setDeviceField(this.deviceID, field, val);
this.updateDevice();
}
@@ -317,7 +325,7 @@ export class VMDeviceCard extends VMDeviceMixin(BaseElement) {
const slot = parseInt(input.getAttribute("slotIndex")!);
const field = input.getAttribute("key")!;
const val = parseNumber(input.value);
window.VM?.setDeviceSlotField(this.deviceID, slot, field, val)
window.VM?.setDeviceSlotField(this.deviceID, slot, field, val);
this.updateDevice();
}
@@ -361,6 +369,16 @@ export class VMDeviceList extends BaseElement {
static styles = [
...defaultCss,
css`
.header {
margin-botton: 1rem;
margin-right: 2rem;
padding: 0.25rem 0.25rem;
align-items: center;
display: flex;
flex-direction: row;
width: 100%;
box-sizing: border-box;
}
.device-list {
display: flex;
flex-direction: row;
@@ -394,6 +412,13 @@ export class VMDeviceList extends BaseElement {
protected render(): HTMLTemplateResult {
return html`
<div class="header">
<span>
Devices:
<sl-badge variant="neutral" pill>${this.devices.length}</sl-badge>
</span>
<vm-add-device-button class="ms-auto"></vm-add-device-button>
</div>
<div class="device-list">
${this.devices.map(
(id, _index, _ids) =>
@@ -406,3 +431,231 @@ export class VMDeviceList extends BaseElement {
`;
}
}
@customElement("vm-add-device-button")
export class VMAddDeviceButton extends BaseElement {
static styles = [
...defaultCss,
css`
.add-device-drawer {
--size: 28rem;
}
.search-results {
display: flex;
flex-direction: row;
overflow-x: auto;
}
.card {
margin-top: var(--sl-spacing-small);
margin-right: var(--sl-spacing-small);
}
.card + .card {
}
`,
];
@query("sl-drawer") accessor drawer: SlDrawer;
@query(".device-search-input") accessor searchInput: SlInput;
private _deviceDB: DeviceDB;
private _strutures: Map<string, DeviceDBEntry>;
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.strutures.includes(entry.name), this)
.filter(
(entry) => this.deviceDB.logic_enabled.includes(entry.name),
this,
)
.map((entry) => [entry.name, entry]),
);
this.performSearch();
}
_filter: string = "";
get filter() {
return this._filter;
}
@state()
set filter(val: string) {
this._filter = val;
this.performSearch();
}
private _searchResults: DeviceDBEntry[];
performSearch() {
if (this.filter) {
const datapoints: [string, string][] = [];
for (const entry of this._strutures.values()) {
datapoints.push([entry.name, entry.name], [entry.desc, entry.name]);
}
const haystack: string[] = datapoints.map((data) => data[0]);
const uf = new uFuzzy({});
const [idxs, _info, _order] = uf.search(haystack, this._filter, 0, 1e3);
const filtered = idxs?.map((idx) => datapoints[idx]);
const names =
filtered
?.map((data) => data[1])
?.filter((val, index, arr) => arr.indexOf(val) === index) ?? [];
this._searchResults = names.map((name) => this._strutures.get(name)!);
} else {
this._searchResults =
[] ?? this._strutures ? [...this._strutures.values()] : [];
}
}
connectedCallback(): void {
const root = super.connectedCallback();
window.VM!.addEventListener(
"vm-device-db-loaded",
this._handleDeviceDBLoad.bind(this),
);
return root;
}
_handleDeviceDBLoad(e: CustomEvent) {
this.deviceDB = e.detail;
}
renderSearchResults(): HTMLTemplateResult {
const renderedResults: HTMLTemplateResult[] = this._searchResults?.map(
(result) =>
html`<vm-device-template
name=${result.name}
class="card"
></vm-device-template>`,
);
return html`${renderedResults}`;
}
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"
@sl-input=${this._handleSearchInput}
>
<span slot="prefix">Search Strutures</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();
}}
>
Close
</sl-button>
</sl-drawer>
`;
}
_handleSearchInput(e: CustomEvent) {
console.log("search-input", e);
this.filter = this.searchInput.value;
}
_handleAddButtonClick() {
this.drawer.show();
}
}
@customElement("vm-device-template")
export class VmDeviceTemplate extends BaseElement {
@property({ type: String }) accessor name: string;
private _deviceDB: DeviceDB;
private image_err: boolean = false;
static styles = [
...defaultCss,
css`
.image {
width: 3rem;
height: 3rem;
}
.header {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
`,
];
constructor() {
super();
this.deviceDB = window.VM!.db;
}
get deviceDB() {
return this._deviceDB;
}
@state()
set deviceDB(val: DeviceDB) {
this._deviceDB = val;
}
connectedCallback(): void {
const root = super.connectedCallback();
window.VM!.addEventListener(
"vm-device-db-loaded",
this._handleDeviceDBLoad.bind(this),
);
return root;
}
_handleDeviceDBLoad(e: CustomEvent) {
this.deviceDB = e.detail;
}
onImageErr(e: Event) {
this.image_err = true;
console.log("Image load error", e);
}
render() {
const device = this.deviceDB.db[this.name];
return html`
<sl-card>
<div class"header" slot="header">
<sl-tooltip content="${device.name}">
<img
class="image"
src="img/stationpedia/${device.name}.png"
@onerr=${this.onImageErr}
/>
</sl-tooltip>
<span class="prefab-name">${device.name}</span> <span class="prefab-hash">${device.hash}</span>
</div>
</sl-card>
`;
}
}

View File

@@ -0,0 +1,20 @@
export type DeviceDBEntry = {
name: string;
hash: number;
desc: string;
logic?: { [key: string]: string };
slots?: { name: string; typ: string }[];
modes?: { [key: string]: string };
conn?: { [key: string]: string[] };
};
export type DeviceDB = {
logic_enabled: string[];
slot_logic_enabled: string[];
devices: string[];
items: string[];
strutures: string[];
db: {
[key: string]: DeviceDBEntry;
};
};

View File

@@ -1,4 +1,5 @@
import { DeviceRef, VM, init } from "ic10emu_wasm";
import { DeviceDB } from "./device_db"
import "./base_device";
declare global {
@@ -7,28 +8,14 @@ declare global {
}
}
type DeviceDB = {
logic_enabled: string[];
slot_logic_enabled: string[];
devices: string[];
items: {
[key: string]: {
name: string;
hash: number;
desc: string;
logic?: { [key: string]: string };
slots?: { name: string; type: string }[];
modes?: { [key: string]: string };
conn?: { [key: string]: string[] };
};
};
};
class VirtualMachine extends EventTarget {
ic10vm: VM;
_devices: Map<number, DeviceRef>;
_ics: Map<number, DeviceRef>;
db: DeviceDB;
accessor db: DeviceDB;
dbPromise: Promise<{ default: DeviceDB }>
constructor() {
super();
@@ -41,8 +28,10 @@ class VirtualMachine extends EventTarget {
this._devices = new Map();
this._ics = new Map();
this.updateDevices();
this.dbPromise = import("../../../data/database.json");
this.dbPromise.then((module) => this.setupDeviceDatabase(module.default))
this.updateDevices();
this.updateCode();
}
@@ -257,9 +246,13 @@ class VirtualMachine extends EventTarget {
}
return false;
}
setupDeviceDatabase(db: DeviceDB) {
this.db = db;
console.log("Loaded Device Database", this.db);
this.dispatchEvent(
new CustomEvent("vm-device-db-loaded", { detail: this.db }),
);
}
}