Compare commits
2 Commits
e2f804bac7
...
90b190b8d5
Author | SHA1 | Date | |
---|---|---|---|
90b190b8d5 | |||
c10f4b43cb |
@@ -31,7 +31,7 @@ export const Header = ({ title, breadcrumbs = [] }: HeaderProps) => {
|
||||
try {
|
||||
const list = await ListCharacters();
|
||||
setChars((list as any[]).map((c: any) => ({ character_id: c.character_id, character_name: c.character_name })));
|
||||
} catch {}
|
||||
} catch { }
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@@ -96,7 +96,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
||||
|
||||
type OffIndicator = { from: string; toRegion: string; count: number; color: string; angle: number; sampleTo?: string };
|
||||
const [offRegionIndicators, setOffRegionIndicators] = useState<OffIndicator[]>([]);
|
||||
const [meanInboundAngle, setMeanInboundAngle] = useState<Record<string, number>>({});
|
||||
const [meanNeighborAngle, setMeanNeighborAngle] = useState<Record<string, number>>({});
|
||||
const [charLocs, setCharLocs] = useState<Array<{ character_id: number; character_name: string; solar_system_name: string }>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -133,7 +133,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
||||
setPositions(positions);
|
||||
const connections = computeNodeConnections(systems);
|
||||
setConnections(connections);
|
||||
// Compute per-system mean outbound BEARING (0=north, clockwise positive) to in-region neighbors
|
||||
// Compute per-system mean outbound angle in screen coords (atan2(dy,dx)) to in-region neighbors
|
||||
const angleMap: Record<string, number> = {};
|
||||
systems.forEach((sys, name) => {
|
||||
const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean);
|
||||
@@ -142,17 +142,17 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
||||
const neighbor = systems.get(n);
|
||||
if (!neighbor) continue;
|
||||
const dx = neighbor.x - sys.x;
|
||||
const dy = neighbor.y - sys.y; // screen coords (y down)
|
||||
const bearing = Math.atan2(dx, -dy); // bearing relative to north
|
||||
sumSin += Math.sin(bearing);
|
||||
sumCos += Math.cos(bearing);
|
||||
const dy = neighbor.y - sys.y; // y-down screen
|
||||
const a = Math.atan2(dy, dx);
|
||||
sumSin += Math.sin(a);
|
||||
sumCos += Math.cos(a);
|
||||
count++;
|
||||
}
|
||||
if (count > 0) {
|
||||
angleMap[name] = Math.atan2(sumSin, sumCos); // average bearing
|
||||
angleMap[name] = Math.atan2(sumSin, sumCos); // average angle
|
||||
}
|
||||
});
|
||||
setMeanInboundAngle(angleMap);
|
||||
setMeanNeighborAngle(angleMap);
|
||||
}, [systems]);
|
||||
|
||||
// Poll character locations every 7s and store those in this region
|
||||
@@ -208,58 +208,49 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
||||
}
|
||||
await ensureUniversePositions();
|
||||
|
||||
// Build indicators: group by from+toRegion
|
||||
const grouped: Map<string, OffIndicator> = new Map();
|
||||
// Build indicators: group by from+toRegion; angle from local geometry only (meanNeighborAngle + PI)
|
||||
type Agg = { from: string; toRegion: string; count: number; sumRemoteSec: number; sampleTo?: string };
|
||||
const grouped: Map<string, Agg> = new Map();
|
||||
for (const [fromName, sys] of systems.entries()) {
|
||||
const neighbors = (sys.connectedSystems || '').split(',').map(s => s.trim()).filter(Boolean);
|
||||
for (const n of neighbors) {
|
||||
if (systems.has(n)) continue;
|
||||
const toRegion = nameToRegion[n];
|
||||
if (!toRegion || toRegion === regionName) continue;
|
||||
|
||||
// compute color
|
||||
const remote = regionSystemsCache.get(toRegion)?.get(n);
|
||||
const avgSec = ((sys.security || 0) + (remote?.security || 0)) / 2;
|
||||
const color = getSecurityColor(avgSec);
|
||||
|
||||
// compute angle via universe region centroids
|
||||
let angle: number | undefined = undefined;
|
||||
const inbound = meanInboundAngle[fromName];
|
||||
if (inbound !== undefined) {
|
||||
angle = inbound; // mean outbound bearing already computed
|
||||
} else {
|
||||
const curPos = universeRegionPosCache.get(regionName);
|
||||
const toPos = universeRegionPosCache.get(toRegion);
|
||||
if (curPos && toPos) {
|
||||
const dxr = toPos.x - curPos.x;
|
||||
const dyr = toPos.y - curPos.y;
|
||||
angle = Math.atan2(dxr, -dyr); // bearing to remote region
|
||||
const gkey = `${fromName}__${toRegion}`;
|
||||
const agg = grouped.get(gkey) || { from: fromName, toRegion, count: 0, sumRemoteSec: 0, sampleTo: n };
|
||||
agg.count += 1;
|
||||
if (remote) agg.sumRemoteSec += (remote.security || 0);
|
||||
grouped.set(gkey, agg);
|
||||
}
|
||||
}
|
||||
const out: OffIndicator[] = [];
|
||||
for (const [, agg] of grouped) {
|
||||
if (agg.count === 0) continue;
|
||||
// Angle: point away from existing connections = meanNeighborAngle + PI
|
||||
let angle = meanNeighborAngle[agg.from];
|
||||
if (angle === undefined) {
|
||||
// final fallback deterministic angle
|
||||
let h = 0; const key = `${fromName}->${toRegion}`;
|
||||
for (let i = 0; i < key.length; i++) h = (h * 31 + key.charCodeAt(i)) >>> 0;
|
||||
angle = (h % 360) * (Math.PI / 180);
|
||||
// fallback: away from region centroid
|
||||
// compute centroid of current region nodes
|
||||
let cx = 0, cy = 0, c = 0;
|
||||
systems.forEach(s => { cx += s.x; cy += s.y; c++; });
|
||||
if (c > 0) { cx /= c; cy /= c; }
|
||||
const sys = systems.get(agg.from)!;
|
||||
angle = Math.atan2(sys.y - cy, sys.x - cx);
|
||||
}
|
||||
// Flip 180° so indicator points away from existing connections
|
||||
angle = angle + Math.PI;
|
||||
|
||||
const gkey = `${fromName}__${toRegion}`;
|
||||
const prev = grouped.get(gkey);
|
||||
if (prev) {
|
||||
prev.count += 1;
|
||||
if (!prev.sampleTo) prev.sampleTo = n;
|
||||
} else {
|
||||
grouped.set(gkey, { from: fromName, toRegion, count: 1, color, angle, sampleTo: n });
|
||||
// Color from avg of local system sec and avg remote sec; local from this system
|
||||
const localSec = (systems.get(agg.from)?.security || 0);
|
||||
const remoteAvg = agg.count > 0 ? (agg.sumRemoteSec / agg.count) : 0;
|
||||
const color = getSecurityColor((localSec + remoteAvg) / 2);
|
||||
out.push({ from: agg.from, toRegion: agg.toRegion, count: agg.count, color, angle, sampleTo: agg.sampleTo });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setOffRegionIndicators(Array.from(grouped.values()));
|
||||
setOffRegionIndicators(out);
|
||||
};
|
||||
computeOffRegion();
|
||||
}, [systems, regionName]);
|
||||
}, [systems, regionName, meanNeighborAngle]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isWormholeRegion) {
|
||||
@@ -686,8 +677,8 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
||||
if (!pos) return null;
|
||||
const len = 26;
|
||||
const r0 = 10; // start just outside node
|
||||
const dx = Math.sin(ind.angle);
|
||||
const dy = -Math.cos(ind.angle);
|
||||
const dx = Math.cos(ind.angle);
|
||||
const dy = Math.sin(ind.angle);
|
||||
const x1 = pos.x + dx * r0;
|
||||
const y1 = pos.y + dy * r0;
|
||||
const x2 = x1 + dx * len;
|
||||
|
@@ -88,8 +88,7 @@ export const SignatureListItem = ({ signature, onDelete, onUpdate }: SignatureLi
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`flex items-center justify-between p-4 border-b border-slate-700 hover:bg-slate-800/40 transition-colors cursor-pointer ${
|
||||
oldEntry ? "opacity-50" : ""
|
||||
className={`flex items-center justify-between p-4 border-b border-slate-700 hover:bg-slate-800/40 transition-colors cursor-pointer ${oldEntry ? "opacity-50" : ""
|
||||
} ${isGasSite ? 'bg-emerald-900/40 border-emerald-500 shadow-[0_0_15px_rgba(16,185,129,0.5)] hover:shadow-[0_0_20px_rgba(16,185,129,0.7)]' : ''}`}
|
||||
onClick={() => setIsEditModalOpen(true)}
|
||||
>
|
||||
|
@@ -25,7 +25,7 @@ const badgeVariants = cva(
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
VariantProps<typeof badgeVariants> { }
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
|
@@ -21,7 +21,7 @@ const Command = React.forwardRef<
|
||||
))
|
||||
Command.displayName = CommandPrimitive.displayName
|
||||
|
||||
interface CommandDialogProps extends DialogProps {}
|
||||
interface CommandDialogProps extends DialogProps { }
|
||||
|
||||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||
return (
|
||||
|
@@ -128,4 +128,3 @@ export {
|
||||
Sheet, SheetClose,
|
||||
SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface TextareaProps
|
||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { }
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
|
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@@ -66,7 +64,9 @@
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
html, body {
|
||||
|
||||
html,
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
@@ -74,9 +74,11 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import './index.css'
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App.tsx';
|
||||
import './index.css';
|
||||
|
||||
createRoot(document.getElementById("root")!).render(<App />);
|
||||
|
Reference in New Issue
Block a user