mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 15:24:52 +09:00
Use the defines.prototypes data structure from the game to find which prototype sub-types belong in the same base type, and export icons based on these base type. This renames the planets category to space-location, signal to virtual-signal and plits item up into item and fluid. And changes the exported metadata to include prototypes with no icons, as future code might want to use it to for example enumerate all item prototypes.
216 lines
5.3 KiB
TypeScript
216 lines
5.3 KiB
TypeScript
import React, { useContext, useEffect, useState } from "react";
|
|
import { Input, Table, Typography } from "antd";
|
|
|
|
import * as lib from "@clusterio/lib";
|
|
import {
|
|
BaseWebPlugin, PageLayout, PageHeader, Control, ControlContext,
|
|
notifyErrorHandler, useItemMetadata, useFluidMetadata, useLocale,
|
|
} from "@clusterio/web_ui";
|
|
import { GetStorageRequest, Item, SetStorageSubscriptionRequest, UpdateStorageEvent } from "../messages";
|
|
|
|
import "./style.css";
|
|
|
|
const { Paragraph } = Typography;
|
|
|
|
|
|
function useStorage(control: Control) {
|
|
let plugin = control.plugins.get("subspace_storage") as WebPlugin;
|
|
let [storage, setStorage] = useState([...plugin.storage]);
|
|
|
|
useEffect(() => {
|
|
function update() {
|
|
setStorage([...plugin.storage]);
|
|
}
|
|
|
|
plugin.onUpdate(update);
|
|
return () => {
|
|
plugin.offUpdate(update);
|
|
};
|
|
}, []);
|
|
return storage;
|
|
}
|
|
|
|
function StoragePage() {
|
|
let control = useContext(ControlContext);
|
|
let locale = useLocale();
|
|
let itemMetadata = useItemMetadata();
|
|
let fluidMetadata = useFluidMetadata();
|
|
let storage = useStorage(control);
|
|
type ItemFilter = ([name, item]: [string, Item]) => boolean;
|
|
let [filter, setFilter] = useState<null | ItemFilter>(null);
|
|
|
|
function getLocaleName(itemName: string) {
|
|
let localeName = itemName;
|
|
let meta = itemMetadata.get(itemName) ?? fluidMetadata.get(itemName);
|
|
if (meta && meta.localised_name) {
|
|
// TODO: implement the locale to name conversion.
|
|
if (typeof meta.localised_name === "string") {
|
|
localeName = meta.localised_name;
|
|
} else {
|
|
localeName = locale.get(meta.localised_name[0])!;
|
|
}
|
|
} else {
|
|
for (let section of ["item-name", "entity-name", "fluid-name", "equipment-name"]) {
|
|
let name = locale.get(`${section}.${itemName}`);
|
|
if (name) {
|
|
localeName = name;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return localeName;
|
|
}
|
|
|
|
let numberFormat = new Intl.NumberFormat("en-US");
|
|
|
|
return <PageLayout nav={[{ name: "Storage" }]}>
|
|
<PageHeader title="Storage" />
|
|
<Paragraph>
|
|
<Input
|
|
placeholder="Search"
|
|
onChange={(event) => {
|
|
let search = event.target.value.trim();
|
|
if (!search) {
|
|
setFilter(null);
|
|
return;
|
|
}
|
|
search = search.replace(/(^| )(\w)/g, "$1\\b$2");
|
|
search = search.replace(/ +/g, ".*");
|
|
let filterExpr = new RegExp(search, "i");
|
|
setFilter(() => ((item: [string, Item]) => {
|
|
let name = getLocaleName(item[1].name);
|
|
return filterExpr.test(name) || filterExpr.test(item[1].name);
|
|
}));
|
|
}}
|
|
/>
|
|
</Paragraph>
|
|
<Table
|
|
columns={[
|
|
{
|
|
title: "Resource",
|
|
key: "resource",
|
|
sorter: (a, b) => {
|
|
let aName = getLocaleName(a[1].name);
|
|
let bName = getLocaleName(b[1].name);
|
|
if (aName < bName) { return -1; }
|
|
if (aName > bName) { return 1; }
|
|
return 0;
|
|
},
|
|
render: (_, item) => {
|
|
let localeName = getLocaleName(item[1].name);
|
|
let type;
|
|
let name;
|
|
if (itemMetadata.has(item[1].name)) {
|
|
type = "item";
|
|
name = item[1].name;
|
|
} else if (fluidMetadata.has(item[1].name)) {
|
|
type = "fluid";
|
|
name = item[1].name;
|
|
} else {
|
|
type = "item";
|
|
name = "unknown-item";
|
|
}
|
|
|
|
return <>
|
|
<span className={`factorio-icon ${type}-${name}`}/>
|
|
{localeName}
|
|
</>;
|
|
},
|
|
},
|
|
{
|
|
title: "Quality",
|
|
key: "quality",
|
|
render: (_, item) => item[1].quality,
|
|
},
|
|
{
|
|
title: "Quantity",
|
|
key: "quantity",
|
|
align: "right",
|
|
defaultSortOrder: "descend",
|
|
sorter: (a, b) => a[1].count - b[1].count,
|
|
render: (_, item) => numberFormat.format(item[1].count),
|
|
},
|
|
]}
|
|
dataSource={filter ? storage.filter(filter) : storage}
|
|
rowKey={item => item[0]}
|
|
pagination={false}
|
|
/>
|
|
</PageLayout>;
|
|
}
|
|
|
|
export class WebPlugin extends BaseWebPlugin {
|
|
storage = new Map<string, Item>();
|
|
callbacks: (() => void)[] = [];
|
|
|
|
async init() {
|
|
this.pages = [
|
|
{
|
|
path: "/storage",
|
|
sidebarName: "Storage",
|
|
permission: "subspace_storage.storage.view",
|
|
content: <StoragePage/>,
|
|
},
|
|
];
|
|
this.control.handle(UpdateStorageEvent, this.handleUpdateStorageEvent.bind(this));
|
|
}
|
|
|
|
onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") {
|
|
if (event === "connect") {
|
|
this.updateSubscription();
|
|
}
|
|
}
|
|
|
|
async handleUpdateStorageEvent(event: UpdateStorageEvent) {
|
|
this.updateStorage(event.items);
|
|
}
|
|
|
|
onUpdate(callback: () => void) {
|
|
this.callbacks.push(callback);
|
|
if (this.callbacks.length) {
|
|
this.updateSubscription();
|
|
}
|
|
}
|
|
|
|
offUpdate(callback: () => void) {
|
|
let index = this.callbacks.lastIndexOf(callback);
|
|
if (index === -1) {
|
|
throw new Error("callback is not registered");
|
|
}
|
|
|
|
this.callbacks.splice(index, 1);
|
|
if (!this.callbacks.length) {
|
|
this.updateSubscription();
|
|
}
|
|
}
|
|
|
|
updateStorage(items: Item[]) {
|
|
for (let item of items) {
|
|
this.storage.set(`${item.name}:${item.quality}`, item);
|
|
}
|
|
for (let callback of this.callbacks) {
|
|
callback();
|
|
}
|
|
}
|
|
|
|
updateSubscription() {
|
|
if (!this.control.connector.connected) {
|
|
return;
|
|
}
|
|
|
|
this.control.send(
|
|
new SetStorageSubscriptionRequest(Boolean(this.callbacks.length))
|
|
).catch(notifyErrorHandler("Error subscribing to storage"));
|
|
|
|
if (this.callbacks.length) {
|
|
this.control!.send(new GetStorageRequest()).then(
|
|
items => {
|
|
this.updateStorage(items);
|
|
}
|
|
).catch(notifyErrorHandler("Error updating storage"));
|
|
} else {
|
|
this.storage.clear();
|
|
}
|
|
}
|
|
}
|