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,16 +418,21 @@ 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 => ({
...storage,
isLaunchpad: LAUNCHPAD_IDS.includes(storage.type_id)
}))
.sort((a, b) => (b.isLaunchpad ? 1 : 0) - (a.isLaunchpad ? 1 : 0))
.map((storage, idx) => {
const fillRate = storage.fillRate; const fillRate = storage.fillRate;
const color = fillRate > 90 ? '#ff0000' : fillRate > 80 ? '#ffa500' : fillRate > 60 ? '#ffd700' : 'inherit'; const color = fillRate > 90 ? '#ff0000' : fillRate > 80 ? '#ffa500' : fillRate > 60 ? '#ffd700' : 'inherit';
const contents = planet.info.pins.find(p => p.type_id === storage.type_id)?.contents || []; const contents = planet.info.pins.find(p => p.type_id === storage.type_id)?.contents || [];
return ( return (
<React.Fragment key={`storage-${character.character.characterId}-${planet.planet_id}-${storage.type}`}> <React.Fragment key={`storage-${character.character.characterId}-${planet.planet_id}-${storage.type}-${idx}`}>
<TableRow> <TableRow>
<TableCell>{isLaunchpad ? 'Launchpad' : 'Storage'}</TableCell> <TableCell>{storage.isLaunchpad ? 'Launchpad' : 'Storage'}</TableCell>
<TableCell align="right">{storage.capacity.toFixed(1)} m³</TableCell> <TableCell align="right">{storage.capacity.toFixed(1)} m³</TableCell>
<TableCell align="right">{storage.used.toFixed(1)} m³</TableCell> <TableCell align="right">{storage.used.toFixed(1)} m³</TableCell>
<TableCell align="right" sx={{ color }}>{fillRate.toFixed(1)}%</TableCell> <TableCell align="right" sx={{ color }}>{fillRate.toFixed(1)}%</TableCell>
@@ -445,8 +449,8 @@ export const PlanetTableRow = ({
<TableCell colSpan={5} sx={{ pt: 0, pb: 0 }}> <TableCell colSpan={5} sx={{ pt: 0, pb: 0 }}>
<Table size="small"> <Table size="small">
<TableBody> <TableBody>
{contents.map(content => ( {contents.map((content, idy) => (
<TableRow key={content.type_id}> <TableRow key={`content-${character.character.characterId}-${planet.planet_id}-${storage.type}-${content.type_id}-${idx}-${idy}`}>
<TableCell sx={{ pl: 2 }}> <TableCell sx={{ pl: 2 }}>
{PI_TYPES_MAP[content.type_id]?.name} {PI_TYPES_MAP[content.type_id]?.name}
</TableCell> </TableCell>
@@ -482,15 +486,20 @@ 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 => ({
...storage,
isLaunchpad: LAUNCHPAD_IDS.includes(storage.type_id)
}))
.sort((a, b) => (b.isLaunchpad ? 1 : 0) - (a.isLaunchpad ? 1 : 0))
.map((storage, idx) => {
const fillRate = storage.fillRate; const fillRate = storage.fillRate;
const color = fillRate > 90 ? '#ff0000' : fillRate > 80 ? '#ffa500' : fillRate > 60 ? '#ffd700' : 'inherit'; const color = fillRate > 90 ? '#ff0000' : fillRate > 80 ? '#ffa500' : fillRate > 60 ? '#ffd700' : 'inherit';
return ( return (
<div key={`storage-${character.character.characterId}-${planet.planet_id}-${storage.type}`} style={{ display: "flex", alignItems: "center" }}> <div key={`storage-${character.character.characterId}-${planet.planet_id}-${storage.type}-${idx}`} style={{ display: "flex", alignItems: "center" }}>
<Typography fontSize={theme.custom.smallText} style={{ marginRight: "5px" }}> <Typography fontSize={theme.custom.smallText} style={{ marginRight: "5px" }}>
{isLaunchpad ? 'L' : 'S'} {storage.isLaunchpad ? 'L' : 'S'}
</Typography> </Typography>
<Typography fontSize={theme.custom.smallText} style={{ color }}> <Typography fontSize={theme.custom.smallText} style={{ color }}>
{fillRate.toFixed(1)}% {fillRate.toFixed(1)}%

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 refreshToken(c); return await 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 Promise.all( const planetsWithInfo: PlanetWithInfo[] = await processInBatches(
planets.map(async (p) => ({ planets,
3,
async (p) => ({
...p, ...p,
info: await getPlanet(c, p), info: await getPlanet(c, p),
infoUniverse: await getPlanetUniverse(p), infoUniverse: await getPlanetUniverse(p),
})), })
); );
return { return {
...c, ...c,
planets: planetsWithInfo, planets: planetsWithInfo,
}; };
}), });
);
const saveCharacters = (characters: AccessToken[]): AccessToken[] => { const saveCharacters = (characters: AccessToken[]): AccessToken[] => {
localStorage.setItem("characters", JSON.stringify(characters)); localStorage.setItem("characters", JSON.stringify(characters));