mirror of
https://github.com/wanderer-industries/wanderer
synced 2026-08-25 15:26:33 +00:00
fix: fix scroll and size issues with kills widget (#219)
* fix: fix scroll and size issues with kills widget
This commit is contained in:
+20
-23
@@ -1,8 +1,11 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { SystemKillsContent } from '../../../mapInterface/widgets/SystemKills/SystemKillsContent/SystemKillsContent';
|
||||
import { useKillsCounter } from '../../hooks/useKillsCounter';
|
||||
import { WdTooltipWrapper } from '@/hooks/Mapper/components/ui-kit/WdTooltipWrapper';
|
||||
import { WithChildren, WithClassName } from '@/hooks/Mapper/types/common';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
const ITEM_HEIGHT = 35;
|
||||
const MIN_TOOLTIP_HEIGHT = 40;
|
||||
|
||||
type TooltipSize = 'xs' | 'sm' | 'md' | 'lg';
|
||||
|
||||
@@ -16,45 +19,39 @@ type KillsBookmarkTooltipProps = {
|
||||
WithClassName;
|
||||
|
||||
export const KillsCounter = ({ killsCount, systemId, className, children, size = 'xs' }: KillsBookmarkTooltipProps) => {
|
||||
const { isLoading, kills: detailedKills, systemNameMap } = useKillsCounter({ realSystemId: systemId });
|
||||
const {
|
||||
isLoading,
|
||||
kills: detailedKills,
|
||||
systemNameMap,
|
||||
} = useKillsCounter({
|
||||
realSystemId: systemId,
|
||||
});
|
||||
|
||||
// Limit the kills shown to match the killsCount parameter
|
||||
const limitedKills = useMemo(() => {
|
||||
if (!detailedKills || detailedKills.length === 0) return [];
|
||||
return detailedKills.slice(0, killsCount);
|
||||
}, [detailedKills, killsCount]);
|
||||
|
||||
if (!killsCount || limitedKills.length === 0 || !systemId || isLoading) return null;
|
||||
if (!killsCount || limitedKills.length === 0 || !systemId || isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate a reasonable height for the tooltip based on the number of kills
|
||||
// but cap it to avoid excessively large tooltips
|
||||
const maxKillsToShow = Math.min(limitedKills.length, 20);
|
||||
const tooltipHeight = Math.max(200, Math.min(500, maxKillsToShow * 35));
|
||||
// Calculate height based on number of kills, but ensure a minimum height
|
||||
const killsNeededHeight = limitedKills.length * ITEM_HEIGHT;
|
||||
const tooltipHeight = Math.max(MIN_TOOLTIP_HEIGHT, Math.min(killsNeededHeight, 500));
|
||||
|
||||
const tooltipContent = (
|
||||
<div
|
||||
style={{
|
||||
width: '400px',
|
||||
height: `${tooltipHeight}px`,
|
||||
maxHeight: '500px',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="p-2 border-b border-stone-700 bg-stone-800 text-stone-200 font-medium">
|
||||
System Kills ({limitedKills.length})
|
||||
</div>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<SystemKillsContent
|
||||
kills={limitedKills}
|
||||
systemNameMap={systemNameMap}
|
||||
onlyOneSystem={true}
|
||||
// Don't use autoSize here as we want the virtual scroller to handle scrolling
|
||||
autoSize={false}
|
||||
// We've already limited the kills to match killsCount
|
||||
limit={undefined}
|
||||
/>
|
||||
<div className="flex-1 h-full">
|
||||
<SystemKillsContent kills={limitedKills} systemNameMap={systemNameMap} onlyOneSystem />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
+36
-39
@@ -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 'Active' 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 'Active' 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>
|
||||
) : (
|
||||
<SystemKillsContent
|
||||
kills={filteredKills}
|
||||
systemNameMap={systemNameMap}
|
||||
onlyOneSystem={!visible}
|
||||
timeRange={settings.timeRange}
|
||||
/>
|
||||
)}
|
||||
</Widget>
|
||||
|
||||
{settingsDialogVisible && <KillsSettingsDialog visible setVisible={setSettingsDialogVisible} />}
|
||||
</div>
|
||||
|
||||
+16
-8
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+27
-20
@@ -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}`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
+8
@@ -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">
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+20
-12
@@ -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();
|
||||
|
||||
@@ -556,7 +556,7 @@ defmodule WandererApp.Character.Tracker do
|
||||
{:ok, [character_aff_info]} when not is_nil(character_aff_info) ->
|
||||
update_corporation(state, character_aff_info |> Map.get("corporation_id"))
|
||||
|
||||
error ->
|
||||
_error ->
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
@@ -119,10 +119,10 @@ defmodule WandererApp.Maps do
|
||||
|
||||
@decorate cacheable(
|
||||
cache: WandererApp.Cache,
|
||||
key: "map_characters-#{_map_id}",
|
||||
key: "map_characters-#{map_id}",
|
||||
opts: [ttl: :timer.seconds(5)]
|
||||
)
|
||||
defp _get_map_characters(%{id: _map_id} = map) do
|
||||
defp _get_map_characters(%{id: map_id} = map) do
|
||||
map_acls =
|
||||
map.acls
|
||||
|> Enum.map(fn acl -> acl |> Ash.load!(:members) end)
|
||||
|
||||
@@ -14,7 +14,6 @@ defmodule WandererApp.Zkb.KillsProvider.Parser do
|
||||
use Retry
|
||||
|
||||
# Maximum retries for enrichment calls
|
||||
@max_enrichment_retries 2
|
||||
|
||||
@doc """
|
||||
Merges the 'partial' from zKB and the 'full' killmail from ESI, checks its time
|
||||
|
||||
@@ -220,20 +220,6 @@ defmodule WandererAppWeb.MapAccessListAPIController do
|
||||
end
|
||||
end
|
||||
|
||||
# Helper to find a character by internal id.
|
||||
defp find_character_by_id(id) do
|
||||
query =
|
||||
Character
|
||||
|> Ash.Query.new()
|
||||
|> filter(id == ^id)
|
||||
|
||||
case WandererApp.Api.read(query) do
|
||||
{:ok, [character]} -> {:ok, character}
|
||||
{:ok, []} -> {:error, "Character not found"}
|
||||
other -> other
|
||||
end
|
||||
end
|
||||
|
||||
# Helper to associate a new ACL with a map.
|
||||
defp associate_acl_with_map(map, new_acl) do
|
||||
with {:ok, api_map} <- WandererApp.Api.Map.by_id(map.id),
|
||||
|
||||
@@ -184,13 +184,12 @@ defmodule WandererAppWeb.MapAPIController do
|
||||
Returns kills data for all *visible* systems on the map.
|
||||
|
||||
Requires either `?map_id=<UUID>` or `?slug=<map-slug>`.
|
||||
Optional hours_ago
|
||||
Optional hours_ago parameter.
|
||||
|
||||
Example:
|
||||
GET /api/map/systems_kills?map_id=<uuid>
|
||||
GET /api/map/systems_kills?slug=<map-slug>
|
||||
GET /api/map/systems_kills?map_id=<uuid>&hour_ago=<somehours>
|
||||
|
||||
GET /api/map/systems_kills?map_id=<uuid>&hours_ago=<somehours>
|
||||
"""
|
||||
def list_systems_kills(conn, params) do
|
||||
with {:ok, map_id} <- Util.fetch_map_id(params),
|
||||
@@ -199,8 +198,10 @@ defmodule WandererAppWeb.MapAPIController do
|
||||
|
||||
Logger.debug(fn -> "[list_systems_kills] Found #{length(systems)} visible systems for map_id=#{map_id}" end)
|
||||
|
||||
# Parse the hours_ago param
|
||||
hours_ago = parse_hours_ago(params["hours_ago"])
|
||||
# Parse the hours_ago param (check both "hours_ago" and "hour_ago" for backward compatibility)
|
||||
hours_ago = parse_hours_ago(params["hours_ago"] || params["hour_ago"])
|
||||
|
||||
Logger.debug(fn -> "[list_systems_kills] Using hours_ago=#{inspect(hours_ago)}, from params: hours_ago=#{inspect(params["hours_ago"])}, hour_ago=#{inspect(params["hour_ago"])}" end)
|
||||
|
||||
# Gather system IDs
|
||||
solar_ids = Enum.map(systems, & &1.solar_system_id)
|
||||
@@ -216,11 +217,11 @@ defmodule WandererAppWeb.MapAPIController do
|
||||
# Filter out kills older than hours_ago
|
||||
filtered_kills = maybe_filter_kills_by_time(kills, hours_ago)
|
||||
|
||||
Logger.debug(fn -> "
|
||||
[list_systems_kills] For system_id=#{sys.solar_system_id},
|
||||
found #{length(kills)} kills total,
|
||||
returning #{length(filtered_kills)} kills after hours_ago filter
|
||||
" end)
|
||||
Logger.debug(fn ->
|
||||
"[list_systems_kills] For system_id=#{sys.solar_system_id}, " <>
|
||||
"found #{length(kills)} kills total, " <>
|
||||
"returning #{length(filtered_kills)} kills after hours_ago=#{inspect(hours_ago)} filter"
|
||||
end)
|
||||
|
||||
%{
|
||||
solar_system_id: sys.solar_system_id,
|
||||
@@ -231,7 +232,7 @@ defmodule WandererAppWeb.MapAPIController do
|
||||
json(conn, %{data: data})
|
||||
else
|
||||
{:error, msg} when is_binary(msg) ->
|
||||
Logger.warn("[list_systems_kills] Bad request: #{msg}")
|
||||
Logger.warning("[list_systems_kills] Bad request: #{msg}")
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> json(%{error: msg})
|
||||
@@ -244,34 +245,74 @@ defmodule WandererAppWeb.MapAPIController do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
GET /api/map/systems-kills
|
||||
|
||||
This is an alias for list_systems_kills to support the hyphenated URL format.
|
||||
See list_systems_kills for full documentation.
|
||||
"""
|
||||
def list_systems_kills_hyphenated(conn, params) do
|
||||
list_systems_kills(conn, params)
|
||||
end
|
||||
|
||||
# If hours_str is present and valid, parse it. Otherwise return nil (no filter).
|
||||
defp parse_hours_ago(nil), do: nil
|
||||
defp parse_hours_ago(hours_str) do
|
||||
case Integer.parse(hours_str) do
|
||||
{num, ""} when num > 0 -> num
|
||||
_ -> nil
|
||||
Logger.debug(fn -> "[parse_hours_ago] Parsing hours_str: #{inspect(hours_str)}" end)
|
||||
|
||||
result = case Integer.parse(hours_str) do
|
||||
{num, ""} when num > 0 ->
|
||||
Logger.debug(fn -> "[parse_hours_ago] Successfully parsed to #{num}" end)
|
||||
num
|
||||
{num, rest} ->
|
||||
Logger.debug(fn -> "[parse_hours_ago] Parsed with remainder: #{num}, rest: #{inspect(rest)}" end)
|
||||
nil
|
||||
:error ->
|
||||
Logger.debug(fn -> "[parse_hours_ago] Failed to parse" end)
|
||||
nil
|
||||
end
|
||||
|
||||
Logger.debug(fn -> "[parse_hours_ago] Final result: #{inspect(result)}" end)
|
||||
result
|
||||
end
|
||||
|
||||
defp maybe_filter_kills_by_time(kills, hours_ago) when is_integer(hours_ago) do
|
||||
cutoff = DateTime.utc_now() |> DateTime.add(-hours_ago * 3600, :second)
|
||||
Logger.debug(fn -> "[maybe_filter_kills_by_time] Filtering kills with cutoff: #{DateTime.to_iso8601(cutoff)}" end)
|
||||
|
||||
Enum.filter(kills, fn kill ->
|
||||
filtered = Enum.filter(kills, fn kill ->
|
||||
kill_time = kill["kill_time"]
|
||||
|
||||
case kill_time do
|
||||
result = case kill_time do
|
||||
%DateTime{} = dt ->
|
||||
# Keep kills that occurred after the cutoff
|
||||
DateTime.compare(dt, cutoff) != :lt
|
||||
|
||||
time when is_binary(time) ->
|
||||
# Try to parse the string time
|
||||
case DateTime.from_iso8601(time) do
|
||||
{:ok, dt, _} -> DateTime.compare(dt, cutoff) != :lt
|
||||
_ -> false
|
||||
end
|
||||
|
||||
# If it's something else (nil, or a weird format), skip
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
|
||||
Logger.debug(fn ->
|
||||
kill_time_str = if is_binary(kill_time), do: kill_time, else: inspect(kill_time)
|
||||
"[maybe_filter_kills_by_time] Kill time: #{kill_time_str}, included: #{result}"
|
||||
end)
|
||||
|
||||
result
|
||||
end)
|
||||
|
||||
Logger.debug(fn -> "[maybe_filter_kills_by_time] Filtered #{length(kills)} kills to #{length(filtered)} kills" end)
|
||||
filtered
|
||||
end
|
||||
|
||||
# If hours_ago is nil, maybe no time filtering:
|
||||
# If hours_ago is nil, no time filtering:
|
||||
defp maybe_filter_kills_by_time(kills, nil), do: kills
|
||||
|
||||
defp handle_all_structure_timers(conn, map_id) do
|
||||
|
||||
@@ -123,8 +123,12 @@ defmodule WandererAppWeb.MapCoreEventHandler do
|
||||
socket
|
||||
end
|
||||
|
||||
def handle_server_event(%{payload: kill_data}, socket) when is_map(kill_data) and map_size(kill_data) > 0 do
|
||||
socket
|
||||
end
|
||||
|
||||
def handle_server_event(event, socket) do
|
||||
Logger.warning(fn -> "unhandled map core event: #{inspect(event)} #{inspect(socket)} " end)
|
||||
Logger.warning(fn -> "unhandled map core event: #{inspect(event)}" end)
|
||||
socket
|
||||
end
|
||||
|
||||
|
||||
@@ -100,10 +100,45 @@ defmodule WandererAppWeb.MapKillsEventHandler do
|
||||
def handle_ui_event("get_systems_kills", %{"system_ids" => sids, "since_hours" => sh} = payload, socket) do
|
||||
with {:ok, since_hours} <- parse_id(sh),
|
||||
{:ok, parsed_ids} <- parse_system_ids(sids) do
|
||||
Logger.debug(fn -> "[#{__MODULE__}] get_systems_kills => system_ids=#{inspect(parsed_ids)}, since_hours=#{since_hours}" end)
|
||||
|
||||
# Get the cutoff time based on since_hours
|
||||
cutoff = DateTime.utc_now() |> DateTime.add(-since_hours * 3600, :second)
|
||||
Logger.debug(fn -> "[#{__MODULE__}] get_systems_kills => cutoff=#{DateTime.to_iso8601(cutoff)}" end)
|
||||
|
||||
# Fetch and filter kills for each system
|
||||
cached_map =
|
||||
Enum.reduce(parsed_ids, %{}, fn sid, acc ->
|
||||
kills_list = KillsCache.fetch_cached_kills(sid)
|
||||
Map.put(acc, sid, kills_list)
|
||||
# Get all cached kills for this system
|
||||
all_kills = KillsCache.fetch_cached_kills(sid)
|
||||
|
||||
# Filter kills based on the cutoff time
|
||||
filtered_kills = Enum.filter(all_kills, fn kill ->
|
||||
kill_time = kill["kill_time"]
|
||||
|
||||
case kill_time do
|
||||
%DateTime{} = dt ->
|
||||
# Keep kills that occurred after the cutoff
|
||||
DateTime.compare(dt, cutoff) != :lt
|
||||
|
||||
time when is_binary(time) ->
|
||||
# Try to parse the string time
|
||||
case DateTime.from_iso8601(time) do
|
||||
{:ok, dt, _} -> DateTime.compare(dt, cutoff) != :lt
|
||||
_ -> false
|
||||
end
|
||||
|
||||
# If it's something else (nil, or a weird format), skip
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end)
|
||||
|
||||
Logger.debug(fn ->
|
||||
"[#{__MODULE__}] get_systems_kills => system_id=#{sid}, all_kills=#{length(all_kills)}, filtered_kills=#{length(filtered_kills)}"
|
||||
end)
|
||||
|
||||
Map.put(acc, sid, filtered_kills)
|
||||
end)
|
||||
|
||||
reply_payload = %{"systems_kills" => cached_map}
|
||||
|
||||
@@ -675,7 +675,7 @@ defmodule WandererAppWeb.MapsLive do
|
||||
map
|
||||
|> WandererApp.Api.Map.update(form)
|
||||
|> case do
|
||||
{:ok, updated_map} ->
|
||||
{:ok, _updated_map} ->
|
||||
{added_acls, removed_acls} = map.acls |> Enum.map(& &1.id) |> _get_acls_diff(form["acls"])
|
||||
|
||||
Phoenix.PubSub.broadcast(
|
||||
|
||||
@@ -4,7 +4,6 @@ defmodule WandererAppWeb.Router do
|
||||
use Plug.ErrorHandler
|
||||
|
||||
import PlugDynamic.Builder
|
||||
import Logger
|
||||
|
||||
import WandererAppWeb.UserAuth,
|
||||
warn: false,
|
||||
|
||||
Reference in New Issue
Block a user