fix: fix scroll and size issues with kills widget (#219)

* fix: fix scroll and size issues with kills widget
This commit is contained in:
guarzo
2025-03-06 11:15:22 -05:00
committed by GitHub
parent 994e03945d
commit ee8f9e4d24
16 changed files with 232 additions and 143 deletions

View File

@@ -43,6 +43,7 @@ export const SystemKills: React.FC = React.memo(() => {
systemId,
outCommand,
showAllVisible: visible,
sinceHours: settings.timeRange,
});
const isNothingSelected = !systemId && !visible;
@@ -61,45 +62,41 @@ export const SystemKills: React.FC = React.memo(() => {
}, [kills, settings.whOnly, systemBySolarSystemId, visible]);
return (
<div className="h-full flex flex-col min-h-0">
<div className="flex flex-col flex-1 min-h-0">
<Widget label={<KillsHeader systemId={systemId} onOpenSettings={() => setSettingsDialogVisible(true)} />}>
{!isSubscriptionActive ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">
Kills available with &#39;Active&#39; map subscription only (contact map administrators)
</span>
</div>
) : isNothingSelected ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">
No system selected (or toggle Show all systems)
</span>
</div>
) : showLoading ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">Loading Kills...</span>
</div>
) : error ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-red-400 text-sm">{error}</span>
</div>
) : !filteredKills || filteredKills.length === 0 ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">No kills found</span>
</div>
) : (
<div className="w-full h-full" style={{ height: '100%' }}>
<SystemKillsContent
kills={filteredKills}
systemNameMap={systemNameMap}
onlyOneSystem={!visible}
timeRange={settings.timeRange}
/>
</div>
)}
</Widget>
</div>
<div className="h-full flex flex-col">
<Widget label={<KillsHeader systemId={systemId} onOpenSettings={() => setSettingsDialogVisible(true)} />}>
{!isSubscriptionActive ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">
Kills available with &#39;Active&#39; map subscription only (contact map administrators)
</span>
</div>
) : isNothingSelected ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">
No system selected (or toggle &quot;Show all systems&quot;)
</span>
</div>
) : showLoading ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">Loading Kills...</span>
</div>
) : error ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-red-400 text-sm">{error}</span>
</div>
) : !filteredKills || filteredKills.length === 0 ? (
<div className="w-full h-full flex items-center justify-center">
<span className="select-none text-center text-stone-400/80 text-sm">No kills found</span>
</div>
) : (
<SystemKillsContent
kills={filteredKills}
systemNameMap={systemNameMap}
onlyOneSystem={!visible}
timeRange={settings.timeRange}
/>
)}
</Widget>
{settingsDialogVisible && <KillsSettingsDialog visible setVisible={setSettingsDialogVisible} />}
</div>

View File

@@ -1,14 +1,22 @@
.wrapper {
overflow-x: hidden;
box-sizing: border-box;
}
// Custom scrollbar styling is now handled by the global custom-scrollbar class
.scrollerContent {
width: 100%;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: auto;
}
// VirtualScroller specific styles that can't be handled with Tailwind
.VirtualScroller {
height: 100% !important;
overflow: hidden !important;
}
// Fix for PrimeReact VirtualScroller - these need to be global
:global {
.p-virtualscroller {
display: flex;
flex-direction: column;
}
.p-virtualscroller-content {
flex: 1;
}
}

View File

