Function bodies 44 total
fetchAvailableShips function · javascript · L59-L93 (35 LOC)backend/rsi-store-worker/index.js
async function fetchAvailableShips() {
try {
const response = await fetch(RSI_GRAPHQL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Origin': 'https://robertsspaceindustries.com',
'Referer': 'https://robertsspaceindustries.com/en/pledge',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
},
body: SHIPS_QUERY,
});
if (!response.ok) {
console.error('RSI API error: ' + response.status);
return null;
}
const data = await response.json();
// Response is an array, first element has data
const result = Array.isArray(data) ? data[0] : data;
console.log('RSI response type:', typeof data, 'isArray:', Array.isArray(data));
console.log('Result keys:', result ? Object.keys(result) : 'null');
console.log('Ships count:', result?.data?.ships?.length || 0);
if (result?.data?.ships?.lcheckUpgradeAvailability function · javascript · L96-L117 (22 LOC)backend/rsi-store-worker/index.js
async function checkUpgradeAvailability(fromShipId) {
try {
const response = await fetch(RSI_GRAPHQL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
},
body: JSON.stringify({
query: UPGRADES_QUERY,
variables: { fromShipId },
}),
});
if (!response.ok) return null;
const data = await response.json();
return data?.data?.ships || [];
} catch (err) {
console.error('Failed to check upgrade:', err);
return null;
}
}sendAlertEmail function · javascript · L314-L384 (71 LOC)backend/rsi-store-worker/index.js
async function sendAlertEmail(env, toEmail, alerts) {
const RESEND_API_KEY = env.RESEND_API_KEY;
if (!RESEND_API_KEY) {
console.error('No RESEND_API_KEY configured');
return;
}
const alertList = alerts.map(a =>
`• ${a.fromShip} → ${a.toShip}: $${a.price} ${a.isWarbond ? '(WARBOND)' : '(Standard)'}`
).join('\n');
const htmlAlerts = alerts.map(a => `
<tr>
<td style="padding:8px;border-bottom:1px solid #1e3048;color:#c8d6e5">${a.fromShip}</td>
<td style="padding:8px;border-bottom:1px solid #1e3048;color:#c8d6e5">→</td>
<td style="padding:8px;border-bottom:1px solid #1e3048;color:#00c8ff;font-weight:bold">${a.toShip}</td>
<td style="padding:8px;border-bottom:1px solid #1e3048;color:#f59e0b;font-weight:bold">$${a.price}</td>
<td style="padding:8px;border-bottom:1px solid #1e3048;color:${a.isWarbond ? '#10b981' : '#607080'}">${a.isWarbond ? 'WARBOND' : 'Standard'}</td>
</tr>
`).join('');
try {
await fetch('https:collapsePhantomSteps function · javascript · L7-L42 (36 LOC)collapse_phantoms.cjs
function collapsePhantomSteps(steps) {
if (!steps || steps.length === 0) return steps;
const collapsed = [];
let i = 0;
while (i < steps.length) {
if (steps[i].phantom) {
// Start of a phantom run — find the end
let j = i;
let totalCost = 0;
let totalSaving = 0;
while (j < steps.length && steps[j].phantom) {
totalCost += steps[j].cost;
totalSaving += (steps[j].saving || 0);
j++;
}
// Collapse into one step
collapsed.push({
from: steps[i].from,
to: steps[j - 1].to,
fromMsrp: steps[i].fromMsrp,
toMsrp: steps[j - 1].toMsrp,
cost: totalCost,
pledgeCost: totalCost,
saving: totalSaving,
phantom: true,
standardCost: steps[j - 1].toMsrp - steps[i].fromMsrp,
collapsedCount: j - i,
});
i = j;
} else {
collapsed.push(steps[i]);
i++;
}
}
return collapsed;
}MinHeap class · javascript · L314-L321 (8 LOC)src/App.jsx
class MinHeap {
constructor() { this.h = []; }
push(v) { this.h.push(v); this._up(this.h.length - 1); }
pop() { const top = this.h[0], last = this.h.pop(); if (this.h.length > 0) { this.h[0] = last; this._down(0); } return top; }
get size() { return this.h.length; }
_up(i) { while (i > 0) { const p = (i - 1) >> 1; if (this.h[p][0] <= this.h[i][0]) break; [this.h[p], this.h[i]] = [this.h[i], this.h[p]]; i = p; } }
_down(i) { const n = this.h.length; while (true) { let s = i, l = 2*i+1, r = 2*i+2; if (l < n && this.h[l][0] < this.h[s][0]) s = l; if (r < n && this.h[r][0] < this.h[s][0]) s = r; if (s === i) break; [this.h[s], this.h[i]] = [this.h[i], this.h[s]]; i = s; } }
}findCheapestChains function · javascript · L324-L359 (36 LOC)src/App.jsx
function findCheapestChains(ccus, targetShip) {
const revEdges = {};
const allNodes = new Set();
ccus.forEach(c => {
allNodes.add(c.from); allNodes.add(c.to);
if (!revEdges[c.to]) revEdges[c.to] = [];
revEdges[c.to].push({to:c.from,cost:c.pledge,saving:c.saving,origFrom:c.from,origTo:c.to,fromMsrp:c.fromMsrp,toMsrp:c.toMsrp});
});
if (!allNodes.has(targetShip)) return [];
const dist={},prev={},prevEdge={},visited=new Set();
dist[targetShip]=0;
const pq=new MinHeap();
pq.push([0,targetShip]);
while(pq.size>0){
const[cost,node]=pq.pop();
if(visited.has(node))continue;
visited.add(node);
for(const edge of(revEdges[node]||[])){
const nc=cost+edge.cost;
if(dist[edge.to]===undefined||nc<dist[edge.to]){
dist[edge.to]=nc;prev[edge.to]=node;
prevEdge[edge.to]={from:edge.origFrom,to:edge.origTo,cost:edge.cost,saving:edge.saving,fromMsrp:edge.fromMsrp,toMsrp:edge.toMsrp};
pq.push([nc,edge.to]);
}
}
}
SearchableShipSelect function · javascript · L363-L412 (50 LOC)src/App.jsx
function SearchableShipSelect({ onSelect, exclude, placeholder, restrictTo }) {
const [query, setQuery] = useState("");
const [open, setOpen] = useState(false);
const excl = exclude || new Set();
const allShips = useMemo(() =>
Object.entries(SHIP_MSRP)
.map(([name, msrp]) => ({ name, msrp, mfr: (SHIP_DETAILS[name] || {}).mfr || "" }))
.sort((a, b) => b.msrp - a.msrp),
[]);
const filtered = useMemo(() => {
if (!query) return [];
const q = query.toLowerCase();
return allShips.filter(s =>
!excl.has(s.name) &&
(!restrictTo || restrictTo.has(s.name)) &&
(s.name.toLowerCase().includes(q) || s.mfr.toLowerCase().includes(q))
).slice(0, 15);
}, [query, allShips, excl]);
return (
<div className="ship-search-wrap">
<input
className="ship-search-input"
placeholder={placeholder || "Type to search ships..."}
value={query}
onChange={e => { setQuery(e.target.value); setOpen(true); }}
onRepobility (the analyzer behind this table) · https://repobility.com
ShipLink function · javascript · L418-L424 (7 LOC)src/App.jsx
function ShipLink({ name, style }) {
return (
<span className="ship-link" style={style} onClick={(e) => { e.stopPropagation(); _openShipModal(name); }}>
{name}
</span>
);
}ShipModal function · javascript · L426-L494 (69 LOC)src/App.jsx
function ShipModal({ name, onClose }) {
if (!name) return null;
const msrp = SHIP_MSRP[name] || 0;
const role = SHIP_ROLES[name] || "Unknown";
const d = SHIP_DETAILS[name];
const statusClass = d ? (d.status === "flight-ready" ? "status-flight" : d.status === "in-concept" ? "status-concept" : "status-prod") : "";
const wbSkus = d ? d.skus.filter(s => s.t.includes("Warbond")).sort((a, b) => b.d.localeCompare(a.d)) : [];
const bestWb = wbSkus.length > 0 ? wbSkus[0] : null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal" onClick={e => e.stopPropagation()}>
<div className="modal-header">
<div>
<div style={{ fontFamily: "'Orbitron',sans-serif", fontSize: 18, fontWeight: 700, color: "var(--text-bright)", marginBottom: 4 }}>{name}</div>
<ShipImage name={name} size={200} role={role} style={{ marginTop: 8, borderRadius: 6 }} />
{d && <div style={{ fontSize: 13, color: "var(--text-diFleetVisualization function · javascript · L499-L585 (87 LOC)src/App.jsx
function FleetVisualization() {
const [hovered, setHovered] = useState(null);
const ships = useMemo(() => {
const seen = new Set();
return SHIPS_RAW.filter(s => {
const k = s.name + s.pledgeId;
if (seen.has(k)) return false;
seen.add(k);
return true;
}).map(s => ({
...s,
realMsrp: getShipMsrp(s.name),
role: SHIP_ROLES[s.name] || "Utility",
})).sort((a, b) => b.realMsrp - a.realMsrp);
}, []);
const roles = useMemo(() => [...new Set(ships.map(s => s.role))].sort(), [ships]);
const getSize = (msrp) => {
if (msrp >= 900) return { w: 150, h: 72 };
if (msrp >= 500) return { w: 130, h: 60 };
if (msrp >= 300) return { w: 110, h: 50 };
if (msrp >= 150) return { w: 90, h: 42 };
if (msrp >= 50) return { w: 72, h: 34 };
return { w: 56, h: 28 };
};
const hShip = hovered !== null ? ships[hovered] : null;
return (
<div>
<div className="viz-legend">
{roles.map(r => (
<div FleetTab function · javascript · L587-L784 (198 LOC)src/App.jsx
function FleetTab({search}){
const[viewMode,setViewMode]=useState("visual");
const[allSort,setAllSort]=useState("msrp");
const[allDir,setAllDir]=useState(-1);
const allShips = useMemo(() => {
const seen = new Set();
let ships = SHIPS_RAW.filter(s => {
const k = s.name + s.pledgeId;
if (seen.has(k)) return false;
seen.add(k); return true;
}).map(s => ({
...s,
realMsrp: getShipMsrp(s.name),
role: SHIP_ROLES[s.name] || "Utility",
mfr: (SHIP_DETAILS[s.name] || {}).mfr || "",
size: (SHIP_DETAILS[s.name] || {}).size || "",
}));
if (search) {
const q = search.toLowerCase();
ships = ships.filter(s => s.name.toLowerCase().includes(q) || s.packageName.toLowerCase().includes(q) || s.role.toLowerCase().includes(q) || s.mfr.toLowerCase().includes(q));
}
ships.sort((a, b) => {
let av, bv;
if (allSort === "msrp") { av = a.realMsrp; bv = b.realMsrp; }
else if (allSort === "name") { av = aPackageGroup function · javascript · L786-L803 (18 LOC)src/App.jsx
function PackageGroup({name,ships,defaultOpen}){
const[open,setOpen]=useState(defaultOpen);
const count=ships.length;const totalMsrp=ships.reduce((s,sh)=>s+getShipMsrp(sh.name),0);
const isDom=name==="Package - Dominus Pack - Digital";
const displayName=name.replace("Package - ","").replace("Packs - ","");
return(<div className="pkg-group"><div className="pkg-header"onClick={()=>setOpen(!open)}>
<div style={{display:"flex",alignItems:"center"}}><span className={`chevron ${open?"open":""}`}>▶</span><span className="pkg-title">{displayName}</span></div>
<div className="pkg-meta"><span>{count} ship{count!==1?"s":""}</span>{isDom&&<span style={{color:"var(--green)"}}>Pack: $7,020</span>}<span>Value: {fmt(totalMsrp)}</span></div>
</div>
{open&&<div className="overflow-x"><table><thead><tr><th>Ship</th><th>MSRP</th><th>Ins.</th><th>Melt</th><th>Gift</th><th>Date</th></tr></thead><tbody>
{ships.sort((a,b)=>getShipMsrp(b.name)-getShipMsrp(a.name)).map((sh,i)=><tCCUTab function · javascript · L805-L837 (33 LOC)src/App.jsx
function CCUTab({search}){
const[sortKey,setSortKey]=useState("saving");const[sortDir,setSortDir]=useState(-1);
const handleSort=(key)=>{if(sortKey===key)setSortDir(d=>d*-1);else{setSortKey(key);setSortDir(-1)}};
const sorted=useMemo(()=>{let data=[...CCUS_RAW];if(search){const q=search.toLowerCase();data=data.filter(c=>c.from.toLowerCase().includes(q)||c.to.toLowerCase().includes(q))}data.sort((a,b)=>{let av=a[sortKey],bv=b[sortKey];if(typeof av==="string")return av.localeCompare(bv)*sortDir;return(av-bv)*sortDir});return data},[search,sortKey,sortDir]);
const totalUnits=sorted.reduce((s,c)=>s+c.count,0);const totalPledge=sorted.reduce((s,c)=>s+c.pledge*c.count,0);const totalSaving=sorted.reduce((s,c)=>s+c.saving*c.count,0);
const SortTh=({k,children,style})=><th className={sortKey===k?"sorted":""}onClick={()=>handleSort(k)}style={style}>{children}{sortKey===k&&<span className="sort-arrow">{sortDir===1?"▲":"▼"}</span>}</th>;
return(<div>
<div style={{display:"flex",gapbuildPhantomEdges function · javascript · L840-L858 (19 LOC)src/App.jsx
function buildPhantomEdges(phantomDiscount = 0) {
const edges = {};
const shipNames = Object.keys(SHIP_MSRP);
const discountMult = 1 - (phantomDiscount / 100);
for (const from of shipNames) {
const fromM = SHIP_MSRP[from] || 0;
if (fromM === 0) continue;
for (const to of shipNames) {
if (from === to) continue;
const toM = SHIP_MSRP[to] || 0;
if (toM <= fromM || toM === 0 || toM - fromM > 75) continue;
const standardCost = toM - fromM;
const estCost = Math.max(5, Math.round(standardCost * discountMult));
if (!edges[to]) edges[to] = [];
edges[to].push({ to: from, cost: estCost, pledgeCost: estCost, saving: standardCost - estCost, origFrom: from, origTo: to, fromMsrp: fromM, toMsrp: toM, phantom: true, standardCost });
}
}
return edges;
}findBestChainWithInventory function · javascript · L861-L918 (58 LOC)src/App.jsx
function findBestChainWithInventory(inventory, targetShip, validSources, usePhantoms = false, phantomEdges = null) {
const ccuLookup = {};
CCUS_RAW.forEach(c => { ccuLookup[`${c.from}→${c.to}`] = c; });
const revEdges = {};
// Add owned CCU edges — cost=0 for routing because these are already paid for (sunk cost)
for (const [key, remaining] of Object.entries(inventory)) {
if (remaining <= 0) continue;
const ccu = ccuLookup[key];
if (!ccu) continue;
if (!revEdges[ccu.to]) revEdges[ccu.to] = [];
revEdges[ccu.to].push({ to: ccu.from, cost: 0, pledgeCost: ccu.pledge, saving: ccu.saving, origFrom: ccu.from, origTo: ccu.to, fromMsrp: ccu.fromMsrp, toMsrp: ccu.toMsrp, phantom: false });
}
// Merge pre-built phantom edges (skip where owned exists)
if (usePhantoms && phantomEdges) {
for (const [to, edges] of Object.entries(phantomEdges)) {
for (const edge of edges) {
const key = `${edge.origFrom}→${edge.origTo}`;
if (inventory[key]About: code-quality intelligence by Repobility · https://repobility.com
computeFleetPlan function · javascript · L920-L935 (16 LOC)src/App.jsx
function computeFleetPlan(sources, targets, usePhantoms = false, phantomEdges = null) {
const inventory = {};
CCUS_RAW.forEach(c => { const key = `${c.from}→${c.to}`; inventory[key] = (inventory[key] || 0) + c.count; });
const assignments = [], unassigned = [];
const remainingSources = new Set(sources);
for (const target of targets) {
const chain = findBestChainWithInventory(inventory, target, remainingSources, usePhantoms, phantomEdges);
if (chain) {
assignments.push(chain);
remainingSources.delete(chain.source);
for (const step of chain.steps) { if (!step.phantom) { const key = `${step.from}→${step.to}`; inventory[key] = (inventory[key] || 0) - 1; } }
} else { unassigned.push(target); }
}
const totalCost = assignments.reduce((s, a) => s + a.totalCost, 0);
return { assignments, unassigned, remainingInventory: inventory, totalCost };
}computeOptimalFleetPlan function · javascript · L938-L961 (24 LOC)src/App.jsx
function computeOptimalFleetPlan(sources, targets, usePhantoms = false, phantomDiscount = 0) {
if (targets.length === 0 || sources.length === 0) {
return { assignments: [], unassigned: [...targets], remainingInventory: {}, totalCost: 0, permsTried: 0, note: "" };
}
const phantomEdges = usePhantoms ? buildPhantomEdges(phantomDiscount) : null;
// Smart orderings instead of full N! — covers key scenarios in O(N²) not O(N!)
const byMsrpDesc = [...targets].sort((a, b) => (SHIP_MSRP[b] || 0) - (SHIP_MSRP[a] || 0));
const byMsrpAsc = [...targets].sort((a, b) => (SHIP_MSRP[a] || 0) - (SHIP_MSRP[b] || 0));
const orderings = [byMsrpDesc, byMsrpAsc];
// Add single-target-first orderings for each target
for (const t of targets) {
const rest = targets.filter(x => x !== t).sort((a, b) => (SHIP_MSRP[b] || 0) - (SHIP_MSRP[a] || 0));
orderings.push([t, ...rest]);
}
let bestPlan = null, bestScore = -Infinity;
for (const ordering of orderings) {
const plan = computcollapsePhantomSteps function · javascript · L965-L1000 (36 LOC)src/App.jsx
function collapsePhantomSteps(steps) {
if (!steps || steps.length === 0) return steps;
const collapsed = [];
let i = 0;
while (i < steps.length) {
if (steps[i].phantom) {
// Start of a phantom run — find the end
let j = i;
let totalCost = 0;
let totalSaving = 0;
while (j < steps.length && steps[j].phantom) {
totalCost += steps[j].cost;
totalSaving += (steps[j].saving || 0);
j++;
}
// Collapse into one step
collapsed.push({
from: steps[i].from,
to: steps[j - 1].to,
fromMsrp: steps[i].fromMsrp,
toMsrp: steps[j - 1].toMsrp,
cost: totalCost,
pledgeCost: totalCost,
saving: totalSaving,
phantom: true,
standardCost: steps[j - 1].toMsrp - steps[i].fromMsrp,
collapsedCount: j - i,
});
i = j;
} else {
collapsed.push(steps[i]);
i++;
}
}
return collapsed;
}OptimizerTab function · javascript · L1002-L1292 (291 LOC)src/App.jsx
function OptimizerTab({optMode: mode, setOptMode: setMode, optTarget: target, setOptTarget: setTarget, planTargets, setPlanTargets, planSources, setPlanSources, showPhantoms, setShowPhantoms, phantomDiscount, setPhantomDiscount}){
const [expandedIdx, setExpandedIdx] = useState(null);
const [expandedPlan, setExpandedPlan] = useState(null);
const targets = useMemo(() => [...new Set(CCUS_RAW.map(c => c.to))].sort(), []);
const chains = useMemo(() => findCheapestChains(CCUS_RAW, target), [target]);
const ownedShips = useMemo(() => new Set(SHIPS_RAW.map(s => s.name)), []);
const ranked = useMemo(() => {
const owned = chains.filter(c => ownedShips.has(c.source));
const notOwned = chains.filter(c => !ownedShips.has(c.source));
return [...owned, ...notOwned].slice(0, 25);
}, [chains, ownedShips]);
const removePlanTarget = (t) => setPlanTargets(prev => prev.filter(x => x !== t));
const toggleSource = (name) => setPlanSources(prev => {
const next = new Set(preSaleEvaluatorTab function · javascript · L1295-L1538 (244 LOC)src/App.jsx
function SaleEvaluatorTab() {
const allShipNames = useMemo(() => {
const names = new Set();
Object.keys(SHIP_MSRP).forEach(n => names.add(n));
CCUS_RAW.forEach(c => { names.add(c.from); names.add(c.to); });
return [...names].sort();
}, []);
const [fromShip, setFromShip] = useState("Starlancer TAC");
const [toShip, setToShip] = useState("Galaxy");
const [price, setPrice] = useState("5");
const [isWarbond, setIsWarbond] = useState(true);
const ownedShips = useMemo(() => new Set(SHIPS_RAW.map(s => s.name)), []);
// High-value targets: ships that are final destinations in existing chains
const targetShips = useMemo(() => {
const toSet = new Set(CCUS_RAW.map(c => c.to));
const fromSet = new Set(CCUS_RAW.map(c => c.from));
// "Terminal" targets: things that are CCU destinations but rarely sources, or high-value
const terminals = [...toSet].filter(t => (SHIP_MSRP[t] || 0) >= 350);
return [...new Set(terminals)].sort((a, b) => (SHIP_MSRPApp function · javascript · L1540-L1624 (85 LOC)src/App.jsx
export default function App(){
// ─── Dynamic data state (persisted to localStorage) ─────────────────────────
const [shipsData, setShipsData] = useState(SHIPS_RAW);
const [ccusData, setCcusData] = useState(CCUS_RAW);
const hasData = shipsData.length > 0 || ccusData.length > 0;
const handleImportShips = (ships) => {
SHIPS_RAW = ships;
setShipsData(ships);
localStorage.setItem('drydock_ships', JSON.stringify(ships));
};
const handleImportCCUs = (ccus) => {
CCUS_RAW = ccus;
setCcusData(ccus);
localStorage.setItem('drydock_ccus', JSON.stringify(ccus));
};
const clearData = () => {
SHIPS_RAW = []; CCUS_RAW = [];
setShipsData([]); setCcusData([]);
localStorage.removeItem('drydock_ships');
localStorage.removeItem('drydock_ccus');
};
const[tab,setTab]=useState(SHIPS_RAW.length > 0 ? "fleet" : "import");const[search,setSearch]=useState("");
const[selectedShip,setSelectedShip]=useState(null);
setShipModalOpener(setSelectedShipapiCall function · javascript · L28-L39 (12 LOC)src/components/CCUWatchlist.jsx
async function apiCall(path, options = {}) {
try {
const response = await fetch(`${API_BASE}${path}`, {
...options,
headers: { 'Content-Type': 'application/json', ...options.headers },
});
return await response.json();
} catch (err) {
console.error('API error:', err);
return { error: err.message };
}
}CCUWatchlist function · javascript · L42-L328 (287 LOC)src/components/CCUWatchlist.jsx
export default function CCUWatchlist({ shipMsrp, phantomSteps }) {
const SHIP_MSRP = shipMsrp || {};
const [email, setEmail] = useState(() => localStorage.getItem('scfp_email') || '');
const [emailSaved, setEmailSaved] = useState(!!localStorage.getItem('scfp_email'));
const [watchlist, setWatchlist] = useState([]);
const [alerts, setAlerts] = useState([]);
const [storeData, setStoreData] = useState(null);
const [lastUpdated, setLastUpdated] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [addFrom, setAddFrom] = useState('');
const [addTo, setAddTo] = useState('');
const [addMaxPrice, setAddMaxPrice] = useState('');
const [backendConnected, setBackendConnected] = useState(false);
// ─── Check backend connectivity ──────────────────────────────────────
useEffect(() => {
apiCall('/api/store/ccus').then(result => {
if (!result.error) {
setBackendConnected(true);
setSProvenance: Repobility (https://repobility.com) — every score reproducible from /scan/
parseCSV function · javascript · L18-L49 (32 LOC)src/components/CSVImport.jsx
function parseCSV(text) {
const lines = text.trim().split('\n');
if (lines.length < 2) return { headers: [], rows: [] };
// Handle both comma and tab delimiters
const delimiter = lines[0].includes('\t') ? '\t' : ',';
const headers = lines[0].split(delimiter).map(h => h.trim().replace(/^"|"$/g, ''));
const rows = [];
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
// Handle quoted fields with commas inside
const values = [];
let current = '';
let inQuotes = false;
for (const char of line) {
if (char === '"') { inQuotes = !inQuotes; continue; }
if (char === delimiter && !inQuotes) { values.push(current.trim()); current = ''; continue; }
current += char;
}
values.push(current.trim());
const row = {};
headers.forEach((h, idx) => { row[h] = values[idx] || ''; });
rows.push(row);
}
return { headers, rows };
}detectAndNormalize function · javascript · L52-L91 (40 LOC)src/components/CSVImport.jsx
function detectAndNormalize(headers, rows) {
const h = headers.map(x => x.toLowerCase());
// Ships CSV: look for "name" + ("package" or "pledge" or "insurance")
if (h.includes('name') && (h.includes('package') || h.includes('pledge') || h.includes('insurance') || h.includes('packagename'))) {
const ships = rows.map(r => {
const name = r[headers[h.indexOf('name')]] || r['Name'] || r['name'] || '';
const packageName = r['Package'] || r['package'] || r['packageName'] || r['PackageName'] || r['pledge'] || '';
const insurance = r['Insurance'] || r['insurance'] || r['ins'] || '';
const customName = r['Custom Name'] || r['customName'] || r['custom_name'] || '';
const isMeltable = ['true', 'yes', '1', 'y'].includes((r['Meltable'] || r['meltable'] || r['isMeltable'] || '').toLowerCase());
const isGiftable = ['true', 'yes', '1', 'y'].includes((r['Giftable'] || r['giftable'] || r['isGiftable'] || '').toLowerCase());
const pledgeId = r['PledgeCSVImport function · javascript · L94-L232 (139 LOC)src/components/CSVImport.jsx
export default function CSVImport({ onImportShips, onImportCCUs }) {
const [dragOver, setDragOver] = useState(false);
const [results, setResults] = useState([]);
const [error, setError] = useState('');
const processFile = useCallback((file) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const text = e.target.result;
const { headers, rows } = parseCSV(text);
const result = detectAndNormalize(headers, rows);
if (result.type === 'unknown') {
setError(`Could not detect CSV format for "${file.name}". Expected columns: name+package (ships) or from+to (CCUs).`);
return;
}
setResults(prev => [...prev, { fileName: file.name, ...result }]);
setError('');
if (result.type === 'ships' && onImportShips) {
onImportShips(result.data);
} else if (result.type === 'ccus' && onImportCCUs) {
onImportCCUs(result.data);
getShipDims function · javascript · L40-L44 (5 LOC)src/components/FleetViewer3D.jsx
function getShipDims(name) {
const d = SHIP_DIMENSIONS[name];
if (d) return { length: d[0], beam: d[1], height: d[2] };
return { length: 20, beam: 12, height: 5 }; // fallback
}getShipColor function · javascript · L46-L48 (3 LOC)src/components/FleetViewer3D.jsx
function getShipColor(role) {
return ROLE_SHIP_COLORS[role] || 0x607080;
}snapToGrid function · javascript · L50-L55 (6 LOC)src/components/FleetViewer3D.jsx
function snapToGrid(x, z) {
return {
x: Math.round(x / CELL_SIZE) * CELL_SIZE,
z: Math.round(z / CELL_SIZE) * CELL_SIZE,
};
}createShipMesh function · javascript · L58-L105 (48 LOC)src/components/FleetViewer3D.jsx
function createShipMesh(ship, role, position) {
const dims = getShipDims(ship.name);
const color = getShipColor(role);
const group = new THREE.Group();
group.userData = { shipName: ship.name, role, dims, ship };
// Main hull — elongated box
const hullGeo = new THREE.BoxGeometry(dims.beam, dims.height, dims.length);
const hullMat = new THREE.MeshPhongMaterial({
color,
transparent: true,
opacity: 0.75,
flatShading: true,
});
const hull = new THREE.Mesh(hullGeo, hullMat);
hull.position.y = dims.height / 2 + 0.5;
hull.castShadow = true;
hull.receiveShadow = true;
group.add(hull);
// Wing accent — wider, thinner box for fighters/medium ships
if (dims.length < 100 && dims.beam > 10) {
const wingGeo = new THREE.BoxGeometry(dims.beam * 1.3, dims.height * 0.3, dims.length * 0.5);
const wingMat = new THREE.MeshPhongMaterial({ color, transparent: true, opacity: 0.4 });
const wing = new THREE.Mesh(wingGeo, wingMat);
wing.position.y = createLabel function · javascript · L108-L139 (32 LOC)src/components/FleetViewer3D.jsx
function createLabel(text, position, dims) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 64;
ctx.fillStyle = 'rgba(6, 10, 16, 0.8)';
ctx.roundRect(0, 0, 256, 64, 6);
ctx.fill();
ctx.strokeStyle = 'rgba(0, 200, 255, 0.4)';
ctx.lineWidth = 1;
ctx.roundRect(0, 0, 256, 64, 6);
ctx.stroke();
ctx.font = LABEL_FONT;
ctx.fillStyle = '#e8f0f8';
ctx.textAlign = 'center';
ctx.fillText(text, 128, 28);
ctx.font = '500 10px Rajdhani, sans-serif';
ctx.fillStyle = '#607080';
ctx.fillText(`${dims.length}m × ${dims.beam}m`, 128, 48);
const texture = new THREE.CanvasTexture(canvas);
const spriteMat = new THREE.SpriteMaterial({ map: texture, transparent: true });
const sprite = new THREE.Sprite(spriteMat);
sprite.position.set(position.x, dims.height + 8, position.z);
sprite.scale.set(24, 6, 1);
sprite.userData = { isLabel: true };
return sprite;
}Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
FleetViewer3D function · javascript · L142-L532 (391 LOC)src/components/FleetViewer3D.jsx
export default function FleetViewer3D({ ships, shipRoles, shipMsrp }) {
const containerRef = useRef(null);
const rendererRef = useRef(null);
const sceneRef = useRef(null);
const cameraRef = useRef(null);
const shipsInScene = useRef({});
const labelsInScene = useRef({});
const animFrameRef = useRef(null);
const mouseRef = useRef({ isDown: false, button: -1, x: 0, y: 0, lastX: 0, lastY: 0 });
const cameraOrbit = useRef({ theta: 0.5, phi: 0.8, radius: 400, target: new THREE.Vector3(0, 0, 0) });
const [selectedShip, setSelectedShip] = useState(null);
const [showLabels, setShowLabels] = useState(true);
const [addShipOpen, setAddShipOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [placedShips, setPlacedShips] = useState(() => {
// Auto-place fleet ships in a grid
const unique = [...new Map(ships.map(s => [s.name, s])).values()];
const sorted = unique.sort((a, b) => {
const da = getShipDims(a.name), db = getShanimate function · javascript · L251-L262 (12 LOC)src/components/FleetViewer3D.jsx
function animate() {
animFrameRef.current = requestAnimationFrame(animate);
// Update camera from orbit params
const o = cameraOrbit.current;
camera.position.x = o.target.x + o.radius * Math.sin(o.phi) * Math.cos(o.theta);
camera.position.y = o.target.y + o.radius * Math.cos(o.phi);
camera.position.z = o.target.z + o.radius * Math.sin(o.phi) * Math.sin(o.theta);
camera.lookAt(o.target);
renderer.render(scene, camera);
}TierBadge function · javascript · L15-L24 (10 LOC)src/components/GameLoopGuide.jsx
function TierBadge({ tier }) {
const c = TIER_COLORS[tier] || TIER_COLORS.D;
return (
<span style={{
display: 'inline-block', width: 22, height: 22, lineHeight: '22px',
borderRadius: 4, textAlign: 'center', fontFamily: "'Orbitron',sans-serif",
fontSize: 11, fontWeight: 700, background: c.bg, color: c.text,
}}>{tier}</span>
);
}DifficultyBar function · javascript · L26-L42 (17 LOC)src/components/GameLoopGuide.jsx
function DifficultyBar({ level }) {
const d = DIFFICULTY_LABELS[level] || DIFFICULTY_LABELS[3];
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<div style={{ display: 'flex', gap: 2 }}>
{[1, 2, 3, 4, 5].map(i => (
<div key={i} style={{
width: 16, height: 8, borderRadius: 2,
background: i <= level ? d.color : 'var(--bg-card)',
border: `1px solid ${i <= level ? d.color : 'var(--border)'}`,
}} />
))}
</div>
<span style={{ fontSize: 12, color: d.color, fontWeight: 600 }}>{d.label}</span>
</div>
);
}GameLoopGuide function · javascript · L44-L196 (153 LOC)src/components/GameLoopGuide.jsx
export default function GameLoopGuide({ ownedShips }) {
const [expandedLoop, setExpandedLoop] = useState(null);
const [filter, setFilter] = useState('all'); // 'all' | 'covered' | 'uncovered'
const owned = ownedShips || new Set();
// Analyze fleet coverage per loop
const loopAnalysis = useMemo(() => {
return GAME_LOOPS.map(loop => {
const allShips = Object.entries(loop.tiers).flatMap(([tier, ships]) =>
ships.map(s => ({ name: s, tier }))
);
const ownedInLoop = allShips.filter(s => owned.has(s.name));
const bestOwned = ownedInLoop.length > 0
? ownedInLoop.sort((a, b) => 'SABCD'.indexOf(a.tier) - 'SABCD'.indexOf(b.tier))[0]
: null;
return {
...loop,
allShips,
ownedInLoop,
bestOwned,
coverage: ownedInLoop.length > 0 ? 'covered' : 'uncovered',
};
});
}, [owned]);
const filtered = filter === 'all' ? loopAnalysis
: loopAnalysis.filter(l => l.coveraSaleDashboard function · javascript · L31-L232 (202 LOC)src/components/SaleDashboard.jsx
export default function SaleDashboard({ chainData, shipMsrp }) {
const SHIP_MSRP = shipMsrp || {};
const [saleEntries, setSaleEntries] = useState([]);
const [newFrom, setNewFrom] = useState('');
const [newTo, setNewTo] = useState('');
const [newPrice, setNewPrice] = useState('');
const [newWarbond, setNewWarbond] = useState(true);
const [eventName, setEventName] = useState('Current Sale');
const addEntry = () => {
if (!newFrom || !newTo || !newPrice) return;
const fromMsrp = SHIP_MSRP[newFrom] || 0;
const toMsrp = SHIP_MSRP[newTo] || 0;
const standardCost = toMsrp - fromMsrp;
const price = parseFloat(newPrice);
const saving = standardCost - price;
const savingPct = standardCost > 0 ? Math.round((saving / standardCost) * 100) : 0;
// Determine verdict based on chain impact
let verdict = 'SKIP';
let chainImpact = null;
if (chainData && chainData.assignments) {
for (const chain of chainData.assignments) {
ShipBrowser function · javascript · L23-L271 (249 LOC)src/components/ShipBrowser.jsx
export default function ShipBrowser({ shipMsrp, shipRoles, shipDetails }) {
const MSRP = shipMsrp || {};
const ROLES = shipRoles || {};
const DETAILS = shipDetails || {};
const [search, setSearch] = useState('');
const [sortBy, setSortBy] = useState('msrp');
const [sortDir, setSortDir] = useState(-1);
const [roleFilter, setRoleFilter] = useState('all');
const [mfrFilter, setMfrFilter] = useState('all');
const [statusFilter, setStatusFilter] = useState('all');
const [availFilter, setAvailFilter] = useState('all');
const [viewMode, setViewMode] = useState('grid');
const [wikiData, setWikiData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const cached = sessionStorage.getItem('drydock_wiki_ships');
if (cached) { try { setWikiData(JSON.parse(cached)); setLoading(false); return; } catch(e) {} }
fetch(WIKI_API).then(r => r.json()).then(d => {
const ships = d.data || d;
setWikiData(ships); setLoading(fatoSlug function · javascript · L13-L19 (7 LOC)src/components/ShipImage.jsx
function toSlug(name) {
return name
.toLowerCase()
.replace(/[']/g, '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}Repobility (the analyzer behind this table) · https://repobility.com
loadCache function · javascript · L98-L106 (9 LOC)src/components/ShipImage.jsx
function loadCache() {
try {
const stored = localStorage.getItem(CACHE_KEY);
if (stored) {
const parsed = JSON.parse(stored);
Object.assign(imageCache, parsed);
}
} catch (e) {}
}saveCache function · javascript · L109-L113 (5 LOC)src/components/ShipImage.jsx
function saveCache() {
try {
localStorage.setItem(CACHE_KEY, JSON.stringify(imageCache));
} catch (e) {}
}fetchShipImage function · javascript · L116-L144 (29 LOC)src/components/ShipImage.jsx
async function fetchShipImage(name) {
if (imageCache[name]) return imageCache[name];
if (imageCache[name] === null) return null; // Already tried, no image
const slug = SLUG_OVERRIDES[name] || toSlug(name);
try {
const response = await fetch(`https://api.fleetyards.net/v1/models/${slug}`);
if (!response.ok) {
imageCache[name] = null;
saveCache();
return null;
}
const data = await response.json();
const imageUrl = data.media?.storeImage?.mediumUrl || data.media?.storeImage?.url || data.media?.angledView?.mediumUrl
|| data.media?.angledView?.smallUrl
|| data.media?.angledView?.url
|| data.media?.storeImage?.mediumUrl
|| data.storeImage
|| null;
imageCache[name] = imageUrl;
saveCache();
return imageUrl;
} catch (err) {
imageCache[name] = null;
saveCache();
return null;
}
}ShipImage function · javascript · L154-L222 (69 LOC)src/components/ShipImage.jsx
export default function ShipImage({ name, size = 80, role, style }) {
const [imageUrl, setImageUrl] = useState(imageCache[name] || null);
const [loaded, setLoaded] = useState(false);
const [failed, setFailed] = useState(imageCache[name] === null);
useEffect(() => {
if (imageCache[name]) {
setImageUrl(imageCache[name]);
return;
}
if (imageCache[name] === null) {
setFailed(true);
return;
}
let cancelled = false;
fetchShipImage(name).then(url => {
if (cancelled) return;
if (url) setImageUrl(url);
else setFailed(true);
});
return () => { cancelled = true; };
}, [name]);
const color = ROLE_COLORS[role] || "#607080";
const initials = name.split(' ').map(w => w[0]).join('').slice(0, 3);
// Fallback placeholder
if (failed || !imageUrl) {
return (
<div style={{
width: size, height: size * 0.6, borderRadius: 4,
background: `linear-gradient(135deg, ${color}33, ${color}11)`,
prefetchShipImages function · javascript · L225-L236 (12 LOC)src/components/ShipImage.jsx
export async function prefetchShipImages(shipNames) {
const uncached = shipNames.filter(n => !(n in imageCache));
// Fetch in batches of 5 to avoid hammering the API
for (let i = 0; i < uncached.length; i += 5) {
const batch = uncached.slice(i, i + 5);
await Promise.all(batch.map(fetchShipImage));
// Small delay between batches
if (i + 5 < uncached.length) {
await new Promise(r => setTimeout(r, 200));
}
}
}