mirror of
https://github.com/Xevion/smart-rgb.git
synced 2025-12-17 08:13:24 -06:00
Update source files
This commit is contained in:
73
frontend/src/shared/components/AttackRow.tsx
Normal file
73
frontend/src/shared/components/AttackRow.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { AttackEntry, LeaderboardEntry } from "@/shared/api/types";
|
||||
|
||||
// Format troop count for human-readable display
|
||||
// 100 => "100", 12,493 => "12.4k", 980,455 => "980k"
|
||||
export function formatTroopCount(count: number): string {
|
||||
if (count < 1000) return count.toString();
|
||||
if (count < 1000000) {
|
||||
const k = count / 1000;
|
||||
return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`;
|
||||
}
|
||||
const m = count / 1000000;
|
||||
return m % 1 === 0 ? `${m}M` : `${m.toFixed(1)}M`;
|
||||
}
|
||||
|
||||
// Calculate the background width percentage based on troop count
|
||||
// Uses power scale (x^0.4): 100 troops = 5%, 1k = 7.9%, 10k = 15.3%, 100k = 33.7%, 1M = 80%
|
||||
function calculateBackgroundWidth(troops: number): number {
|
||||
const minTroops = 100;
|
||||
const maxTroops = 1000000;
|
||||
const minWidth = 5;
|
||||
const maxWidth = 80;
|
||||
|
||||
// Clamp troops to range
|
||||
const clampedTroops = Math.max(minTroops, Math.min(maxTroops, troops));
|
||||
|
||||
// Power scale with exponent 0.4 provides gentler progression than logarithmic
|
||||
const powerMin = Math.pow(minTroops, 0.4);
|
||||
const powerMax = Math.pow(maxTroops, 0.4);
|
||||
const powerTroops = Math.pow(clampedTroops, 0.4);
|
||||
|
||||
// Map to 5-80% range
|
||||
const normalized = (powerTroops - powerMin) / (powerMax - powerMin);
|
||||
return minWidth + normalized * (maxWidth - minWidth);
|
||||
}
|
||||
|
||||
interface AttackRowProps {
|
||||
attack: AttackEntry;
|
||||
playerMap: Map<number, LeaderboardEntry>;
|
||||
onNationHover?: (nationId: number | null) => void;
|
||||
}
|
||||
|
||||
export function AttackRow({ attack, playerMap, onNationHover }: AttackRowProps) {
|
||||
// For outgoing attacks, show target's name (who we're attacking)
|
||||
// For incoming attacks, show attacker's name (who is attacking us)
|
||||
const displayPlayerId = attack.is_outgoing ? attack.target_id : attack.attacker_id;
|
||||
|
||||
const displayPlayer = displayPlayerId !== null ? playerMap.get(displayPlayerId) : null;
|
||||
const displayName = displayPlayer?.name || "Unclaimed Territory";
|
||||
|
||||
const backgroundWidth = calculateBackgroundWidth(attack.troops);
|
||||
const backgroundColor = attack.is_outgoing
|
||||
? "rgba(59, 130, 246, 0.3)" // Blue with 30% opacity
|
||||
: "rgba(239, 68, 68, 0.3)"; // Red with 30% opacity
|
||||
|
||||
return (
|
||||
<button
|
||||
className="attacks__row"
|
||||
onClick={() => console.log("Attack clicked:", attack)}
|
||||
onMouseEnter={() => displayPlayerId !== null && onNationHover?.(displayPlayerId)}
|
||||
onMouseLeave={() => onNationHover?.(null)}
|
||||
>
|
||||
<div
|
||||
className="attacks__background"
|
||||
style={{
|
||||
width: `${backgroundWidth}%`,
|
||||
backgroundColor,
|
||||
}}
|
||||
/>
|
||||
<div className="attacks__nation">{displayName}</div>
|
||||
<div className="attacks__troops">{formatTroopCount(attack.troops)}</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user