refactor: split up node hooks (#173)
Some checks failed
Build / 🚀 Deploy to test env (fly.io) (push) Has been cancelled
Build / Manual Approval (push) Has been cancelled
Build / 🛠 Build (1.17, 18.x, 27) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/amd64) (push) Has been cancelled
Build / 🛠 Build Docker Images (linux/arm64) (push) Has been cancelled
Build / merge (push) Has been cancelled
Build / 🏷 Create Release (push) Has been cancelled

This commit is contained in:
guarzo
2025-03-01 04:45:34 -05:00
committed by GitHub
parent 0568533550
commit 5ac8ccbe5c
10 changed files with 300 additions and 196 deletions

View File

@@ -4,7 +4,7 @@ import { Handle, NodeProps, Position } from 'reactflow';
import clsx from 'clsx'; import clsx from 'clsx';
import classes from './SolarSystemNodeDefault.module.scss'; import classes from './SolarSystemNodeDefault.module.scss';
import { PrimeIcons } from 'primereact/api'; import { PrimeIcons } from 'primereact/api';
import { useLocalCounter, useSolarSystemNode, useNodeKillsCount } from '../../hooks/useSolarSystemLogic'; import { useLocalCounter, useSolarSystemNode, useNodeKillsCount } from '../../hooks';
import { import {
EFFECT_BACKGROUND_STYLES, EFFECT_BACKGROUND_STYLES,
MARKER_BOOKMARK_BG_STYLES, MARKER_BOOKMARK_BG_STYLES,

View File

@@ -4,7 +4,7 @@ import { Handle, NodeProps, Position } from 'reactflow';
import clsx from 'clsx'; import clsx from 'clsx';
import classes from './SolarSystemNodeTheme.module.scss'; import classes from './SolarSystemNodeTheme.module.scss';
import { PrimeIcons } from 'primereact/api'; import { PrimeIcons } from 'primereact/api';
import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks/useSolarSystemLogic'; import { useLocalCounter, useNodeKillsCount, useSolarSystemNode } from '../../hooks';
import { import {
EFFECT_BACKGROUND_STYLES, EFFECT_BACKGROUND_STYLES,
MARKER_BOOKMARK_BG_STYLES, MARKER_BOOKMARK_BG_STYLES,

View File

@@ -1,3 +1,11 @@
export * from './useMapHandlers'; export * from './useMapHandlers';
export * from './useUpdateNodes'; export * from './useUpdateNodes';
export * from './useNodesEdgesState'; export * from './useNodesEdgesState';
export * from './useBackgroundVars';
export * from './useKillsCounter';
export * from './useSystemName';
export * from './useNodesEdgesState';
export * from './useSolarSystemNode';
export * from './useUnsplashedSignatures';
export * from './useUpdateNodes';
export * from './useNodeKillsCount';

View File

@@ -0,0 +1,31 @@
import { useMemo } from 'react';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager';
import { LABELS_INFO, LABELS_ORDER } from '@/hooks/Mapper/components/map/constants';
interface UseLabelsInfoParams {
labels: string | null;
linkedSigPrefix: string | null;
isShowLinkedSigId: boolean;
}
export type LabelInfo = {
id: string;
shortName: string;
};
function sortedLabels(labels: string[]): LabelInfo[] {
if (!labels) return [];
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x] as LabelInfo);
}
export function useLabelsInfo({ labels, linkedSigPrefix, isShowLinkedSigId }: UseLabelsInfoParams) {
const labelsManager = useMemo(() => new LabelsManager(labels ?? ''), [labels]);
const labelsInfo = useMemo(() => sortedLabels(labelsManager.list), [labelsManager]);
const labelCustom = useMemo(() => {
if (isShowLinkedSigId && linkedSigPrefix) {
return labelsManager.customLabel ? `${linkedSigPrefix}${labelsManager.customLabel}` : linkedSigPrefix;
}
return labelsManager.customLabel;
}, [linkedSigPrefix, isShowLinkedSigId, labelsManager]);
return { labelsInfo, labelCustom };
}

View File

@@ -0,0 +1,42 @@
import { useEffect, useState, useCallback } from 'react';
import { useMapEventListener } from '@/hooks/Mapper/events';
import { Commands } from '@/hooks/Mapper/types';
interface Kill {
solar_system_id: number | string;
kills: number;
}
interface MapEvent {
name: Commands;
data?: any;
payload?: Kill[];
}
export function useNodeKillsCount(
systemId: number | string,
initialKillsCount: number | null
): number | null {
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
useEffect(() => {
setKillsCount(initialKillsCount);
}, [initialKillsCount]);
const handleEvent = useCallback((event: MapEvent): boolean => {
if (event.name === Commands.killsUpdated && Array.isArray(event.payload)) {
const killForSystem = event.payload.find(
kill => kill.solar_system_id.toString() === systemId.toString()
);
if (killForSystem && typeof killForSystem.kills === 'number') {
setKillsCount(killForSystem.kills);
}
return true;
}
return false;
}, [systemId]);
useMapEventListener(handleEvent);
return killsCount;
}

View File

@@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from 'react'; import { useMemo } from 'react';
import { MapSolarSystemType } from '../map.types'; import { MapSolarSystemType } from '../map.types';
import { NodeProps } from 'reactflow'; import { NodeProps } from 'reactflow';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
@@ -7,19 +7,12 @@ import { useMapState } from '@/hooks/Mapper/components/map/MapProvider';
import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick'; import { useDoubleClick } from '@/hooks/Mapper/hooks/useDoubleClick';
import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants'; import { REGIONS_MAP, Spaces } from '@/hooks/Mapper/constants';
import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace'; import { isWormholeSpace } from '@/hooks/Mapper/components/map/helpers/isWormholeSpace';
import { getSystemClassStyles, prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers'; import { getSystemClassStyles } from '@/hooks/Mapper/components/map/helpers';
import { sortWHClasses } from '@/hooks/Mapper/helpers'; import { sortWHClasses } from '@/hooks/Mapper/helpers';
import { LabelsManager } from '@/hooks/Mapper/utils/labelsManager'; import { CharacterTypeRaw, OutCommand, SystemSignature } from '@/hooks/Mapper/types';
import { CharacterTypeRaw, Commands, OutCommand, SystemSignature } from '@/hooks/Mapper/types'; import { useUnsplashedSignatures } from './useUnsplashedSignatures';
import { LABELS_INFO, LABELS_ORDER } from '@/hooks/Mapper/components/map/constants'; import { useSystemName } from './useSystemName';
import { useMapEventListener } from '@/hooks/Mapper/events'; import { LabelInfo, useLabelsInfo } from './useLabelsInfo';
export type LabelInfo = {
id: string;
shortName: string;
};
export type UnsplashedSignatureType = SystemSignature & { sig_id: string };
function getActivityType(count: number): string { function getActivityType(count: number): string {
if (count <= 5) return 'activityNormal'; if (count <= 5) return 'activityNormal';
@@ -34,11 +27,6 @@ const SpaceToClass: Record<string, string> = {
[Spaces.Gallente]: 'Gallente', [Spaces.Gallente]: 'Gallente',
}; };
function sortedLabels(labels: string[]): LabelInfo[] {
if (!labels) return [];
return LABELS_ORDER.filter(x => labels.includes(x)).map(x => LABELS_INFO[x] as LabelInfo);
}
export function useLocalCounter(nodeVars: SolarSystemNodeVars) { export function useLocalCounter(nodeVars: SolarSystemNodeVars) {
const localCounterCharacters = useMemo(() => { const localCounterCharacters = useMemo(() => {
return nodeVars.charactersInSystem return nodeVars.charactersInSystem
@@ -127,21 +115,19 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
const linkedSigPrefix = useMemo(() => (linkedSigEveId ? linkedSigEveId.split('-')[0] : null), [linkedSigEveId]); const linkedSigPrefix = useMemo(() => (linkedSigEveId ? linkedSigEveId.split('-')[0] : null), [linkedSigEveId]);
const labelsManager = useMemo(() => new LabelsManager(labels ?? ''), [labels]); const { labelsInfo, labelCustom } = useLabelsInfo({
const labelsInfo = useMemo(() => sortedLabels(labelsManager.list), [labelsManager]); labels,
const labelCustom = useMemo(() => { linkedSigPrefix,
if (isShowLinkedSigId && linkedSigPrefix) { isShowLinkedSigId,
return labelsManager.customLabel ? `${linkedSigPrefix}${labelsManager.customLabel}` : linkedSigPrefix; });
}
return labelsManager.customLabel;
}, [linkedSigPrefix, isShowLinkedSigId, labelsManager]);
const killsCount = useMemo(() => kills[solar_system_id] ?? null, [kills, solar_system_id]); const killsCount = useMemo(() => kills[solar_system_id] ?? null, [kills, solar_system_id]);
const killsActivityType = killsCount ? getActivityType(killsCount) : null; const killsActivityType = killsCount ? getActivityType(killsCount) : null;
const hasUserCharacters = useMemo(() => { const hasUserCharacters = useMemo(
return charactersInSystem.some(x => userCharacters.includes(x.eve_id)); () => charactersInSystem.some(x => userCharacters.includes(x.eve_id)),
}, [charactersInSystem, userCharacters]); [charactersInSystem, userCharacters],
);
const dbClick = useDoubleClick(() => { const dbClick = useDoubleClick(() => {
outCommand({ outCommand({
@@ -153,54 +139,19 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
const showHandlers = isConnecting || hoverNodeId === id; const showHandlers = isConnecting || hoverNodeId === id;
const space = showKSpaceBG ? REGIONS_MAP[region_id] : ''; const space = showKSpaceBG ? REGIONS_MAP[region_id] : '';
const regionClass = showKSpaceBG ? SpaceToClass[space] : null; const regionClass = showKSpaceBG ? SpaceToClass[space] || null : null;
const computedTemporaryName = useMemo(() => { const { systemName, computedTemporaryName, customName } = useSystemName({
if (!isTempSystemNameEnabled) { isTempSystemNameEnabled,
return ''; temporary_name,
} solar_system_name: solar_system_name || '',
if (isShowLinkedSigIdTempName && linkedSigPrefix) { isShowLinkedSigIdTempName,
return temporary_name ? `${linkedSigPrefix}${temporary_name}` : `${linkedSigPrefix}${solar_system_name}`; linkedSigPrefix,
} name,
return temporary_name; });
}, [isShowLinkedSigIdTempName, isTempSystemNameEnabled, linkedSigPrefix, solar_system_name, temporary_name]);
const systemName = useMemo(() => { const { unsplashedLeft, unsplashedRight } = useUnsplashedSignatures(systemSigs, isShowUnsplashedSignatures);
if (isTempSystemNameEnabled && computedTemporaryName) {
return computedTemporaryName;
}
return solar_system_name;
}, [isTempSystemNameEnabled, solar_system_name, computedTemporaryName]);
const customName = useMemo(() => {
if (isTempSystemNameEnabled && computedTemporaryName && name) {
return name;
}
if (solar_system_name !== name && name) {
return name;
}
return null;
}, [isTempSystemNameEnabled, computedTemporaryName, name, solar_system_name]);
const [unsplashedLeft, unsplashedRight] = useMemo(() => {
if (!isShowUnsplashedSignatures) {
return [[], []];
}
return prepareUnsplashedChunks(
systemSigs
.filter(s => s.group === 'Wormhole' && !s.linked_system)
.map(s => ({
eve_id: s.eve_id,
type: s.type,
custom_info: s.custom_info,
kind: s.kind,
name: s.name,
group: s.group,
})) as UnsplashedSignatureType[],
);
}, [isShowUnsplashedSignatures, systemSigs]);
// Ensure hubs are always strings.
const hubsAsStrings = useMemo(() => hubs.map(item => item.toString()), [hubs]); const hubsAsStrings = useMemo(() => hubs.map(item => item.toString()), [hubs]);
const nodeVars: SolarSystemNodeVars = { const nodeVars: SolarSystemNodeVars = {
@@ -225,12 +176,10 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
dbClick, dbClick,
sortedStatics, sortedStatics,
effectName: effect_name, effectName: effect_name,
regionName: region_name,
solarSystemId: solar_system_id.toString(), solarSystemId: solar_system_id.toString(),
solarSystemName: solar_system_name,
locked, locked,
hubs: hubsAsStrings, hubs: hubsAsStrings,
name: name, name,
isConnecting, isConnecting,
hoverNodeId, hoverNodeId,
charactersInSystem, charactersInSystem,
@@ -239,6 +188,8 @@ export function useSolarSystemNode(props: NodeProps<MapSolarSystemType>): SolarS
isThickConnections, isThickConnections,
classTitle: class_title, classTitle: class_title,
temporaryName: computedTemporaryName, temporaryName: computedTemporaryName,
regionName: region_name,
solarSystemName: solar_system_name,
}; };
return nodeVars; return nodeVars;
@@ -281,25 +232,3 @@ export interface SolarSystemNodeVars {
classTitle: string | null; classTitle: string | null;
temporaryName?: string | null; temporaryName?: string | null;
} }
export function useNodeKillsCount(systemId: number | string, initialKillsCount: number | null): number | null {
const [killsCount, setKillsCount] = useState<number | null>(initialKillsCount);
useEffect(() => {
setKillsCount(initialKillsCount);
}, [initialKillsCount]);
useMapEventListener(event => {
if (event.name === Commands.killsUpdated && event.data?.toString() === systemId.toString()) {
//@ts-ignore
if (event.payload && typeof event.payload.kills === 'number') {
// @ts-ignore
setKillsCount(event.payload.kills);
}
return true;
}
return false;
});
return killsCount;
}

View File

@@ -0,0 +1,49 @@
// useSystemName.ts
import { useMemo } from 'react';
interface UseSystemNameParams {
isTempSystemNameEnabled: boolean;
temporary_name?: string | null;
solar_system_name: string;
isShowLinkedSigIdTempName: boolean;
linkedSigPrefix: string | null;
name?: string | null;
}
export function useSystemName({
isTempSystemNameEnabled,
temporary_name,
solar_system_name,
isShowLinkedSigIdTempName,
linkedSigPrefix,
name,
}: UseSystemNameParams) {
const computedTemporaryName = useMemo(() => {
if (!isTempSystemNameEnabled) {
return '';
}
if (isShowLinkedSigIdTempName && linkedSigPrefix) {
return temporary_name ? `${linkedSigPrefix}${temporary_name}` : `${linkedSigPrefix}${solar_system_name}`;
}
return temporary_name ?? '';
}, [isTempSystemNameEnabled, temporary_name, solar_system_name, isShowLinkedSigIdTempName, linkedSigPrefix]);
const systemName = useMemo(() => {
if (isTempSystemNameEnabled && computedTemporaryName) {
return computedTemporaryName;
}
return solar_system_name;
}, [isTempSystemNameEnabled, computedTemporaryName, solar_system_name]);
const customName = useMemo(() => {
if (isTempSystemNameEnabled && computedTemporaryName && name) {
return name;
}
if (solar_system_name !== name && name) {
return name;
}
return null;
}, [isTempSystemNameEnabled, computedTemporaryName, name, solar_system_name]);
return { systemName, computedTemporaryName, customName };
}

View File

@@ -0,0 +1,30 @@
import { useMemo } from 'react';
import { SystemSignature } from '@/hooks/Mapper/types';
import { prepareUnsplashedChunks } from '@/hooks/Mapper/components/map/helpers';
export type UnsplashedSignatureType = SystemSignature & { sig_id: string };
export function useUnsplashedSignatures(systemSigs: SystemSignature[], isShowUnsplashedSignatures: boolean) {
return useMemo(() => {
if (!isShowUnsplashedSignatures) {
return {
unsplashedLeft: [] as SystemSignature[],
unsplashedRight: [] as SystemSignature[],
};
}
const chunks = prepareUnsplashedChunks(
systemSigs
.filter(s => s.group === 'Wormhole' && !s.linked_system)
.map(s => ({
eve_id: s.eve_id,
type: s.type,
custom_info: s.custom_info,
kind: s.kind,
name: s.name,
group: s.group,
})) as UnsplashedSignatureType[],
);
const [unsplashedLeft, unsplashedRight] = chunks;
return { unsplashedLeft, unsplashedRight };
}, [isShowUnsplashedSignatures, systemSigs]);
}

View File

@@ -6,9 +6,9 @@ import { SolarSystemRawType } from '@/hooks/Mapper/types';
const useThrottle = () => { const useThrottle = () => {
const throttleSeed = useRef<number | null>(null); const throttleSeed = useRef<number | null>(null);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const throttleFunction = useRef((func: any, delay = 200) => { const throttleFunction = useRef((func: any, delay = 200) => {
if (!throttleSeed.current) { if (!throttleSeed.current) {
// Call the callback immediately for the first time
func(); func();
throttleSeed.current = setTimeout(() => { throttleSeed.current = setTimeout(() => {
throttleSeed.current = null; throttleSeed.current = null;
@@ -75,7 +75,7 @@ export const useUpdateNodes = (nodes: Node<SolarSystemRawType>[]) => {
const visibleNodes = new Set(nodes.filter(x => isNodeVisible(x, viewport)).map(x => x.id)); const visibleNodes = new Set(nodes.filter(x => isNodeVisible(x, viewport)).map(x => x.id));
update({ visibleNodes }); update({ visibleNodes });
}, [nodes]); }, [getViewport, nodes, update]);
useOnViewportChange({ useOnViewportChange({
onChange: () => throttle(updateNodesVisibility.bind(this)), onChange: () => throttle(updateNodesVisibility.bind(this)),
@@ -84,5 +84,5 @@ export const useUpdateNodes = (nodes: Node<SolarSystemRawType>[]) => {
useEffect(() => { useEffect(() => {
updateNodesVisibility(); updateNodesVisibility();
}, [nodes]); }, [nodes, updateNodesVisibility]);
}; };

View File

@@ -1,9 +1,10 @@
import { useCallback, useMemo, useState, useEffect, useRef } from 'react'; import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import { OutCommand } from '@/hooks/Mapper/types/mapHandlers'; import { Commands, OutCommand } from '@/hooks/Mapper/types/mapHandlers';
import { DetailedKill } from '@/hooks/Mapper/types/kills'; import { DetailedKill } from '@/hooks/Mapper/types/kills';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider'; import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { useKillsWidgetSettings } from './useKillsWidgetSettings'; import { useKillsWidgetSettings } from './useKillsWidgetSettings';
import { useMapEventListener, MapEvent } from '@/hooks/Mapper/events';
interface UseSystemKillsProps { interface UseSystemKillsProps {
systemId?: string; systemId?: string;
@@ -13,16 +14,17 @@ interface UseSystemKillsProps {
sinceHours?: number; sinceHours?: number;
} }
function combineKills(existing: DetailedKill[], incoming: DetailedKill[], sinceHours: number): DetailedKill[] { function combineKills(
existing: DetailedKill[],
incoming: DetailedKill[],
sinceHours: number
): DetailedKill[] {
const cutoff = Date.now() - sinceHours * 60 * 60 * 1000; const cutoff = Date.now() - sinceHours * 60 * 60 * 1000;
const byId: Record<string, DetailedKill> = {}; const byId: Record<string, DetailedKill> = {};
for (const kill of [...existing, ...incoming]) { for (const kill of [...existing, ...incoming]) {
if (!kill.kill_time) { if (!kill.kill_time) continue;
continue;
}
const killTimeMs = new Date(kill.kill_time).valueOf(); const killTimeMs = new Date(kill.kill_time).valueOf();
if (killTimeMs >= cutoff) { if (killTimeMs >= cutoff) {
byId[kill.killmail_id] = kill; byId[kill.killmail_id] = kill;
} }
@@ -31,101 +33,117 @@ function combineKills(existing: DetailedKill[], incoming: DetailedKill[], sinceH
return Object.values(byId); return Object.values(byId);
} }
export function useSystemKills({ systemId, outCommand, showAllVisible = false, sinceHours = 24 }: UseSystemKillsProps) { interface DetailedKillsEvent extends MapEvent<Commands> {
payload: Record<string, DetailedKill[]>;
}
export function useSystemKills({
systemId,
outCommand,
showAllVisible = false,
sinceHours = 24,
}: UseSystemKillsProps) {
const { data, update } = useMapRootState(); const { data, update } = useMapRootState();
const { detailedKills = {}, systems = [] } = data; const { detailedKills = {}, systems = [] } = data;
const [settings] = useKillsWidgetSettings(); const [settings] = useKillsWidgetSettings();
const excludedSystems = settings.excludedSystems; const excludedSystems = settings.excludedSystems;
// When showing all visible kills, filter out excluded systems; const updateDetailedKills = useCallback((newKillsMap: Record<string, DetailedKill[]>) => {
// when showAllVisible is false, ignore the exclusion filter. update((prev) => {
const oldKills = prev.detailedKills ?? {};
const updated = { ...oldKills };
for (const [sid, killsArr] of Object.entries(newKillsMap)) {
updated[sid] = killsArr;
}
return { ...prev, detailedKills: updated };
}, true);
}, [update]);
useMapEventListener((event: MapEvent<Commands>) => {
if (event.name === Commands.detailedKillsUpdated) {
const detailedEvent = event as DetailedKillsEvent;
if (systemId && !Object.keys(detailedEvent.payload).includes(systemId.toString())) {
return false;
}
updateDetailedKills(detailedEvent.payload);
return true;
}
return false;
});
const effectiveSystemIds = useMemo(() => { const effectiveSystemIds = useMemo(() => {
if (showAllVisible) { if (showAllVisible) {
return systems.map(s => s.id).filter(id => !excludedSystems.includes(Number(id))); return systems.map((s) => s.id).filter((id) => !excludedSystems.includes(Number(id)));
} }
return systems.map(s => s.id); return systems.map((s) => s.id);
}, [systems, excludedSystems, showAllVisible]); }, [systems, excludedSystems, showAllVisible]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const didFallbackFetch = useRef(Object.keys(detailedKills).length !== 0); const didFallbackFetch = useRef(Object.keys(detailedKills).length !== 0);
const mergeKillsIntoGlobal = useCallback( const mergeKillsIntoGlobal = useCallback((killsMap: Record<string, DetailedKill[]>) => {
(killsMap: Record<string, DetailedKill[]>) => { update((prev) => {
update(prev => { const oldMap = prev.detailedKills ?? {};
const oldMap = prev.detailedKills ?? {}; const updated: Record<string, DetailedKill[]> = { ...oldMap };
const updated: Record<string, DetailedKill[]> = { ...oldMap };
for (const [sid, newKills] of Object.entries(killsMap)) { for (const [sid, newKills] of Object.entries(killsMap)) {
const existing = updated[sid] ?? []; const existing = updated[sid] ?? [];
const combined = combineKills(existing, newKills, sinceHours); const combined = combineKills(existing, newKills, sinceHours);
updated[sid] = combined; updated[sid] = combined;
}
return {
...prev,
detailedKills: updated,
};
});
},
[update, sinceHours],
);
const fetchKills = useCallback(
async (forceFallback = false) => {
setIsLoading(true);
setError(null);
try {
let eventType: OutCommand;
let requestData: Record<string, unknown>;
if (showAllVisible || forceFallback) {
eventType = OutCommand.getSystemsKills;
requestData = {
system_ids: effectiveSystemIds,
since_hours: sinceHours,
};
} else if (systemId) {
eventType = OutCommand.getSystemKills;
requestData = {
system_id: systemId,
since_hours: sinceHours,
};
} else {
// If there's no system and not showing all, do nothing
setIsLoading(false);
return;
}
const resp = await outCommand({
type: eventType,
data: requestData,
});
// Single system => `resp.kills`
if (resp?.kills) {
const arr = resp.kills as DetailedKill[];
const sid = systemId ?? 'unknown';
mergeKillsIntoGlobal({ [sid]: arr });
}
// multiple systems => `resp.systems_kills`
else if (resp?.systems_kills) {
mergeKillsIntoGlobal(resp.systems_kills as Record<string, DetailedKill[]>);
} else {
console.warn('[useSystemKills] Unexpected kills response =>', resp);
}
} catch (err) {
console.error('[useSystemKills] Failed to fetch kills:', err);
setError(err instanceof Error ? err.message : 'Error fetching kills');
} finally {
setIsLoading(false);
} }
},
[showAllVisible, systemId, outCommand, effectiveSystemIds, sinceHours, mergeKillsIntoGlobal], return { ...prev, detailedKills: updated };
); });
}, [update, sinceHours]);
const fetchKills = useCallback(async (forceFallback = false) => {
setIsLoading(true);
setError(null);
try {
let eventType: OutCommand;
let requestData: Record<string, unknown>;
if (showAllVisible || forceFallback) {
eventType = OutCommand.getSystemsKills;
requestData = {
system_ids: effectiveSystemIds,
since_hours: sinceHours,
};
} else if (systemId) {
eventType = OutCommand.getSystemKills;
requestData = {
system_id: systemId,
since_hours: sinceHours,
};
} else {
setIsLoading(false);
return;
}
const resp = await outCommand({
type: eventType,
data: requestData,
});
if (resp?.kills) {
const arr = resp.kills as DetailedKill[];
const sid = systemId ?? 'unknown';
mergeKillsIntoGlobal({ [sid]: arr });
}
else if (resp?.systems_kills) {
mergeKillsIntoGlobal(resp.systems_kills as Record<string, DetailedKill[]>);
} else {
console.warn('[useSystemKills] Unexpected kills response =>', resp);
}
} catch (err) {
console.error('[useSystemKills] Failed to fetch kills:', err);
setError(err instanceof Error ? err.message : 'Error fetching kills');
} finally {
setIsLoading(false);
}
}, [showAllVisible, systemId, outCommand, effectiveSystemIds, sinceHours, mergeKillsIntoGlobal]);
const debouncedFetchKills = useMemo( const debouncedFetchKills = useMemo(
() => () =>
@@ -138,12 +156,11 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
const finalKills = useMemo(() => { const finalKills = useMemo(() => {
if (showAllVisible) { if (showAllVisible) {
return effectiveSystemIds.flatMap(sid => detailedKills[sid] ?? []); return effectiveSystemIds.flatMap((sid) => detailedKills[sid] ?? []);
} else if (systemId) { } else if (systemId) {
return detailedKills[systemId] ?? []; return detailedKills[systemId] ?? [];
} else if (didFallbackFetch.current) { } else if (didFallbackFetch.current) {
// if we already did a fallback, we may have data for multiple systems return effectiveSystemIds.flatMap((sid) => detailedKills[sid] ?? []);
return effectiveSystemIds.flatMap(sid => detailedKills[sid] ?? []);
} }
return []; return [];
}, [showAllVisible, systemId, effectiveSystemIds, detailedKills]); }, [showAllVisible, systemId, effectiveSystemIds, detailedKills]);
@@ -153,9 +170,8 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
useEffect(() => { useEffect(() => {
if (!systemId && !showAllVisible && !didFallbackFetch.current) { if (!systemId && !showAllVisible && !didFallbackFetch.current) {
didFallbackFetch.current = true; didFallbackFetch.current = true;
// Cancel any queued debounced calls, then do the fallback.
debouncedFetchKills.cancel(); debouncedFetchKills.cancel();
fetchKills(true); // forceFallback => fetch as though showAllVisible is true fetchKills(true);
} }
}, [systemId, showAllVisible, debouncedFetchKills, fetchKills]); }, [systemId, showAllVisible, debouncedFetchKills, fetchKills]);
@@ -164,14 +180,13 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
if (showAllVisible || systemId) { if (showAllVisible || systemId) {
debouncedFetchKills(); debouncedFetchKills();
// Clean up the debounce on unmount or changes
return () => debouncedFetchKills.cancel(); return () => debouncedFetchKills.cancel();
} }
}, [showAllVisible, systemId, effectiveSystemIds, debouncedFetchKills]); }, [showAllVisible, systemId, effectiveSystemIds, debouncedFetchKills]);
const refetch = useCallback(() => { const refetch = useCallback(() => {
debouncedFetchKills.cancel(); debouncedFetchKills.cancel();
fetchKills(); // immediate (non-debounced) call fetchKills();
}, [debouncedFetchKills, fetchKills]); }, [debouncedFetchKills, fetchKills]);
return { return {