mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 15:04:52 +09:00
222 lines
7.6 KiB
TypeScript
222 lines
7.6 KiB
TypeScript
import React from "react";
|
|
import { BaseWebPlugin } from "@clusterio/web_ui";
|
|
import * as lib from "@clusterio/lib";
|
|
import CanvasMinimapPage from "./CanvasMinimapPage";
|
|
export { default as CanvasMinimapPage } from "./CanvasMinimapPage";
|
|
export { SingleInstanceDataSource } from "./dataSources/SingleInstanceDataSource";
|
|
export type {
|
|
ChartTagDataWithInstance,
|
|
MinimapActiveView,
|
|
MinimapDataSource,
|
|
MinimapViewBounds,
|
|
} from "./minimap-data-source";
|
|
export {
|
|
GetRawTileRequest,
|
|
GetRawRecipeTileRequest,
|
|
GetChartTagsRequest,
|
|
GetPlayerPathRequest,
|
|
} from "../messages";
|
|
import { TileDataEvent, ChartTagDataEvent, RecipeDataEvent, PlayerPositionEvent } from "../messages";
|
|
|
|
export class WebPlugin extends BaseWebPlugin {
|
|
// Callback arrays for minimap updates
|
|
tileUpdateCallbacks: Array<(event: TileDataEvent) => void> = [];
|
|
chartTagUpdateCallbacks: Array<(event: ChartTagDataEvent) => void> = [];
|
|
recipeUpdateCallbacks: Array<(event: RecipeDataEvent) => void> = [];
|
|
playerPositionUpdateCallbacks: Array<(event: PlayerPositionEvent) => void> = [];
|
|
|
|
// Track subscription state
|
|
private subscribed = false;
|
|
private currentFilters: string[] | undefined = undefined;
|
|
|
|
async init() {
|
|
this.pages = [
|
|
{
|
|
path: "/minimap",
|
|
sidebarName: "Minimap",
|
|
permission: "minimap.view",
|
|
content: <CanvasMinimapPage />,
|
|
},
|
|
];
|
|
|
|
// Handle minimap events from the controller
|
|
this.control.handle(TileDataEvent, this.handleTileDataEvent.bind(this));
|
|
this.control.handle(ChartTagDataEvent, this.handleChartTagDataEvent.bind(this));
|
|
this.control.handle(RecipeDataEvent, this.handleRecipeDataEvent.bind(this));
|
|
this.control.handle(PlayerPositionEvent, this.handlePlayerPositionEvent.bind(this));
|
|
}
|
|
|
|
onControllerConnectionEvent(event: "connect" | "drop" | "resume" | "close") {
|
|
if (event === "connect" || event === "resume") {
|
|
this.updateSubscriptions();
|
|
}
|
|
}
|
|
|
|
// Update instance/surface filters used for subscriptions.
|
|
async setInstanceSurfaceFilters(filters: Array<{ instanceId: number; surface: string }> | null) {
|
|
const newFilters = filters && filters.length > 0
|
|
? filters.map(filter => `${filter.instanceId}:${filter.surface}`)
|
|
: undefined;
|
|
|
|
// No change
|
|
const prev = this.currentFilters;
|
|
const same = (
|
|
(prev === undefined && newFilters === undefined)
|
|
|| (Array.isArray(prev) && Array.isArray(newFilters)
|
|
&& prev.length === newFilters.length
|
|
&& prev.every((v, i) => v === newFilters[i]))
|
|
);
|
|
if (same) { return; }
|
|
|
|
// If we had previous filters and callbacks active, remove those filters first
|
|
if (this.hasActiveCallbacks() && prev && prev.length > 0) {
|
|
try {
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:TileDataEvent", false, 0, prev));
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:ChartTagDataEvent", false, 0, prev));
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:RecipeDataEvent", false, 0, prev));
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:PlayerPositionEvent", false, 0, prev));
|
|
} catch (err) {
|
|
this.logger.error(`Failed to remove previous minimap filters: ${err}`);
|
|
}
|
|
}
|
|
|
|
this.currentFilters = newFilters;
|
|
|
|
// Apply new filters if we have active callbacks
|
|
if (this.hasActiveCallbacks()) {
|
|
await this.updateSubscriptions();
|
|
}
|
|
}
|
|
|
|
// Update instance/surface filter used for subscriptions.
|
|
async setInstanceSurfaceFilter(instanceId: number | null, surface: string | null) {
|
|
await this.setInstanceSurfaceFilters(
|
|
instanceId !== null && surface
|
|
? [{ instanceId, surface }]
|
|
: null,
|
|
);
|
|
}
|
|
|
|
private async updateSubscriptions() {
|
|
if (!this.control.connector.connected) {
|
|
return;
|
|
}
|
|
|
|
const shouldSubscribe = this.hasActiveCallbacks();
|
|
|
|
// Only subscribe if we both have active callbacks and a specific filter set
|
|
if (shouldSubscribe && this.currentFilters && this.currentFilters.length > 0) {
|
|
// Always (re)send with current filters when we have active callbacks
|
|
const filters = this.currentFilters;
|
|
try {
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:TileDataEvent", true, 0, filters));
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:ChartTagDataEvent", true, 0, filters));
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:RecipeDataEvent", true, 0, filters));
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:PlayerPositionEvent", true, 0, filters));
|
|
this.subscribed = true;
|
|
} catch (err) {
|
|
this.logger.error(`Failed to subscribe to minimap events: ${err}`);
|
|
}
|
|
} else if ((!shouldSubscribe || !this.currentFilters || this.currentFilters.length === 0) && this.subscribed) {
|
|
// Unsubscribe from all minimap events
|
|
try {
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:TileDataEvent", false));
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:ChartTagDataEvent", false));
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:RecipeDataEvent", false));
|
|
await this.control.send(new lib.SubscriptionRequest("minimap:PlayerPositionEvent", false));
|
|
this.subscribed = false;
|
|
} catch (err) {
|
|
this.logger.error(`Failed to unsubscribe from minimap events: ${err}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
private hasActiveCallbacks(): boolean {
|
|
return this.tileUpdateCallbacks.length > 0
|
|
|| this.chartTagUpdateCallbacks.length > 0
|
|
|| this.recipeUpdateCallbacks.length > 0
|
|
|| this.playerPositionUpdateCallbacks.length > 0;
|
|
}
|
|
|
|
async handleTileDataEvent(event: TileDataEvent) {
|
|
// Notify all registered callbacks
|
|
for (let callback of this.tileUpdateCallbacks) {
|
|
callback(event);
|
|
}
|
|
}
|
|
|
|
async handleChartTagDataEvent(event: ChartTagDataEvent) {
|
|
for (let callback of this.chartTagUpdateCallbacks) {
|
|
callback(event);
|
|
}
|
|
}
|
|
|
|
async handleRecipeDataEvent(event: RecipeDataEvent) {
|
|
for (let callback of this.recipeUpdateCallbacks) {
|
|
callback(event);
|
|
}
|
|
}
|
|
|
|
async handlePlayerPositionEvent(event: PlayerPositionEvent) {
|
|
for (let callback of this.playerPositionUpdateCallbacks) {
|
|
callback(event);
|
|
}
|
|
}
|
|
|
|
onTileUpdate(callback: (event: TileDataEvent) => void) {
|
|
this.tileUpdateCallbacks.push(callback);
|
|
this.updateSubscriptions();
|
|
}
|
|
|
|
offTileUpdate(callback: (event: TileDataEvent) => void) {
|
|
let index = this.tileUpdateCallbacks.lastIndexOf(callback);
|
|
if (index === -1) {
|
|
throw new Error("callback is not registered");
|
|
}
|
|
this.tileUpdateCallbacks.splice(index, 1);
|
|
this.updateSubscriptions();
|
|
}
|
|
|
|
onChartTagUpdate(callback: (event: ChartTagDataEvent) => void) {
|
|
this.chartTagUpdateCallbacks.push(callback);
|
|
this.updateSubscriptions();
|
|
}
|
|
|
|
offChartTagUpdate(callback: (event: ChartTagDataEvent) => void) {
|
|
let index = this.chartTagUpdateCallbacks.lastIndexOf(callback);
|
|
if (index === -1) {
|
|
throw new Error("callback is not registered");
|
|
}
|
|
this.chartTagUpdateCallbacks.splice(index, 1);
|
|
this.updateSubscriptions();
|
|
}
|
|
|
|
onRecipeUpdate(callback: (event: RecipeDataEvent) => void) {
|
|
this.recipeUpdateCallbacks.push(callback);
|
|
this.updateSubscriptions();
|
|
}
|
|
|
|
offRecipeUpdate(callback: (event: RecipeDataEvent) => void) {
|
|
const index = this.recipeUpdateCallbacks.lastIndexOf(callback);
|
|
if (index === -1) {
|
|
throw new Error("callback is not registered");
|
|
}
|
|
this.recipeUpdateCallbacks.splice(index, 1);
|
|
this.updateSubscriptions();
|
|
}
|
|
|
|
onPlayerPositionUpdate(callback: (event: PlayerPositionEvent) => void) {
|
|
this.playerPositionUpdateCallbacks.push(callback);
|
|
this.updateSubscriptions();
|
|
}
|
|
|
|
offPlayerPositionUpdate(callback: (event: PlayerPositionEvent) => void) {
|
|
const index = this.playerPositionUpdateCallbacks.lastIndexOf(callback);
|
|
if (index === -1) {
|
|
throw new Error("callback is not registered");
|
|
}
|
|
this.playerPositionUpdateCallbacks.splice(index, 1);
|
|
this.updateSubscriptions();
|
|
}
|
|
}
|