Compare commits

..

10 Commits

Author SHA1 Message Date
82877b8870 Add lockfile 2025-09-21 14:59:28 +02:00
calli
470935fe9d update official instance url 2025-08-01 09:45:25 +03:00
calli
7ce238c9c7 increase batch from 5 to 50 2025-05-17 20:28:01 +03:00
calli
6523000e69 lets batch the requests for users with gazillion characters 2025-05-17 19:51:21 +03:00
calli
b993b28840 Add balance and storage alert counts to account card 2025-05-17 17:21:49 +03:00
calli
c036bc10e1 Sort storages 2025-05-17 17:21:37 +03:00
calli
b743193f46 update discord link 2025-05-17 17:10:58 +03:00
calli
02ebaf6e35 remove unused imports 2025-05-02 23:00:56 +03:00
calli
3a0843e54c make keys unique for the new tooltip 2025-05-02 22:00:40 +03:00
calli
e43bd91bef make active filters more visible 2025-05-02 21:54:22 +03:00
7 changed files with 1675 additions and 98 deletions

View File

@@ -2,9 +2,9 @@
Simple tool to track your PI planet extractors. Login with your characters and enjoy the PI! Simple tool to track your PI planet extractors. Login with your characters and enjoy the PI!
Any questions, feedback or suggestions are welcome at [EVE PI Discord](https://discord.gg/GPtw5kfuJu) Any questions, feedback or suggestions are welcome at [EVE PI Discord](https://discord.gg/bCdXzU8PHK)
## [Avanto hosted PI tool](https://pi.avanto.tk) ## [Hosted PI tool](https://pi.calli.fi)
![Screenshot of PI tool](https://github.com/calli-eve/eve-pi/blob/main/images/eve-pi.png) ![Screenshot of PI tool](https://github.com/calli-eve/eve-pi/blob/main/images/eve-pi.png)
![3D render of a planet](https://github.com/calli-eve/eve-pi/blob/main/images/3dplanet.png) ![3D render of a planet](https://github.com/calli-eve/eve-pi/blob/main/images/3dplanet.png)

1537
bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -376,6 +376,24 @@ export const AccountCard = ({ characters, isCollapsed: propIsCollapsed }: { char
> >
Extractors: {runningExtractors}/{totalExtractors} Extractors: {runningExtractors}/{totalExtractors}
</Typography> </Typography>
<Divider orientation="vertical" flexItem sx={{ height: 16, borderColor: theme.palette.divider }} />
<Typography
sx={{
fontSize: "0.8rem",
color: Object.values(planetDetails).some(d => d.alertState.hasLowStorage) ? theme.palette.error.main : theme.palette.text.secondary,
}}
>
Storage Alerts: {Object.values(planetDetails).filter(d => d.alertState.hasLowStorage).length}
</Typography>
<Divider orientation="vertical" flexItem sx={{ height: 16, borderColor: theme.palette.divider }} />
<Typography
sx={{
fontSize: "0.8rem",
color: Object.values(planetDetails).some(d => d.alertState.hasLargeExtractorDifference) ? theme.palette.error.main : theme.palette.text.secondary,
}}
>
Balance Alerts: {Object.values(planetDetails).filter(d => d.alertState.hasLargeExtractorDifference).length}
</Typography>
</Box> </Box>
</Box> </Box>
<IconButton <IconButton

View File

@@ -4,7 +4,7 @@ export const DiscordButton = () => {
<Box> <Box>
<Tooltip title="Come nerd out in discord about PI and this tool"> <Tooltip title="Come nerd out in discord about PI and this tool">
<Button <Button
href="https://discord.gg/GPtw5kfuJu" href="https://discord.gg/bCdXzU8PHK"
target="_blank" target="_blank"
style={{ width: "100%" }} style={{ width: "100%" }}
sx={{ color: "white", display: "block" }} sx={{ color: "white", display: "block" }}

View File

@@ -48,7 +48,7 @@ declare module "@mui/material/styles" {
} }
export const MainGrid = () => { export const MainGrid = () => {
const { characters, updateCharacter } = useContext(CharacterContext); const { characters } = useContext(CharacterContext);
const { compactMode, toggleCompactMode, alertMode, toggleAlertMode, planMode, togglePlanMode, extractionTimeMode, toggleExtractionTimeMode } = useContext(SessionContext); const { compactMode, toggleCompactMode, alertMode, toggleAlertMode, planMode, togglePlanMode, extractionTimeMode, toggleExtractionTimeMode } = useContext(SessionContext);
const [accountOrder, setAccountOrder] = useState<string[]>([]); const [accountOrder, setAccountOrder] = useState<string[]>([]);
const [allCollapsed, setAllCollapsed] = useState(false); const [allCollapsed, setAllCollapsed] = useState(false);
@@ -170,7 +170,7 @@ export const MainGrid = () => {
size="small" size="small"
style={{ style={{
backgroundColor: compactMode backgroundColor: compactMode
? "rgba(144, 202, 249, 0.08)" ? "rgba(144, 202, 249, 0.16)"
: "inherit", : "inherit",
}} }}
onClick={toggleCompactMode} onClick={toggleCompactMode}
@@ -183,7 +183,7 @@ export const MainGrid = () => {
size="small" size="small"
style={{ style={{
backgroundColor: alertMode backgroundColor: alertMode
? "rgba(144, 202, 249, 0.08)" ? "rgba(144, 202, 249, 0.16)"
: "inherit", : "inherit",
}} }}
onClick={toggleAlertMode} onClick={toggleAlertMode}
@@ -196,7 +196,7 @@ export const MainGrid = () => {
size="small" size="small"
style={{ style={{
backgroundColor: planMode backgroundColor: planMode
? "rgba(144, 202, 249, 0.08)" ? "rgba(144, 202, 249, 0.16)"
: "inherit", : "inherit",
}} }}
onClick={togglePlanMode} onClick={togglePlanMode}
@@ -209,7 +209,7 @@ export const MainGrid = () => {
size="small" size="small"
style={{ style={{
backgroundColor: extractionTimeMode backgroundColor: extractionTimeMode
? "rgba(144, 202, 249, 0.08)" ? "rgba(144, 202, 249, 0.16)"
: "inherit", : "inherit",
}} }}
onClick={toggleExtractionTimeMode} onClick={toggleExtractionTimeMode}

View File

@@ -1,6 +1,5 @@
import { ColorContext, SessionContext } from "@/app/context/Context"; import { ColorContext, SessionContext } from "@/app/context/Context";
import { PI_TYPES_MAP, STORAGE_IDS, STORAGE_CAPACITIES, PI_PRODUCT_VOLUMES, EVE_IMAGE_URL, PI_SCHEMATICS, LAUNCHPAD_IDS } from "@/const"; import { PI_TYPES_MAP, EVE_IMAGE_URL, LAUNCHPAD_IDS } from "@/const";
import { planetCalculations } from "@/planets";
import { AccessToken, PlanetWithInfo } from "@/types"; import { AccessToken, PlanetWithInfo } from "@/types";
import { PlanetCalculations, StorageInfo } from "@/types/planet"; import { PlanetCalculations, StorageInfo } from "@/types/planet";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
@@ -419,50 +418,55 @@ export const PlanetTableRow = ({
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{planetDetails.storageInfo.map((storage: StorageInfo) => { {planetDetails.storageInfo
const isLaunchpad = LAUNCHPAD_IDS.includes(storage.type_id); .map(storage => ({
const fillRate = storage.fillRate; ...storage,
const color = fillRate > 90 ? '#ff0000' : fillRate > 80 ? '#ffa500' : fillRate > 60 ? '#ffd700' : 'inherit'; isLaunchpad: LAUNCHPAD_IDS.includes(storage.type_id)
const contents = planet.info.pins.find(p => p.type_id === storage.type_id)?.contents || []; }))
.sort((a, b) => (b.isLaunchpad ? 1 : 0) - (a.isLaunchpad ? 1 : 0))
return ( .map((storage, idx) => {
<React.Fragment key={`storage-${character.character.characterId}-${planet.planet_id}-${storage.type}`}> const fillRate = storage.fillRate;
<TableRow> const color = fillRate > 90 ? '#ff0000' : fillRate > 80 ? '#ffa500' : fillRate > 60 ? '#ffd700' : 'inherit';
<TableCell>{isLaunchpad ? 'Launchpad' : 'Storage'}</TableCell> const contents = planet.info.pins.find(p => p.type_id === storage.type_id)?.contents || [];
<TableCell align="right">{storage.capacity.toFixed(1)} m³</TableCell>
<TableCell align="right">{storage.used.toFixed(1)} m³</TableCell> return (
<TableCell align="right" sx={{ color }}>{fillRate.toFixed(1)}%</TableCell> <React.Fragment key={`storage-${character.character.characterId}-${planet.planet_id}-${storage.type}-${idx}`}>
<TableCell align="right">
{storage.value > 0 ? (
storage.value >= 1000000000
? `${(storage.value / 1000000000).toFixed(2)} B`
: `${(storage.value / 1000000).toFixed(0)} M`
) : '-'} ISK
</TableCell>
</TableRow>
{contents.length > 0 && (
<TableRow> <TableRow>
<TableCell colSpan={5} sx={{ pt: 0, pb: 0 }}> <TableCell>{storage.isLaunchpad ? 'Launchpad' : 'Storage'}</TableCell>
<Table size="small"> <TableCell align="right">{storage.capacity.toFixed(1)} m³</TableCell>
<TableBody> <TableCell align="right">{storage.used.toFixed(1)} m³</TableCell>
{contents.map(content => ( <TableCell align="right" sx={{ color }}>{fillRate.toFixed(1)}%</TableCell>
<TableRow key={content.type_id}> <TableCell align="right">
<TableCell sx={{ pl: 2 }}> {storage.value > 0 ? (
{PI_TYPES_MAP[content.type_id]?.name} storage.value >= 1000000000
</TableCell> ? `${(storage.value / 1000000000).toFixed(2)} B`
<TableCell align="right" colSpan={4}> : `${(storage.value / 1000000).toFixed(0)} M`
{content.amount.toFixed(1)} units ) : '-'} ISK
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableCell> </TableCell>
</TableRow> </TableRow>
)} {contents.length > 0 && (
</React.Fragment> <TableRow>
); <TableCell colSpan={5} sx={{ pt: 0, pb: 0 }}>
})} <Table size="small">
<TableBody>
{contents.map((content, idy) => (
<TableRow key={`content-${character.character.characterId}-${planet.planet_id}-${storage.type}-${content.type_id}-${idx}-${idy}`}>
<TableCell sx={{ pl: 2 }}>
{PI_TYPES_MAP[content.type_id]?.name}
</TableCell>
<TableCell align="right" colSpan={4}>
{content.amount.toFixed(1)} units
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableCell>
</TableRow>
)}
</React.Fragment>
);
})}
</TableBody> </TableBody>
</Table> </Table>
</Box> </Box>
@@ -482,27 +486,32 @@ export const PlanetTableRow = ({
> >
<div style={{ display: "flex", flexDirection: "column" }}> <div style={{ display: "flex", flexDirection: "column" }}>
{planetDetails.storageInfo.length === 0 &&<Typography fontSize={theme.custom.smallText}>No storage</Typography>} {planetDetails.storageInfo.length === 0 &&<Typography fontSize={theme.custom.smallText}>No storage</Typography>}
{planetDetails.storageInfo.map((storage: StorageInfo) => { {planetDetails.storageInfo
const isLaunchpad = LAUNCHPAD_IDS.includes(storage.type_id); .map(storage => ({
const fillRate = storage.fillRate; ...storage,
const color = fillRate > 90 ? '#ff0000' : fillRate > 80 ? '#ffa500' : fillRate > 60 ? '#ffd700' : 'inherit'; isLaunchpad: LAUNCHPAD_IDS.includes(storage.type_id)
}))
return ( .sort((a, b) => (b.isLaunchpad ? 1 : 0) - (a.isLaunchpad ? 1 : 0))
<div key={`storage-${character.character.characterId}-${planet.planet_id}-${storage.type}`} style={{ display: "flex", alignItems: "center" }}> .map((storage, idx) => {
<Typography fontSize={theme.custom.smallText} style={{ marginRight: "5px" }}> const fillRate = storage.fillRate;
{isLaunchpad ? 'L' : 'S'} const color = fillRate > 90 ? '#ff0000' : fillRate > 80 ? '#ffa500' : fillRate > 60 ? '#ffd700' : 'inherit';
</Typography>
<Typography fontSize={theme.custom.smallText} style={{ color }}> return (
{fillRate.toFixed(1)}% <div key={`storage-${character.character.characterId}-${planet.planet_id}-${storage.type}-${idx}`} style={{ display: "flex", alignItems: "center" }}>
</Typography> <Typography fontSize={theme.custom.smallText} style={{ marginRight: "5px" }}>
{storage.value > 0 && ( {storage.isLaunchpad ? 'L' : 'S'}
<Typography fontSize={theme.custom.smallText} style={{ marginLeft: "5px" }}>
({Math.round(storage.value / 1000000)}M)
</Typography> </Typography>
)} <Typography fontSize={theme.custom.smallText} style={{ color }}>
</div> {fillRate.toFixed(1)}%
); </Typography>
})} {storage.value > 0 && (
<Typography fontSize={theme.custom.smallText} style={{ marginLeft: "5px" }}>
({Math.round(storage.value / 1000000)}M)
</Typography>
)}
</div>
);
})}
</div> </div>
</Tooltip> </Tooltip>
</TableCell> </TableCell>

View File

@@ -19,6 +19,21 @@ import { EvePraisalResult, fetchAllPrices } from "@/eve-praisal";
import { getPlanet, getPlanetUniverse, getPlanets } from "@/planets"; import { getPlanet, getPlanetUniverse, getPlanets } from "@/planets";
import { PlanetConfig } from "@/types"; import { PlanetConfig } from "@/types";
// Add batch processing utility
const processInBatches = async <T, R>(
items: T[],
batchSize: number,
processFn: (item: T) => Promise<R>
): Promise<R[]> => {
const results: R[] = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map(processFn));
results.push(...batchResults);
}
return results;
};
const Home = () => { const Home = () => {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const [characters, setCharacters] = useState<AccessToken[]>([]); const [characters, setCharacters] = useState<AccessToken[]>([]);
@@ -63,15 +78,13 @@ const Home = () => {
}; };
const refreshSession = async (characters: AccessToken[]) => { const refreshSession = async (characters: AccessToken[]) => {
return Promise.all( return processInBatches(characters, 50, async (c) => {
characters.map((c) => { try {
try { return await refreshToken(c);
return refreshToken(c); } catch {
} catch { return { ...c, needsLogin: true };
return { ...c, needsLogin: true }; }
} });
}),
);
}; };
const handleCallback = async ( const handleCallback = async (
@@ -107,24 +120,24 @@ const Home = () => {
const initializeCharacterPlanets = ( const initializeCharacterPlanets = (
characters: AccessToken[], characters: AccessToken[],
): Promise<AccessToken[]> => ): Promise<AccessToken[]> =>
Promise.all( processInBatches(characters, 50, async (c) => {
characters.map(async (c) => { if (c.needsLogin || c.character === undefined)
if (c.needsLogin || c.character === undefined) return { ...c, planets: [] };
return { ...c, planets: [] }; const planets = await getPlanets(c);
const planets = await getPlanets(c); const planetsWithInfo: PlanetWithInfo[] = await processInBatches(
const planetsWithInfo: PlanetWithInfo[] = await Promise.all( planets,
planets.map(async (p) => ({ 3,
...p, async (p) => ({
info: await getPlanet(c, p), ...p,
infoUniverse: await getPlanetUniverse(p), info: await getPlanet(c, p),
})), infoUniverse: await getPlanetUniverse(p),
); })
return { );
...c, return {
planets: planetsWithInfo, ...c,
}; planets: planetsWithInfo,
}), };
); });
const saveCharacters = (characters: AccessToken[]): AccessToken[] => { const saveCharacters = (characters: AccessToken[]): AccessToken[] => {
localStorage.setItem("characters", JSON.stringify(characters)); localStorage.setItem("characters", JSON.stringify(characters));