@@ -1,5 +1,4 @@
import React, { useMemo } from 'react';
import clsx from 'clsx';
import { DetailedKill } from '@/hooks/Mapper/types/kills';
import { VirtualScroller } from 'primereact/virtualscroller';
import { useSystemKillsItemTemplate } from '../hooks/useSystemKillsItemTemplate';
@@ -11,7 +10,6 @@ export interface SystemKillsContentProps {
kills: DetailedKill[];
systemNameMap: Record<string, string>;
onlyOneSystem?: boolean;
autoSize?: boolean;
timeRange?: number;
limit?: number;
}
@@ -20,44 +18,53 @@ export const SystemKillsContent: React.FC<SystemKillsContentProps> = ({
kills,
systemNameMap,
onlyOneSystem = false,
autoSize = false,
timeRange = 4,
limit,
}) => {
const processedKills = useMemo(() => {
if (!kills || kills.length === 0) return [];
// sort by newest first
const sortedKills = kills
.filter(k => k.kill_time)
.sort((a, b) => new Date(b.kill_time!).getTime() - new Date(a.kill_time!).getTime());
if (limit !== undefined) {
return sortedKills.slice(0, limit);
} else {
const now = Date.now();
const cutoff = now - timeRange * 60 * 60 * 1000;
return sortedKills.filter(k => new Date(k.kill_time!).getTime() >= cutoff);
// filter by timeRange
let filteredKills = sortedKills;
if (timeRange !== undefined) {
const cutoffTime = new Date();
cutoffTime.setHours(cutoffTime.getHours() - timeRange);
filteredKills = sortedKills.filter(kill => {
const killTime = new Date(kill.kill_time!).getTime();
return killTime >= cutoffTime.getTime();
});
}
}, [kills, timeRange, limit]);
const computedHeight = autoSize ? Math.max(processedKills.length, 1) * ITEM_HEIGHT : undefined;
const scrollerHeight = autoSize ? `${computedHeight}px` : '100%';
// apply limit if present
if (limit !== undefined) {
return filteredKills.slice(0, limit);
}
return filteredKills;
}, [kills, timeRange, limit]);
const itemTemplate = useSystemKillsItemTemplate(systemNameMap, onlyOneSystem);
// Define style for the VirtualScroller
const virtualScrollerStyle: React.CSSProperties = {
boxSizing: 'border-box',
};
return (
<div className={clsx('w-full h-full', classes.wrapper)}>
<div className="h-full w-full flex flex-col overflow-hidden" data-testid="system-kills-content">
<VirtualScroller
items={processedKills}
itemSize={ITEM_HEIGHT}
itemTemplate={itemTemplate}
autoSize={autoSize}
scrollWidth="100%"
style={{ height: scrollerHeight }}
className={clsx('w-full h-full custom-scrollbar select-none', {
[classes.VirtualScroller]: !autoSize,
})}
className={`w-full h-full flex-1 select-none ${classes.VirtualScroller}`}
style={virtualScrollerStyle}
pt={{
content: {
className: classes.scrollerContent,
className: `custom-scrollbar ${classes.scrollerContent}`,
},
}}
/>

View File

@@ -90,6 +90,14 @@ export const KillsSettingsDialog: React.FC<KillsSettingsDialogProps> = ({ visibl
const excluded = localData.excludedSystems || [];
const timeRangeOptions = [4, 12, 24];
// Ensure timeRange is one of the valid options
useEffect(() => {
if (visible && !timeRangeOptions.includes(localData.timeRange)) {
// If current timeRange is not in options, set it to the default (4 hours)
handleTimeRangeChange(4);
}
}, [visible, localData.timeRange, handleTimeRangeChange]);
return (
<Dialog header="Kills Settings" visible={visible} style={{ width: '440px' }} draggable={false} onHide={handleHide}>
<div className="flex flex-col gap-3 p-2.5">

View File

@@ -14,7 +14,7 @@ export const DEFAULT_KILLS_WIDGET_SETTINGS: KillsWidgetSettings = {
whOnly: true,
excludedSystems: [],
version: 2,
timeRange: 1,
timeRange: 4,
};
function mergeWithDefaults(settings?: Partial<KillsWidgetSettings>): KillsWidgetSettings {

View File

@@ -39,6 +39,8 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
const [settings] = useKillsWidgetSettings();
const excludedSystems = settings.excludedSystems;
const effectiveSinceHours = sinceHours;
const updateDetailedKills = useCallback(
(newKillsMap: Record<string, DetailedKill[]>) => {
update(prev => {
@@ -84,14 +86,14 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
for (const [sid, newKills] of Object.entries(killsMap)) {
const existing = updated[sid] ?? [];
const combined = combineKills(existing, newKills, sinceHours);
const combined = combineKills(existing, newKills, effectiveSinceHours);
updated[sid] = combined;
}
return { ...prev, detailedKills: updated };
});
},
[update, sinceHours],
[update, effectiveSinceHours],
);
const fetchKills = useCallback(
@@ -107,13 +109,13 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
eventType = OutCommand.getSystemsKills;
requestData = {
system_ids: effectiveSystemIds,
since_hours: sinceHours,
since_hours: effectiveSinceHours,
};
} else if (systemId) {
eventType = OutCommand.getSystemKills;
requestData = {
system_id: systemId,
since_hours: sinceHours,
since_hours: effectiveSinceHours,
};
} else {
setIsLoading(false);
@@ -141,7 +143,7 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
setIsLoading(false);
}
},
[showAllVisible, systemId, outCommand, effectiveSystemIds, sinceHours, mergeKillsIntoGlobal],
[showAllVisible, systemId, outCommand, effectiveSystemIds, effectiveSinceHours, mergeKillsIntoGlobal],
);
const debouncedFetchKills = useMemo(
@@ -154,15 +156,18 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
);
const finalKills = useMemo(() => {
let result: DetailedKill[] = [];
if (showAllVisible) {
return effectiveSystemIds.flatMap(sid => detailedKills[sid] ?? []);
result = effectiveSystemIds.flatMap(sid => detailedKills[sid] ?? []);
} else if (systemId) {
return detailedKills[systemId] ?? [];
result = detailedKills[systemId] ?? [];
} else if (didFallbackFetch.current) {
return effectiveSystemIds.flatMap(sid => detailedKills[sid] ?? []);
result = effectiveSystemIds.flatMap(sid => detailedKills[sid] ?? []);
}
return [];
}, [showAllVisible, systemId, effectiveSystemIds, detailedKills]);
return result;
}, [showAllVisible, systemId, effectiveSystemIds, detailedKills, didFallbackFetch]);
const effectiveIsLoading = isLoading && finalKills.length === 0;
@@ -178,10 +183,13 @@ export function useSystemKills({ systemId, outCommand, showAllVisible = false, s
if (effectiveSystemIds.length === 0) return;
if (showAllVisible || systemId) {
debouncedFetchKills();
// Cancel any pending debounced fetch
debouncedFetchKills.cancel();
// Fetch kills immediately
fetchKills();
return () => debouncedFetchKills.cancel();
}
}, [showAllVisible, systemId, effectiveSystemIds, debouncedFetchKills]);
}, [showAllVisible, systemId, effectiveSystemIds, debouncedFetchKills, fetchKills]);
const refetch = useCallback(() => {
debouncedFetchKills.cancel();