Compare commits
7 Commits
6f3a5dce64
...
master
Author | SHA1 | Date | |
---|---|---|---|
2c0134ed4d | |||
634c72be2d | |||
7efa724631 | |||
22a7d1ad45 | |||
f6450fdafb | |||
7f4ca796aa | |||
0c5d0616e5 |
@@ -7,6 +7,7 @@ import { SystemView } from "./pages/SystemView";
|
|||||||
import NotFound from "./pages/NotFound";
|
import NotFound from "./pages/NotFound";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { SearchDialog } from "@/components/SearchDialog";
|
import { SearchDialog } from "@/components/SearchDialog";
|
||||||
|
import { SignatureRules } from "./pages/SignatureRules";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ function App() {
|
|||||||
<Route path="/regions/:region" element={<RegionPage />} />
|
<Route path="/regions/:region" element={<RegionPage />} />
|
||||||
<Route path="/regions/:region/:system" element={<SystemView />} />
|
<Route path="/regions/:region/:system" element={<SystemView />} />
|
||||||
<Route path="/systems/:system" element={<SystemView />} />
|
<Route path="/systems/:system" element={<SystemView />} />
|
||||||
|
<Route path="/settings/signature-rules" element={<SignatureRules />} />
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
@@ -98,23 +98,23 @@ export const Header = ({ title, breadcrumbs = [] }: HeaderProps) => {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h1 className="text-2xl font-bold text-white">{title}</h1>
|
<h1 className="text-2xl font-bold text-white">{title}</h1>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
|
<Button size="sm" variant="outline" className="border-purple-500/40 text-purple-200" onClick={() => navigate('/settings/signature-rules')}>Rules</Button>
|
||||||
{chars.length > 0 && (
|
{chars.length > 0 && (
|
||||||
<div
|
<div
|
||||||
className="grid gap-1 flex-1 justify-end"
|
className="grid gap-1 flex-1 justify-end"
|
||||||
style={{
|
style={{
|
||||||
gridTemplateColumns: `repeat(${Math.ceil(chars.length / 2)}, 1fr)`,
|
gridTemplateColumns: `repeat(${Math.ceil(chars.length / 2)}, 1fr)`,
|
||||||
gridTemplateRows: 'repeat(2, auto)'
|
gridTemplateRows: 'repeat(2, auto)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{chars.map((c) => (
|
{chars.map((c) => (
|
||||||
<span
|
<span
|
||||||
key={c.character_id}
|
key={c.character_id}
|
||||||
onClick={() => handleCharacterClick(c)}
|
onClick={() => handleCharacterClick(c)}
|
||||||
className={`px-3 py-1 text-xs cursor-pointer transition-colors text-center overflow-hidden text-ellipsis ${
|
className={`px-3 py-1 text-xs cursor-pointer transition-colors text-center overflow-hidden text-ellipsis ${c.waypoint_enabled
|
||||||
c.waypoint_enabled
|
? 'bg-purple-500/20 text-purple-200 border border-purple-400/40 hover:bg-purple-500/30'
|
||||||
? 'bg-purple-500/20 text-purple-200 border border-purple-400/40 hover:bg-purple-500/30'
|
|
||||||
: 'bg-gray-500/20 text-gray-400 border border-gray-400/40 hover:bg-gray-500/30'
|
: 'bg-gray-500/20 text-gray-400 border border-gray-400/40 hover:bg-gray-500/30'
|
||||||
}`}
|
}`}
|
||||||
title={`Click to ${c.waypoint_enabled ? 'disable' : 'enable'} waypoints for ${c.character_name}`}
|
title={`Click to ${c.waypoint_enabled ? 'disable' : 'enable'} waypoints for ${c.character_name}`}
|
||||||
>
|
>
|
||||||
{c.character_name}
|
{c.character_name}
|
||||||
|
@@ -21,6 +21,7 @@ interface MapNodeProps {
|
|||||||
showJumps?: boolean;
|
showJumps?: boolean;
|
||||||
showKills?: boolean;
|
showKills?: boolean;
|
||||||
viewBoxWidth?: number; // Add viewBox width for scaling calculations
|
viewBoxWidth?: number; // Add viewBox width for scaling calculations
|
||||||
|
labelScale?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MapNode: React.FC<MapNodeProps> = ({
|
export const MapNode: React.FC<MapNodeProps> = ({
|
||||||
@@ -43,6 +44,7 @@ export const MapNode: React.FC<MapNodeProps> = ({
|
|||||||
showJumps = false,
|
showJumps = false,
|
||||||
showKills = false,
|
showKills = false,
|
||||||
viewBoxWidth = 1200,
|
viewBoxWidth = 1200,
|
||||||
|
labelScale = 1,
|
||||||
}) => {
|
}) => {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
@@ -197,72 +199,92 @@ export const MapNode: React.FC<MapNodeProps> = ({
|
|||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
className={`transition-all duration-300 ${isHovered ? 'fill-purple-200' : 'fill-white'
|
className={`transition-all duration-300 ${isHovered ? 'fill-purple-200' : 'fill-white'
|
||||||
} pointer-events-none select-none`}
|
} pointer-events-none select-none`}
|
||||||
style={{
|
style={{
|
||||||
textShadow: '2px 2px 4px rgba(0,0,0,0.8)',
|
textShadow: '2px 2px 4px rgba(0,0,0,0.8)',
|
||||||
vectorEffect: 'non-scaling-stroke'
|
vectorEffect: 'non-scaling-stroke'
|
||||||
}}
|
}}
|
||||||
transform={`scale(${1 / (1200 / viewBoxWidth)})`}
|
transform={`scale(${(1 / (1200 / viewBoxWidth)) * labelScale})`}
|
||||||
transformOrigin="0 0"
|
|
||||||
>
|
>
|
||||||
{name} {security !== undefined && (
|
{name} {security !== undefined && (
|
||||||
<tspan fill={getSecurityColor(security)}>{security.toFixed(1)}</tspan>
|
<tspan fill={getSecurityColor(security)}>{security.toFixed(1)}</tspan>
|
||||||
)}
|
)}
|
||||||
</text>
|
</text>
|
||||||
<text
|
|
||||||
x="0"
|
{/* Dynamic text positioning based on what's shown */}
|
||||||
y={textOffset + 15}
|
{(() => {
|
||||||
textAnchor="middle"
|
let currentY = textOffset + 15;
|
||||||
fill="#a3a3a3"
|
const textElements = [];
|
||||||
fontSize="12"
|
|
||||||
className="pointer-events-none select-none"
|
// Add signatures if present
|
||||||
style={{
|
if (signatures !== undefined && signatures > 0) {
|
||||||
textShadow: '1px 1px 2px rgba(0,0,0,0.8)',
|
textElements.push(
|
||||||
vectorEffect: 'non-scaling-stroke'
|
<text
|
||||||
}}
|
key="signatures"
|
||||||
transform={`scale(${1 / (1200 / viewBoxWidth)})`}
|
x="0"
|
||||||
transformOrigin="0 0"
|
y={currentY}
|
||||||
>
|
textAnchor="middle"
|
||||||
{signatures !== undefined && signatures > 0 && `📡 ${signatures}`}
|
fill="#a3a3a3"
|
||||||
</text>
|
fontSize="12"
|
||||||
|
className="pointer-events-none select-none"
|
||||||
{/* Statistics display - fixed visual size regardless of zoom */}
|
style={{
|
||||||
{showJumps && jumps !== undefined && (
|
textShadow: '1px 1px 2px rgba(0,0,0,0.8)',
|
||||||
<text
|
vectorEffect: 'non-scaling-stroke'
|
||||||
x="0"
|
}}
|
||||||
y={textOffset + 30}
|
transform={`scale(${(1 / (1200 / viewBoxWidth)) * labelScale})`}
|
||||||
textAnchor="middle"
|
>
|
||||||
fill="#60a5fa"
|
📡 {signatures}
|
||||||
fontSize="10"
|
</text>
|
||||||
className="pointer-events-none select-none"
|
);
|
||||||
style={{
|
currentY += 15;
|
||||||
textShadow: '1px 1px 2px rgba(0,0,0,0.8)',
|
}
|
||||||
vectorEffect: 'non-scaling-stroke'
|
|
||||||
}}
|
// Add jumps if enabled and present
|
||||||
transform={`scale(${1 / (1200 / viewBoxWidth)})`}
|
if (showJumps && jumps !== undefined) {
|
||||||
transformOrigin="0 0"
|
textElements.push(
|
||||||
>
|
<text
|
||||||
🚀 {jumps}
|
key="jumps"
|
||||||
</text>
|
x="0"
|
||||||
)}
|
y={currentY}
|
||||||
|
textAnchor="middle"
|
||||||
{showKills && kills !== undefined && (
|
fill="#60a5fa"
|
||||||
<text
|
fontSize="10"
|
||||||
x="0"
|
className="pointer-events-none select-none"
|
||||||
y={textOffset + 45}
|
style={{
|
||||||
textAnchor="middle"
|
textShadow: '1px 1px 2px rgba(0,0,0,0.8)',
|
||||||
fill="#f87171"
|
vectorEffect: 'non-scaling-stroke'
|
||||||
fontSize="10"
|
}}
|
||||||
className="pointer-events-none select-none"
|
transform={`scale(${(1 / (1200 / viewBoxWidth)) * labelScale})`}
|
||||||
style={{
|
>
|
||||||
textShadow: '1px 1px 2px rgba(0,0,0,0.8)',
|
🚀 {jumps}
|
||||||
vectorEffect: 'non-scaling-stroke'
|
</text>
|
||||||
}}
|
);
|
||||||
transform={`scale(${1 / (1200 / viewBoxWidth)})`}
|
currentY += 15;
|
||||||
transformOrigin="0 0"
|
}
|
||||||
>
|
|
||||||
⚔️ {kills}
|
// Add kills if enabled and present
|
||||||
</text>
|
if (showKills && kills !== undefined) {
|
||||||
)}
|
textElements.push(
|
||||||
|
<text
|
||||||
|
key="kills"
|
||||||
|
x="0"
|
||||||
|
y={currentY}
|
||||||
|
textAnchor="middle"
|
||||||
|
fill="#f87171"
|
||||||
|
fontSize="10"
|
||||||
|
className="pointer-events-none select-none"
|
||||||
|
style={{
|
||||||
|
textShadow: '1px 1px 2px rgba(0,0,0,0.8)',
|
||||||
|
vectorEffect: 'non-scaling-stroke'
|
||||||
|
}}
|
||||||
|
transform={`scale(${(1 / (1200 / viewBoxWidth)) * labelScale})`}
|
||||||
|
>
|
||||||
|
⚔️ {kills}
|
||||||
|
</text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textElements;
|
||||||
|
})()}
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
import React, { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { MapNode } from '@/components/MapNode';
|
import { MapNode } from '@/components/MapNode';
|
||||||
import { SystemContextMenu } from '@/components/SystemContextMenu';
|
import { SystemContextMenu } from '@/components/SystemContextMenu';
|
||||||
@@ -11,7 +11,7 @@ import { Header } from './Header';
|
|||||||
import { ListCharacters, StartESILogin, SetDestinationForAll, PostRouteForAllByNames, GetCharacterLocations } from 'wailsjs/go/main/App';
|
import { ListCharacters, StartESILogin, SetDestinationForAll, PostRouteForAllByNames, GetCharacterLocations } from 'wailsjs/go/main/App';
|
||||||
import { toast } from '@/hooks/use-toast';
|
import { toast } from '@/hooks/use-toast';
|
||||||
import { getSystemsRegions } from '@/utils/systemApi';
|
import { getSystemsRegions } from '@/utils/systemApi';
|
||||||
import { useSystemJumps, useSystemKills } from '@/hooks/useSystemStatistics';
|
import { useSystemJumps, useSystemKills, resolveSystemID } from '@/hooks/useSystemStatistics';
|
||||||
import { StatisticsToggle } from './StatisticsToggle';
|
import { StatisticsToggle } from './StatisticsToggle';
|
||||||
|
|
||||||
// Interaction/indicator constants
|
// Interaction/indicator constants
|
||||||
@@ -33,6 +33,7 @@ interface RegionMapProps {
|
|||||||
focusSystem?: string;
|
focusSystem?: string;
|
||||||
isCompact?: boolean;
|
isCompact?: boolean;
|
||||||
isWormholeRegion?: boolean;
|
isWormholeRegion?: boolean;
|
||||||
|
header?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContextMenuState {
|
interface ContextMenuState {
|
||||||
@@ -93,7 +94,7 @@ const ensureUniversePositions = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormholeRegion = false }: RegionMapProps) => {
|
export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormholeRegion = false, header = true }: RegionMapProps) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [viewBox, setViewBox] = useState({ x: 0, y: 0, width: 1200, height: 800 });
|
const [viewBox, setViewBox] = useState({ x: 0, y: 0, width: 1200, height: 800 });
|
||||||
const [isPanning, setIsPanning] = useState(false);
|
const [isPanning, setIsPanning] = useState(false);
|
||||||
@@ -119,10 +120,11 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
// Statistics state - MUST default to false to avoid API spam!
|
// Statistics state - MUST default to false to avoid API spam!
|
||||||
const [showJumps, setShowJumps] = useState(false);
|
const [showJumps, setShowJumps] = useState(false);
|
||||||
const [showKills, setShowKills] = useState(false);
|
const [showKills, setShowKills] = useState(false);
|
||||||
|
|
||||||
// Cache for system name to ID mappings
|
// System ID cache for statistics lookup
|
||||||
const [systemIDCache, setSystemIDCache] = useState<Map<string, number>>(new Map());
|
const [systemIDCache, setSystemIDCache] = useState<Map<string, number>>(new Map());
|
||||||
|
|
||||||
|
|
||||||
// New: selection/aim state for left-click aimbot behavior
|
// New: selection/aim state for left-click aimbot behavior
|
||||||
const [isSelecting, setIsSelecting] = useState(false);
|
const [isSelecting, setIsSelecting] = useState(false);
|
||||||
const [indicatedSystem, setIndicatedSystem] = useState<string | null>(null);
|
const [indicatedSystem, setIndicatedSystem] = useState<string | null>(null);
|
||||||
@@ -180,19 +182,35 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
}, [viaMode, viaDest, viaQueue]);
|
}, [viaMode, viaDest, viaQueue]);
|
||||||
|
|
||||||
const { data: rsystems, isLoading, error } = useRegionData(regionName);
|
const { data: rsystems, isLoading, error } = useRegionData(regionName);
|
||||||
|
|
||||||
// Fetch statistics data - only when toggles are enabled
|
// Fetch statistics data - only when toggles are enabled
|
||||||
const { data: jumpsData } = useSystemJumps(showJumps);
|
const { data: jumpsData } = useSystemJumps(showJumps);
|
||||||
const { data: killsData } = useSystemKills(showKills);
|
const { data: killsData } = useSystemKills(showKills);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoading && error == null && rsystems && rsystems.size > 0)
|
if (!isLoading && error == null && rsystems && rsystems.size > 0) {
|
||||||
setSystems(rsystems);
|
setSystems(rsystems);
|
||||||
|
|
||||||
|
// Pre-resolve all system IDs for statistics lookup
|
||||||
|
const resolveSystemIDs = async () => {
|
||||||
|
const newCache = new Map<string, number>();
|
||||||
|
for (const systemName of rsystems.keys()) {
|
||||||
|
try {
|
||||||
|
const id = await resolveSystemID(systemName);
|
||||||
|
if (id) {
|
||||||
|
newCache.set(systemName, id);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to resolve system ID for ${systemName}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSystemIDCache(newCache);
|
||||||
|
};
|
||||||
|
|
||||||
|
resolveSystemIDs();
|
||||||
|
}
|
||||||
}, [rsystems, isLoading, error]);
|
}, [rsystems, isLoading, error]);
|
||||||
|
|
||||||
// For now, we'll use a simplified approach without system ID resolution
|
|
||||||
// The ESI data will be displayed for systems that have data, but we won't
|
|
||||||
// be able to match system names to IDs until the binding issue is resolved
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!systems || systems.size === 0) return;
|
if (!systems || systems.size === 0) return;
|
||||||
@@ -519,45 +537,50 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
return nearestName;
|
return nearestName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create lookup maps for system statistics
|
||||||
|
const jumpsBySystemID = useMemo(() => {
|
||||||
|
if (!jumpsData) return new Map();
|
||||||
|
const map = new Map<number, number>();
|
||||||
|
jumpsData.forEach(jump => {
|
||||||
|
map.set(jump.system_id, jump.ship_jumps);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [jumpsData]);
|
||||||
|
|
||||||
|
const killsBySystemID = useMemo(() => {
|
||||||
|
if (!killsData) return new Map();
|
||||||
|
const map = new Map<number, number>();
|
||||||
|
killsData.forEach(kill => {
|
||||||
|
map.set(kill.system_id, kill.ship_kills);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [killsData]);
|
||||||
|
|
||||||
// Helper functions to get statistics for a system
|
// Helper functions to get statistics for a system
|
||||||
const getSystemJumps = (systemName: string): number | undefined => {
|
const getSystemJumps = (systemName: string): number | undefined => {
|
||||||
if (!jumpsData || !showJumps) return undefined;
|
if (!showJumps) return undefined;
|
||||||
|
|
||||||
// For demonstration, show the first few systems with jump data
|
const systemID = systemIDCache.get(systemName);
|
||||||
// This is a temporary solution until system ID resolution is fixed
|
if (!systemID) return undefined;
|
||||||
const systemNames = Array.from(systems.keys());
|
|
||||||
const systemIndex = systemNames.indexOf(systemName);
|
const jumps = jumpsBySystemID.get(systemID);
|
||||||
|
if (!jumps || jumps === 0) return undefined;
|
||||||
if (systemIndex >= 0 && systemIndex < jumpsData.length) {
|
|
||||||
const jumps = jumpsData[systemIndex].ship_jumps;
|
console.log(`🚀 Found ${jumps} jumps for ${systemName} (ID: ${systemID})`);
|
||||||
// Don't show 0 values - return undefined so nothing is rendered
|
return jumps;
|
||||||
if (jumps === 0) return undefined;
|
|
||||||
|
|
||||||
console.log(`🚀 Found ${jumps} jumps for ${systemName} (using index ${systemIndex})`);
|
|
||||||
return jumps;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSystemKills = (systemName: string): number | undefined => {
|
const getSystemKills = (systemName: string): number | undefined => {
|
||||||
if (!killsData || !showKills) return undefined;
|
if (!showKills) return undefined;
|
||||||
|
|
||||||
// For demonstration, show the first few systems with kill data
|
const systemID = systemIDCache.get(systemName);
|
||||||
// This is a temporary solution until system ID resolution is fixed
|
if (!systemID) return undefined;
|
||||||
const systemNames = Array.from(systems.keys());
|
|
||||||
const systemIndex = systemNames.indexOf(systemName);
|
const kills = killsBySystemID.get(systemID);
|
||||||
|
if (!kills || kills === 0) return undefined;
|
||||||
if (systemIndex >= 0 && systemIndex < killsData.length) {
|
|
||||||
const kills = killsData[systemIndex].ship_kills;
|
console.log(`⚔️ Found ${kills} kills for ${systemName} (ID: ${systemID})`);
|
||||||
// Don't show 0 values - return undefined so nothing is rendered
|
return kills;
|
||||||
if (kills === 0) return undefined;
|
|
||||||
|
|
||||||
console.log(`⚔️ Found ${kills} kills for ${systemName} (using index ${systemIndex})`);
|
|
||||||
return kills;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Commit shift selection: toggle all systems within radius
|
// Commit shift selection: toggle all systems within radius
|
||||||
@@ -999,13 +1022,15 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 relative">
|
<div className="w-full h-full bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 relative">
|
||||||
<Header
|
{header && (
|
||||||
title={`Region: ${regionName}`}
|
<Header
|
||||||
breadcrumbs={[
|
title={`Region: ${regionName}`}
|
||||||
{ label: "Universe", path: "/" },
|
breadcrumbs={[
|
||||||
{ label: regionName }
|
{ label: "Universe", path: "/" },
|
||||||
]}
|
{ label: regionName }
|
||||||
/>
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<svg
|
<svg
|
||||||
ref={svgRef}
|
ref={svgRef}
|
||||||
width="100%"
|
width="100%"
|
||||||
@@ -1089,6 +1114,7 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
showJumps={showJumps}
|
showJumps={showJumps}
|
||||||
showKills={showKills}
|
showKills={showKills}
|
||||||
viewBoxWidth={viewBox.width}
|
viewBoxWidth={viewBox.width}
|
||||||
|
labelScale={isCompact ? 2.0 : 1}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
@@ -1200,13 +1226,15 @@ export const RegionMap = ({ regionName, focusSystem, isCompact = false, isWormho
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Statistics Toggle */}
|
{/* Statistics Toggle - positioned to avoid overlaps */}
|
||||||
<StatisticsToggle
|
<div className="absolute bottom-4 left-4">
|
||||||
jumpsEnabled={showJumps}
|
<StatisticsToggle
|
||||||
killsEnabled={showKills}
|
jumpsEnabled={showJumps}
|
||||||
onJumpsToggle={setShowJumps}
|
killsEnabled={showKills}
|
||||||
onKillsToggle={setShowKills}
|
onJumpsToggle={setShowJumps}
|
||||||
/>
|
onKillsToggle={setShowKills}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Context Menu */}
|
{/* Context Menu */}
|
||||||
{contextMenu && (
|
{contextMenu && (
|
||||||
|
@@ -75,10 +75,12 @@ export const SignatureCard = ({ signature, onDelete, onUpdate }: SignatureCardPr
|
|||||||
{signature.signame || 'Unnamed Signature'}
|
{signature.signame || 'Unnamed Signature'}
|
||||||
</h3>
|
</h3>
|
||||||
{signature.note && (
|
{signature.note && (
|
||||||
<div className="mt-2">
|
<div className="mt-2 flex flex-wrap gap-1 justify-center">
|
||||||
<Badge variant="outline" className="bg-blue-900/50 text-blue-200 border-blue-500 px-3 py-1 text-sm font-semibold">
|
{signature.note.split(';').filter(Boolean).map((note, index) => (
|
||||||
{signature.note}
|
<Badge key={index} variant="outline" className="bg-blue-900/50 text-blue-200 border-blue-500 px-3 py-1 text-sm font-semibold">
|
||||||
</Badge>
|
{note.trim()}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -117,9 +117,13 @@ export const SignatureListItem = ({ signature, onDelete, onUpdate }: SignatureLi
|
|||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
{signature.note && (
|
{signature.note && (
|
||||||
<Badge variant="outline" className="bg-blue-900/50 text-blue-200 border-blue-500 px-2 py-0.5 text-sm font-semibold ml-2">
|
<div className="flex flex-wrap gap-1 ml-2">
|
||||||
{signature.note}
|
{signature.note.split(';').filter(Boolean).map((note, index) => (
|
||||||
</Badge>
|
<Badge key={index} variant="outline" className="bg-blue-900/50 text-blue-200 border-blue-500 px-2 py-0.5 text-sm font-semibold">
|
||||||
|
{note.trim()}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
|
|
||||||
interface StatisticsToggleProps {
|
interface StatisticsToggleProps {
|
||||||
jumpsEnabled: boolean;
|
jumpsEnabled: boolean;
|
||||||
@@ -15,28 +14,30 @@ export const StatisticsToggle: React.FC<StatisticsToggleProps> = ({
|
|||||||
onKillsToggle,
|
onKillsToggle,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="absolute top-2 left-2 bg-slate-800/90 backdrop-blur-sm rounded-lg p-3 shadow-lg border border-slate-700">
|
<div className="bg-slate-800/90 backdrop-blur-sm rounded-lg p-2 shadow-lg border border-slate-700">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex gap-2">
|
||||||
<div className="flex items-center gap-3">
|
<button
|
||||||
<Switch
|
onClick={() => onJumpsToggle(!jumpsEnabled)}
|
||||||
id="jumps-toggle"
|
className={`p-2 rounded transition-colors ${
|
||||||
checked={jumpsEnabled}
|
jumpsEnabled
|
||||||
onCheckedChange={onJumpsToggle}
|
? 'bg-blue-600/20 text-blue-400 hover:bg-blue-600/30'
|
||||||
/>
|
: 'bg-gray-600/20 text-gray-400 hover:bg-gray-600/30'
|
||||||
<label htmlFor="jumps-toggle" className="text-sm font-medium text-white">
|
}`}
|
||||||
🚀 Show Jumps
|
title={jumpsEnabled ? 'Hide Jumps' : 'Show Jumps'}
|
||||||
</label>
|
>
|
||||||
</div>
|
🚀
|
||||||
<div className="flex items-center gap-3">
|
</button>
|
||||||
<Switch
|
<button
|
||||||
id="kills-toggle"
|
onClick={() => onKillsToggle(!killsEnabled)}
|
||||||
checked={killsEnabled}
|
className={`p-2 rounded transition-colors ${
|
||||||
onCheckedChange={onKillsToggle}
|
killsEnabled
|
||||||
/>
|
? 'bg-red-600/20 text-red-400 hover:bg-red-600/30'
|
||||||
<label htmlFor="kills-toggle" className="text-sm font-medium text-white">
|
: 'bg-gray-600/20 text-gray-400 hover:bg-gray-600/30'
|
||||||
⚔️ Show Kills
|
}`}
|
||||||
</label>
|
title={killsEnabled ? 'Hide Kills' : 'Show Kills'}
|
||||||
</div>
|
>
|
||||||
|
⚔️
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -11,8 +11,13 @@ export enum Collections {
|
|||||||
Mfas = "_mfas",
|
Mfas = "_mfas",
|
||||||
Otps = "_otps",
|
Otps = "_otps",
|
||||||
Superusers = "_superusers",
|
Superusers = "_superusers",
|
||||||
|
IndBillitem = "ind_billItem",
|
||||||
|
IndChar = "ind_char",
|
||||||
|
IndJob = "ind_job",
|
||||||
|
IndTransaction = "ind_transaction",
|
||||||
Regionview = "regionview",
|
Regionview = "regionview",
|
||||||
Signature = "signature",
|
Signature = "signature",
|
||||||
|
SignatureNoteRules = "signature_note_rules",
|
||||||
Sigview = "sigview",
|
Sigview = "sigview",
|
||||||
System = "system",
|
System = "system",
|
||||||
WormholeSystems = "wormholeSystems",
|
WormholeSystems = "wormholeSystems",
|
||||||
@@ -94,6 +99,74 @@ export type SuperusersRecord = {
|
|||||||
verified?: boolean
|
verified?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IndBillitemRecord = {
|
||||||
|
created?: IsoDateString
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
quantity: number
|
||||||
|
updated?: IsoDateString
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IndCharRecord = {
|
||||||
|
created?: IsoDateString
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
updated?: IsoDateString
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum IndJobStatusOptions {
|
||||||
|
"Planned" = "Planned",
|
||||||
|
"Acquisition" = "Acquisition",
|
||||||
|
"Running" = "Running",
|
||||||
|
"Done" = "Done",
|
||||||
|
"Selling" = "Selling",
|
||||||
|
"Closed" = "Closed",
|
||||||
|
"Tracked" = "Tracked",
|
||||||
|
"Staging" = "Staging",
|
||||||
|
"Inbound" = "Inbound",
|
||||||
|
"Outbound" = "Outbound",
|
||||||
|
"Delivered" = "Delivered",
|
||||||
|
"Queued" = "Queued",
|
||||||
|
}
|
||||||
|
export type IndJobRecord = {
|
||||||
|
billOfMaterials?: RecordIdString[]
|
||||||
|
character?: RecordIdString
|
||||||
|
consumedMaterials?: RecordIdString[]
|
||||||
|
created?: IsoDateString
|
||||||
|
expenditures?: RecordIdString[]
|
||||||
|
id: string
|
||||||
|
income?: RecordIdString[]
|
||||||
|
jobEnd?: IsoDateString
|
||||||
|
jobStart?: IsoDateString
|
||||||
|
outputItem: string
|
||||||
|
outputQuantity: number
|
||||||
|
parallel?: number
|
||||||
|
produced?: number
|
||||||
|
projectedCost?: number
|
||||||
|
projectedRevenue?: number
|
||||||
|
runtime?: number
|
||||||
|
saleEnd?: IsoDateString
|
||||||
|
saleStart?: IsoDateString
|
||||||
|
status: IndJobStatusOptions
|
||||||
|
updated?: IsoDateString
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IndTransactionRecord = {
|
||||||
|
buyer?: string
|
||||||
|
corporation?: string
|
||||||
|
created?: IsoDateString
|
||||||
|
date: IsoDateString
|
||||||
|
id: string
|
||||||
|
itemName: string
|
||||||
|
job?: RecordIdString
|
||||||
|
location?: string
|
||||||
|
quantity: number
|
||||||
|
totalPrice: number
|
||||||
|
unitPrice: number
|
||||||
|
updated?: IsoDateString
|
||||||
|
wallet?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type RegionviewRecord = {
|
export type RegionviewRecord = {
|
||||||
id: string
|
id: string
|
||||||
sigcount?: number
|
sigcount?: number
|
||||||
@@ -114,6 +187,15 @@ export type SignatureRecord = {
|
|||||||
updated?: IsoDateString
|
updated?: IsoDateString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SignatureNoteRulesRecord = {
|
||||||
|
created?: IsoDateString
|
||||||
|
enabled?: boolean
|
||||||
|
id: string
|
||||||
|
note: string
|
||||||
|
regex: string
|
||||||
|
updated?: IsoDateString
|
||||||
|
}
|
||||||
|
|
||||||
export type SigviewRecord = {
|
export type SigviewRecord = {
|
||||||
created?: IsoDateString
|
created?: IsoDateString
|
||||||
dangerous?: boolean
|
dangerous?: boolean
|
||||||
@@ -153,8 +235,13 @@ export type ExternalauthsResponse<Texpand = unknown> = Required<ExternalauthsRec
|
|||||||
export type MfasResponse<Texpand = unknown> = Required<MfasRecord> & BaseSystemFields<Texpand>
|
export type MfasResponse<Texpand = unknown> = Required<MfasRecord> & BaseSystemFields<Texpand>
|
||||||
export type OtpsResponse<Texpand = unknown> = Required<OtpsRecord> & BaseSystemFields<Texpand>
|
export type OtpsResponse<Texpand = unknown> = Required<OtpsRecord> & BaseSystemFields<Texpand>
|
||||||
export type SuperusersResponse<Texpand = unknown> = Required<SuperusersRecord> & AuthSystemFields<Texpand>
|
export type SuperusersResponse<Texpand = unknown> = Required<SuperusersRecord> & AuthSystemFields<Texpand>
|
||||||
|
export type IndBillitemResponse<Texpand = unknown> = Required<IndBillitemRecord> & BaseSystemFields<Texpand>
|
||||||
|
export type IndCharResponse<Texpand = unknown> = Required<IndCharRecord> & BaseSystemFields<Texpand>
|
||||||
|
export type IndJobResponse<Texpand = unknown> = Required<IndJobRecord> & BaseSystemFields<Texpand>
|
||||||
|
export type IndTransactionResponse<Texpand = unknown> = Required<IndTransactionRecord> & BaseSystemFields<Texpand>
|
||||||
export type RegionviewResponse<Texpand = unknown> = Required<RegionviewRecord> & BaseSystemFields<Texpand>
|
export type RegionviewResponse<Texpand = unknown> = Required<RegionviewRecord> & BaseSystemFields<Texpand>
|
||||||
export type SignatureResponse<Texpand = unknown> = Required<SignatureRecord> & BaseSystemFields<Texpand>
|
export type SignatureResponse<Texpand = unknown> = Required<SignatureRecord> & BaseSystemFields<Texpand>
|
||||||
|
export type SignatureNoteRulesResponse<Texpand = unknown> = Required<SignatureNoteRulesRecord> & BaseSystemFields<Texpand>
|
||||||
export type SigviewResponse<Texpand = unknown> = Required<SigviewRecord> & BaseSystemFields<Texpand>
|
export type SigviewResponse<Texpand = unknown> = Required<SigviewRecord> & BaseSystemFields<Texpand>
|
||||||
export type SystemResponse<Texpand = unknown> = Required<SystemRecord> & BaseSystemFields<Texpand>
|
export type SystemResponse<Texpand = unknown> = Required<SystemRecord> & BaseSystemFields<Texpand>
|
||||||
export type WormholeSystemsResponse<Texpand = unknown> = Required<WormholeSystemsRecord> & BaseSystemFields<Texpand>
|
export type WormholeSystemsResponse<Texpand = unknown> = Required<WormholeSystemsRecord> & BaseSystemFields<Texpand>
|
||||||
@@ -167,8 +254,13 @@ export type CollectionRecords = {
|
|||||||
_mfas: MfasRecord
|
_mfas: MfasRecord
|
||||||
_otps: OtpsRecord
|
_otps: OtpsRecord
|
||||||
_superusers: SuperusersRecord
|
_superusers: SuperusersRecord
|
||||||
|
ind_billItem: IndBillitemRecord
|
||||||
|
ind_char: IndCharRecord
|
||||||
|
ind_job: IndJobRecord
|
||||||
|
ind_transaction: IndTransactionRecord
|
||||||
regionview: RegionviewRecord
|
regionview: RegionviewRecord
|
||||||
signature: SignatureRecord
|
signature: SignatureRecord
|
||||||
|
signature_note_rules: SignatureNoteRulesRecord
|
||||||
sigview: SigviewRecord
|
sigview: SigviewRecord
|
||||||
system: SystemRecord
|
system: SystemRecord
|
||||||
wormholeSystems: WormholeSystemsRecord
|
wormholeSystems: WormholeSystemsRecord
|
||||||
@@ -180,8 +272,13 @@ export type CollectionResponses = {
|
|||||||
_mfas: MfasResponse
|
_mfas: MfasResponse
|
||||||
_otps: OtpsResponse
|
_otps: OtpsResponse
|
||||||
_superusers: SuperusersResponse
|
_superusers: SuperusersResponse
|
||||||
|
ind_billItem: IndBillitemResponse
|
||||||
|
ind_char: IndCharResponse
|
||||||
|
ind_job: IndJobResponse
|
||||||
|
ind_transaction: IndTransactionResponse
|
||||||
regionview: RegionviewResponse
|
regionview: RegionviewResponse
|
||||||
signature: SignatureResponse
|
signature: SignatureResponse
|
||||||
|
signature_note_rules: SignatureNoteRulesResponse
|
||||||
sigview: SigviewResponse
|
sigview: SigviewResponse
|
||||||
system: SystemResponse
|
system: SystemResponse
|
||||||
wormholeSystems: WormholeSystemsResponse
|
wormholeSystems: WormholeSystemsResponse
|
||||||
@@ -196,8 +293,13 @@ export type TypedPocketBase = PocketBase & {
|
|||||||
collection(idOrName: '_mfas'): RecordService<MfasResponse>
|
collection(idOrName: '_mfas'): RecordService<MfasResponse>
|
||||||
collection(idOrName: '_otps'): RecordService<OtpsResponse>
|
collection(idOrName: '_otps'): RecordService<OtpsResponse>
|
||||||
collection(idOrName: '_superusers'): RecordService<SuperusersResponse>
|
collection(idOrName: '_superusers'): RecordService<SuperusersResponse>
|
||||||
|
collection(idOrName: 'ind_billItem'): RecordService<IndBillitemResponse>
|
||||||
|
collection(idOrName: 'ind_char'): RecordService<IndCharResponse>
|
||||||
|
collection(idOrName: 'ind_job'): RecordService<IndJobResponse>
|
||||||
|
collection(idOrName: 'ind_transaction'): RecordService<IndTransactionResponse>
|
||||||
collection(idOrName: 'regionview'): RecordService<RegionviewResponse>
|
collection(idOrName: 'regionview'): RecordService<RegionviewResponse>
|
||||||
collection(idOrName: 'signature'): RecordService<SignatureResponse>
|
collection(idOrName: 'signature'): RecordService<SignatureResponse>
|
||||||
|
collection(idOrName: 'signature_note_rules'): RecordService<SignatureNoteRulesResponse>
|
||||||
collection(idOrName: 'sigview'): RecordService<SigviewResponse>
|
collection(idOrName: 'sigview'): RecordService<SigviewResponse>
|
||||||
collection(idOrName: 'system'): RecordService<SystemResponse>
|
collection(idOrName: 'system'): RecordService<SystemResponse>
|
||||||
collection(idOrName: 'wormholeSystems'): RecordService<WormholeSystemsResponse>
|
collection(idOrName: 'wormholeSystems'): RecordService<WormholeSystemsResponse>
|
||||||
|
124
frontend/src/pages/SignatureRules.tsx
Normal file
124
frontend/src/pages/SignatureRules.tsx
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import pb from '@/lib/pocketbase';
|
||||||
|
import { Header } from '@/components/Header';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { toast } from '@/hooks/use-toast';
|
||||||
|
import { SignatureNoteRulesResponse, Collections } from '@/lib/pbtypes';
|
||||||
|
|
||||||
|
export const SignatureRules = () => {
|
||||||
|
const [rules, setRules] = useState<SignatureNoteRulesResponse[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [creating, setCreating] = useState({ regex: '', note: '' });
|
||||||
|
|
||||||
|
const load = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const list = await pb.collection(Collections.SignatureNoteRules).getFullList<SignatureNoteRulesResponse>({ batch: 1000, sort: '-updated' });
|
||||||
|
setRules(list);
|
||||||
|
} catch (e: any) {
|
||||||
|
toast({ title: 'Load failed', description: String(e), variant: 'destructive' });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => { load(); }, []);
|
||||||
|
|
||||||
|
const handleCreate = async () => {
|
||||||
|
if (!creating.regex.trim() || !creating.note.trim()) return;
|
||||||
|
try {
|
||||||
|
await pb.collection(Collections.SignatureNoteRules).create({ regex: creating.regex.trim(), note: creating.note.trim(), enabled: true });
|
||||||
|
setCreating({ regex: '', note: '' });
|
||||||
|
await load();
|
||||||
|
toast({ title: 'Rule added', description: 'New rule created.' });
|
||||||
|
} catch (e: any) {
|
||||||
|
toast({ title: 'Create failed', description: String(e), variant: 'destructive' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdate = async (id: string, patch: Partial<SignatureNoteRulesResponse>) => {
|
||||||
|
try {
|
||||||
|
await pb.collection(Collections.SignatureNoteRules).update(id, patch);
|
||||||
|
await load();
|
||||||
|
} catch (e: any) {
|
||||||
|
toast({ title: 'Update failed', description: String(e), variant: 'destructive' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (id: string) => {
|
||||||
|
try {
|
||||||
|
await pb.collection(Collections.SignatureNoteRules).delete(id);
|
||||||
|
await load();
|
||||||
|
toast({ title: 'Rule deleted' });
|
||||||
|
} catch (e: any) {
|
||||||
|
toast({ title: 'Delete failed', description: String(e), variant: 'destructive' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-screen w-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 overflow-hidden">
|
||||||
|
<div className="h-full flex flex-col">
|
||||||
|
<Header title="Signature Rules" breadcrumbs={[{ label: 'Universe', path: '/' }, { label: 'Signature Rules' }]} />
|
||||||
|
<div className="flex-1 overflow-auto p-4 space-y-4">
|
||||||
|
<div className="bg-black/20 border border-purple-500/30 rounded p-4 space-y-3">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Regex (e.g. ^Angel.*Outpost$|Guristas.*)"
|
||||||
|
value={creating.regex}
|
||||||
|
onChange={e => setCreating({ ...creating, regex: e.target.value })}
|
||||||
|
className="font-mono"
|
||||||
|
/>
|
||||||
|
<Input placeholder="Note/Tag (e.g. 3/10)" value={creating.note} onChange={e => setCreating({ ...creating, note: e.target.value })} />
|
||||||
|
<Button onClick={handleCreate} disabled={loading}>Add Rule</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-black/20 border border-purple-500/30 rounded">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="text-slate-300">Enabled</TableHead>
|
||||||
|
<TableHead className="text-slate-300">Regex</TableHead>
|
||||||
|
<TableHead className="text-slate-300">Note</TableHead>
|
||||||
|
<TableHead></TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{rules.map(r => (
|
||||||
|
<TableRow key={r.id}>
|
||||||
|
<TableCell>
|
||||||
|
<Switch checked={!!r.enabled} onCheckedChange={(v) => handleUpdate(r.id, { enabled: v })} />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="max-w-0">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={r.regex}
|
||||||
|
onChange={e => setRules(prev => prev.map(x => x.id === r.id ? { ...x, regex: e.target.value } : x))}
|
||||||
|
onBlur={e => handleUpdate(r.id, { regex: e.currentTarget.value })}
|
||||||
|
className="font-mono"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="max-w-0">
|
||||||
|
<Input
|
||||||
|
value={r.note}
|
||||||
|
onChange={e => setRules(prev => prev.map(x => x.id === r.id ? { ...x, note: e.target.value } : x))}
|
||||||
|
onBlur={e => handleUpdate(r.id, { note: e.currentTarget.value })}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
<Button variant="destructive" onClick={() => handleDelete(r.id)}>Delete</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@@ -8,7 +8,7 @@ import { Header } from "@/components/Header";
|
|||||||
import { parseSignature, parseScannedPercentage } from "@/utils/signatureParser";
|
import { parseSignature, parseScannedPercentage } from "@/utils/signatureParser";
|
||||||
import { getSystemId } from "@/utils/systemApi";
|
import { getSystemId } from "@/utils/systemApi";
|
||||||
import pb from "@/lib/pocketbase";
|
import pb from "@/lib/pocketbase";
|
||||||
import { SigviewRecord as Signature, SignatureRecord } from "@/lib/pbtypes";
|
import { SigviewRecord as Signature, SignatureRecord, SignatureNoteRulesResponse, Collections } from "@/lib/pbtypes";
|
||||||
|
|
||||||
export const SystemView = () => {
|
export const SystemView = () => {
|
||||||
const { system, region } = useParams();
|
const { system, region } = useParams();
|
||||||
@@ -163,12 +163,17 @@ export const SystemView = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const systemId = await getSystemId(system);
|
const systemId = await getSystemId(system);
|
||||||
|
let rules: Array<Pick<SignatureNoteRulesResponse, 'regex' | 'note' | 'enabled'>> = [];
|
||||||
|
try {
|
||||||
|
const list = await pb.collection(Collections.SignatureNoteRules).getFullList<SignatureNoteRulesResponse>({ batch: 1000 });
|
||||||
|
rules = list.filter(r => r.enabled).map(r => ({ regex: r.regex, note: r.note, enabled: r.enabled }));
|
||||||
|
} catch { }
|
||||||
const lines = pastedText.trim().split('\n').filter(line => line.trim());
|
const lines = pastedText.trim().split('\n').filter(line => line.trim());
|
||||||
const parsedSignatures: Omit<Signature, 'id'>[] = [];
|
const parsedSignatures: Omit<Signature, 'id'>[] = [];
|
||||||
|
|
||||||
// Parse all signatures
|
// Parse all signatures
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const parsed = parseSignature(line);
|
const parsed = parseSignature(line, rules);
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
parsedSignatures.push({
|
parsedSignatures.push({
|
||||||
...parsed,
|
...parsed,
|
||||||
@@ -276,6 +281,7 @@ export const SystemView = () => {
|
|||||||
regionName={region}
|
regionName={region}
|
||||||
focusSystem={system}
|
focusSystem={system}
|
||||||
isCompact={true}
|
isCompact={true}
|
||||||
|
header={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,52 +1,7 @@
|
|||||||
import { SigviewRecord as Signature } from "@/lib/pbtypes";
|
import { SigviewRecord as Signature, SignatureNoteRulesResponse } from "@/lib/pbtypes";
|
||||||
|
|
||||||
const oneOutOfTen = [
|
|
||||||
"Minmatar Contracted Bio-Farm",
|
|
||||||
"Old Meanie - Cultivation Center",
|
|
||||||
"Pith Robux Asteroid Mining & Co.",
|
|
||||||
"Sansha Military Outpost",
|
|
||||||
"Serpentis Drug Outlet",
|
|
||||||
];
|
|
||||||
const twoOutOfTen = [
|
|
||||||
"Angel Creo-Corp Mining",
|
|
||||||
"Blood Raider Human Farm",
|
|
||||||
"Pith Merchant Depot",
|
|
||||||
"Sansha Acclimatization Facility",
|
|
||||||
"Serpentis Live Cargo Distribution Facilities",
|
|
||||||
"Rogue Drone Infestation Sprout",
|
|
||||||
];
|
|
||||||
const threeOutOfTen = [
|
|
||||||
"Angel Repurposed Outpost",
|
|
||||||
"Blood Raider Intelligence Collection Point",
|
|
||||||
"Guristas Guerilla Grounds",
|
|
||||||
"Sansha's Command Relay Outpost",
|
|
||||||
"Serpentis Narcotic Warehouses",
|
|
||||||
"Rogue Drone Asteroid Infestation",
|
|
||||||
];
|
|
||||||
const fourOutOfTen = [
|
|
||||||
"Angel Cartel Occupied Mining Colony",
|
|
||||||
"Mul-Zatah Monastery",
|
|
||||||
"Guristas Scout Outpost",
|
|
||||||
"Sansha's Nation Occupied Mining Colony",
|
|
||||||
"Serpentis Phi-Outpost",
|
|
||||||
"Drone Infested Mine",
|
|
||||||
];
|
|
||||||
const fiveOutOfTen = [
|
|
||||||
"Angel's Red Light District",
|
|
||||||
"Blood Raider Psychotropics Depot",
|
|
||||||
"Guristas Hallucinogen Supply Waypoint",
|
|
||||||
"Sansha's Nation Neural Paralytic Facility",
|
|
||||||
"Serpentis Corporation Hydroponics Site",
|
|
||||||
"Outgrowth Rogue Drone Hive",
|
|
||||||
];
|
|
||||||
function isFourOutOfTen(signature: string): boolean {
|
|
||||||
return fourOutOfTen.some((s) => signature.includes(s));
|
|
||||||
}
|
|
||||||
function isFiveOutOfTen(signature: string): boolean {
|
|
||||||
return fiveOutOfTen.some((s) => signature.includes(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
export const parseSignature = (text: string): Omit<Signature, 'system' | 'id' | 'sysid'> | null => {
|
export const parseSignature = (text: string, rules?: Array<Pick<SignatureNoteRulesResponse, 'regex' | 'note' | 'enabled'>>): Omit<Signature, 'system' | 'id' | 'sysid'> | null => {
|
||||||
const parts = text.split('\t');
|
const parts = text.split('\t');
|
||||||
if (parts.length < 4) return null;
|
if (parts.length < 4) return null;
|
||||||
|
|
||||||
@@ -56,16 +11,26 @@ export const parseSignature = (text: string): Omit<Signature, 'system' | 'id' |
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let note = "";
|
const appliedNotes: string[] = [];
|
||||||
const isFour = isFourOutOfTen(parts[3]);
|
|
||||||
if (isFour) {
|
if (rules && rules.length > 0) {
|
||||||
note = "4/10";
|
for (const rule of rules) {
|
||||||
}
|
if (rule && rule.enabled) {
|
||||||
const isFive = isFiveOutOfTen(parts[3]);
|
try {
|
||||||
if (isFive) {
|
const re = new RegExp(rule.regex, 'i');
|
||||||
note = "5/10";
|
if (re.test(parts[3])) {
|
||||||
|
appliedNotes.push(rule.note);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// invalid regex - ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dedupedNotes = Array.from(new Set(appliedNotes)).filter(Boolean);
|
||||||
|
const note = dedupedNotes.join(';');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
identifier: parts[0],
|
identifier: parts[0],
|
||||||
type: parts[2],
|
type: parts[2],
|
||||||
|
Reference in New Issue
Block a user