6 Commits

14 changed files with 447 additions and 198 deletions

View File

@@ -1,5 +1,5 @@
import { AccessToken } from "@/types";
import { Box, Stack, Typography, useTheme, Paper, IconButton } from "@mui/material";
import { Box, Stack, Typography, useTheme, Paper, IconButton, Divider } from "@mui/material";
import { CharacterRow } from "../Characters/CharacterRow";
import { PlanetaryInteractionRow } from "../PlanetaryInteraction/PlanetaryInteractionRow";
import { SessionContext } from "@/app/context/Context";
@@ -14,17 +14,36 @@ import { STORAGE_IDS } from "@/const";
interface AccountTotals {
monthlyEstimate: number;
storageValue: number;
planetCount: number;
characterCount: number;
runningExtractors: number;
totalExtractors: number;
}
const calculateAccountTotals = (characters: AccessToken[], piPrices: EvePraisalResult | undefined): AccountTotals => {
let totalMonthlyEstimate = 0;
let totalStorageValue = 0;
let totalPlanetCount = 0;
let totalCharacterCount = characters.length;
let runningExtractors = 0;
let totalExtractors = 0;
characters.forEach((character) => {
totalPlanetCount += character.planets.length;
character.planets.forEach((planet) => {
const { localExports } = planetCalculations(planet);
const { localExports, extractors } = planetCalculations(planet);
const planetConfig = character.planetConfig.find(p => p.planetId === planet.planet_id);
// Count running and total extractors
if (!planetConfig?.excludeFromTotals) {
extractors.forEach(extractor => {
totalExtractors++;
if (extractor.expiry_time && new Date(extractor.expiry_time) > new Date()) {
runningExtractors++;
}
});
}
// Calculate monthly estimate
if (!planetConfig?.excludeFromTotals) {
localExports.forEach((exportItem) => {
@@ -56,7 +75,11 @@ const calculateAccountTotals = (characters: AccessToken[], piPrices: EvePraisalR
return {
monthlyEstimate: totalMonthlyEstimate,
storageValue: totalStorageValue
storageValue: totalStorageValue,
planetCount: totalPlanetCount,
characterCount: totalCharacterCount,
runningExtractors,
totalExtractors
};
};
@@ -64,7 +87,7 @@ export const AccountCard = ({ characters, isCollapsed: propIsCollapsed }: { char
const theme = useTheme();
const [localIsCollapsed, setLocalIsCollapsed] = useState(false);
const { planMode, piPrices } = useContext(SessionContext);
const { monthlyEstimate, storageValue } = calculateAccountTotals(characters, piPrices);
const { monthlyEstimate, storageValue, planetCount, characterCount, runningExtractors, totalExtractors } = calculateAccountTotals(characters, piPrices);
// Update local collapse state when prop changes
useEffect(() => {
@@ -102,7 +125,12 @@ export const AccountCard = ({ characters, isCollapsed: propIsCollapsed }: { char
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
cursor: 'pointer',
'&:hover': {
backgroundColor: theme.palette.action.hover,
},
}}
onClick={() => setLocalIsCollapsed(!localIsCollapsed)}
>
<Box>
<Typography
@@ -116,30 +144,65 @@ export const AccountCard = ({ characters, isCollapsed: propIsCollapsed }: { char
? `Account: ${characters[0].account}`
: "No account name"}
</Typography>
<Typography
sx={{
fontSize: "0.8rem",
color: theme.palette.text.secondary,
}}
>
Monthly Estimate: {monthlyEstimate >= 1000
? `${(monthlyEstimate / 1000).toFixed(2)} B`
: `${monthlyEstimate.toFixed(2)} M`} ISK
</Typography>
<Typography
sx={{
fontSize: "0.8rem",
color: theme.palette.text.secondary,
}}
>
Storage Value: {storageValue >= 1000
? `${(storageValue / 1000).toFixed(2)} B`
: `${storageValue.toFixed(2)} M`} ISK
</Typography>
<Box sx={{
display: 'flex',
gap: 2,
flexWrap: 'wrap',
mt: 1,
alignItems: 'center'
}}>
<Typography
sx={{
fontSize: "0.8rem",
color: theme.palette.text.secondary,
}}
>
Monthly: {monthlyEstimate >= 1000
? `${(monthlyEstimate / 1000).toFixed(2)} B`
: `${monthlyEstimate.toFixed(2)} M`} ISK
</Typography>
<Divider orientation="vertical" flexItem sx={{ height: 16, borderColor: theme.palette.divider }} />
<Typography
sx={{
fontSize: "0.8rem",
color: theme.palette.text.secondary,
}}
>
Storage: {storageValue >= 1000
? `${(storageValue / 1000).toFixed(2)} B`
: `${storageValue.toFixed(2)} M`} ISK
</Typography>
<Divider orientation="vertical" flexItem sx={{ height: 16, borderColor: theme.palette.divider }} />
<Typography
sx={{
fontSize: "0.8rem",
color: theme.palette.text.secondary,
}}
>
Planets: {planetCount}
</Typography>
<Divider orientation="vertical" flexItem sx={{ height: 16, borderColor: theme.palette.divider }} />
<Typography
sx={{
fontSize: "0.8rem",
color: theme.palette.text.secondary,
}}
>
Characters: {characterCount}
</Typography>
<Divider orientation="vertical" flexItem sx={{ height: 16, borderColor: theme.palette.divider }} />
<Typography
sx={{
fontSize: "0.8rem",
color: runningExtractors < totalExtractors ? theme.palette.error.main : theme.palette.text.secondary,
}}
>
Extractors: {runningExtractors}/{totalExtractors}
</Typography>
</Box>
</Box>
<IconButton
size="small"
onClick={() => setLocalIsCollapsed(!localIsCollapsed)}
sx={{
transform: localIsCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)',
transition: 'transform 0.2s ease-in-out'

View File

@@ -1,21 +0,0 @@
import { SessionContext } from "@/app/context/Context";
import { Button, Tooltip } from "@mui/material";
import { useContext } from "react";
export const AlertModeButton = () => {
const { alertMode, toggleAlertMode } = useContext(SessionContext);
return (
<Tooltip title="Toggle alert mode to show only accounts and planets that need action.">
<Button
style={{
backgroundColor: alertMode
? "rgba(144, 202, 249, 0.08)"
: "inherit",
}}
onClick={toggleAlertMode}
>
Alert mode
</Button>
</Tooltip>
);
};

View File

@@ -12,20 +12,25 @@ import * as React from "react";
import { DowloadButton } from "../Backup/DowloadButton";
import { UploadButton } from "../Backup/UploadButton";
import { CCPButton } from "../CCP/CCPButton";
import { CompactModeButton } from "../CompactModeButton/CompactModeButton";
import { DiscordButton } from "../Discord/DiscordButton";
import { GitHubButton } from "../Github/GitHubButton";
import { LoginButton } from "../Login/LoginButton";
import { PlanModeButton } from "../PlanModeButton/PlanModeButton";
import { SettingsButton } from "../Settings/SettingsButtons";
import { AlertModeButton } from "../AlertModeButton/AlertModeButton";
import { SupportButton } from "../SupportButton/SupportButton";
import {
Button,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
} from "@mui/material";
import { useState } from "react";
function ResponsiveAppBar() {
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(
null,
);
const [faqOpen, setFaqOpen] = useState(false);
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
setAnchorElNav(event.currentTarget);
@@ -102,23 +107,26 @@ function ResponsiveAppBar() {
<MenuItem onClick={handleCloseNavMenu}>
<GitHubButton />
</MenuItem>
<MenuItem onClick={handleCloseNavMenu}>
<CCPButton />
</MenuItem>
<MenuItem onClick={handleCloseNavMenu}>
<SupportButton />
</MenuItem>
<MenuItem onClick={handleCloseNavMenu}>
<SettingsButton />
</MenuItem>
<MenuItem onClick={handleCloseNavMenu}>
<CompactModeButton />
<MenuItem
onClick={() => {
handleCloseNavMenu();
setFaqOpen(true);
}}
>
<Button
href=""
style={{ width: "100%" }}
sx={{ color: "white", display: "flex", justifyContent: "flex-start" }}
>
FAQ
</Button>
</MenuItem>
<MenuItem onClick={handleCloseNavMenu}>
<PlanModeButton />
</MenuItem>
<MenuItem onClick={handleCloseNavMenu}>
<AlertModeButton />
<CCPButton />
</MenuItem>
</Menu>
</Box>
@@ -146,7 +154,7 @@ function ResponsiveAppBar() {
flexGrow: 1,
display: { xs: "none", md: "flex" },
alignItems: "center",
gap: "0.2rem"
gap: "0.2rem",
}}
>
<LoginButton />
@@ -154,15 +162,132 @@ function ResponsiveAppBar() {
<UploadButton />
<DiscordButton />
<GitHubButton />
<CCPButton />
<SupportButton />
<SettingsButton />
<CompactModeButton />
<PlanModeButton />
<AlertModeButton />
<Button onClick={() => setFaqOpen(true)} color="inherit">
FAQ
</Button>
<CCPButton />
</Box>
</Toolbar>
</Container>
<Dialog
open={faqOpen}
onClose={() => setFaqOpen(false)}
aria-labelledby="faq-dialog-title"
>
<DialogTitle id="faq-dialog-title">
Frequently Asked Questions
</DialogTitle>
<DialogContent>
<DialogContentText>
<strong>EVE Online Partner Code</strong>
<br />
Consider using my partner code for CCP related purchases to support
this project:
<Button
href=""
style={{ width: "100%" }}
sx={{ color: "white", display: "block" }}
onClick={() => {
navigator.clipboard.writeText("CALLIEVE");
}}
>
CALLIEVE
</Button>{" "}
Click button above to copy to clipboard.
<br />
<br />
<strong>What is this application?</strong>
<br />
This EVE Online Planetary Interaction tool that helps you track and
manage your colonies and production chains. Main usecase is to see
if your extractors are running.
<br />
<br />
<strong>How do I add characters?</strong>
<br />
You can add characters by clicking the &quot;Add Character&quot;
button in the app bar and following the authentication process.
<br />
<br />
<strong>Why don&apos;t my storage numbers add up?</strong>
<br />
EVE Online API (ESI) provides planetary interaction endpoints. These
are updated when you submit changes to planet. For example after an
extractor restart.
<br />
<br />
<strong>What does exclude mean?</strong>
<br />
Exclude means that this planet will not be counted in totals. This
is useful if you have longer production chains where exports of a
planet are consumed by another planet.
<br />
<br />
<strong>How do see my planets production chains?</strong>
<br />
Click on the planet row to open the extraction simulation.
<br />
<br />
<strong>What is Compact Mode?</strong>
<br />
Compact Mode reduces the size of character cards to show more
information at once. You can toggle it in the settings.
<br />
<br />
<strong>What is Alert Mode?</strong>
<br />
Alert mode shows only the planets that have extractors offline and
need attention.
<br />
<br />
<strong>What does off-blanace mean?</strong>
<br />
Off-blanace alert shows up for planets that have two extractors that
are extracting different amount of material. Treshold can be set in
settings. Generally you want to keep this below 1000.
<br />
<br />
<strong>How do I reorder accounts?</strong>
<br />
You can drag and drop account cards to reorder them. The order will
be saved automatically.
<br />
<br />
<strong>How do I add a character to account groups?</strong>
<br />
You can add a character to an account group by clicking the cog on
the character card and typing in the account/group name.
<br />
<br />
<strong>I like icons! Why so much text?</strong>
<br />
Toggle this option in settings.
<br />
<br />
<strong>I like colors! Why your alert colors are so ugly?</strong>
<br />
Change the color scheme in settings.
<br />
<br />
<strong>What does the 3D view do?</strong>
<br />
Nothing. Just made it for fun
<br />
<br />
<strong>Why are planet settings empty?</strong>
<br />
Its a work in progress feature. Plan is to be able to configuer
planet and planet exports to belong to production chains.
<br />
<br />
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setFaqOpen(false)}>Close</Button>
</DialogActions>
</Dialog>
</AppBar>
);
}

View File

@@ -7,16 +7,18 @@ import { styled, useTheme } from "@mui/material/styles";
import React from "react";
import { CharacterDialog } from "./CharacterDialog";
import { AccessToken } from "@/types";
import { Box, Button, Tooltip } from "@mui/material";
import { Box, Tooltip, IconButton, Typography } from "@mui/material";
import SettingsIcon from "@mui/icons-material/Settings";
import { EVE_IMAGE_URL } from "@/const";
import { CharacterContext } from "@/app/context/Context";
const StackItem = styled(Stack)(({ theme }) => ({
...theme.typography.body2,
padding: theme.custom.compactMode ? theme.spacing(1) : theme.spacing(2),
display: "flex",
textAlign: "left",
justifyContent: "center",
alignItems: "center",
justifyContent: "flex-start",
alignItems: "flex-start",
}));
export const CharacterRow = ({ character }: { character: AccessToken }) => {
@@ -29,8 +31,6 @@ export const CharacterRow = ({ character }: { character: AccessToken }) => {
return (
<StackItem
key={character.character.characterId}
alignItems="flex-start"
justifyContent="flex-start"
>
<CharacterDialog
character={selectedCharacter}
@@ -38,13 +38,49 @@ export const CharacterRow = ({ character }: { character: AccessToken }) => {
updateCharacter={updateCharacter}
closeDialog={() => setSelectedCharacter(undefined)}
/>
<Typography
sx={{
whiteSpace: "nowrap",
fontSize: theme.custom.smallText,
textAlign: "left",
lineHeight: 1.2,
marginBottom: "0.4rem",
marginLeft: "0.2rem",
overflow: "visible",
textOverflow: "clip",
width: "1rem"
}}
>
{character.character.name}
</Typography>
<Tooltip title={character.comment}>
<Box
display="flex"
flexDirection="column"
maxWidth={120}
onClick={() => setSelectedCharacter(character)}
position="relative"
sx={{ cursor: "pointer" }}
>
<IconButton
size="small"
onClick={(e) => {
e.stopPropagation();
setSelectedCharacter(character);
}}
sx={{
p: 0,
position: "absolute",
top: 4,
left: 4,
backgroundColor: "rgba(0, 0, 0, 0.5)",
"&:hover": {
backgroundColor: "rgba(0, 0, 0, 0.7)",
},
}}
>
<SettingsIcon fontSize="small" sx={{ color: "white" }} />
</IconButton>
<Image
unoptimized
src={`${EVE_IMAGE_URL}/characters/${character.character.characterId}/portrait?size=128`}
@@ -53,16 +89,6 @@ export const CharacterRow = ({ character }: { character: AccessToken }) => {
height={theme.custom.cardImageSize}
style={{ marginBottom: "0.2rem", borderRadius: 8 }}
/>
<Button
style={{
padding: 6,
fontSize: theme.custom.smallText,
lineHeight: 1,
}}
variant="outlined"
>
{character.character.name}
</Button>
</Box>
</Tooltip>
</StackItem>

View File

@@ -1,21 +0,0 @@
import { SessionContext } from "@/app/context/Context";
import { Button, Tooltip } from "@mui/material";
import { useContext } from "react";
export const CompactModeButton = () => {
const { compactMode, toggleCompactMode } = useContext(SessionContext);
return (
<Tooltip title="Toggle compact layout for widescreen">
<Button
style={{
backgroundColor: compactMode
? "rgba(144, 202, 249, 0.08)"
: "inherit",
}}
onClick={toggleCompactMode}
>
Compact mode
</Button>
</Tooltip>
);
};

View File

@@ -6,15 +6,21 @@ import {
ThemeProvider,
createTheme,
Button,
Tooltip,
} from "@mui/material";
import { AccountCard } from "./Account/AccountCard";
import { AccessToken } from "@/types";
import { CharacterContext, SessionContext } from "../context/Context";
import ResponsiveAppBar from "./AppBar/AppBar";
import { Summary } from "./Summary/Summary";
import { DragDropContext, Droppable, Draggable, DropResult } from "@hello-pangea/dnd";
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
import {
DragDropContext,
Droppable,
Draggable,
DropResult,
} from "@hello-pangea/dnd";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
interface Grouped {
[key: string]: AccessToken[];
@@ -43,6 +49,7 @@ declare module "@mui/material/styles" {
export const MainGrid = () => {
const { characters, updateCharacter } = useContext(CharacterContext);
const { compactMode, toggleCompactMode, alertMode, toggleAlertMode, planMode, togglePlanMode } = useContext(SessionContext);
const [accountOrder, setAccountOrder] = useState<string[]>([]);
const [allCollapsed, setAllCollapsed] = useState(false);
@@ -54,15 +61,19 @@ export const MainGrid = () => {
group[account ?? ""] = group[account ?? ""] ?? [];
group[account ?? ""].push(character);
return group;
}, {})
}, {}),
);
const savedOrder = localStorage.getItem('accountOrder');
const savedOrder = localStorage.getItem("accountOrder");
if (savedOrder) {
try {
const parsedOrder = JSON.parse(savedOrder);
const validOrder = parsedOrder.filter((account: string) => currentAccounts.includes(account));
const newAccounts = currentAccounts.filter(account => !validOrder.includes(account));
const validOrder = parsedOrder.filter((account: string) =>
currentAccounts.includes(account),
);
const newAccounts = currentAccounts.filter(
(account) => !validOrder.includes(account),
);
setAccountOrder([...validOrder, ...newAccounts]);
} catch (e) {
setAccountOrder(currentAccounts);
@@ -74,7 +85,7 @@ export const MainGrid = () => {
useEffect(() => {
if (accountOrder.length > 0) {
localStorage.setItem('accountOrder', JSON.stringify(accountOrder));
localStorage.setItem("accountOrder", JSON.stringify(accountOrder));
}
}, [accountOrder]);
@@ -85,7 +96,6 @@ export const MainGrid = () => {
return group;
}, {});
const { compactMode } = useContext(SessionContext);
const [darkTheme, setDarkTheme] = useState(
createTheme({
palette: {
@@ -138,14 +148,62 @@ export const MainGrid = () => {
<Box sx={{ flexGrow: 1 }}>
<ResponsiveAppBar />
{compactMode ? <></> : <Summary characters={characters} />}
<Box sx={{ display: 'flex', justifyContent: 'flex-start', padding: 1 }}>
<Box
sx={{
display: "flex",
justifyContent: "flex-start",
padding: 1,
gap: 1,
}}
>
<Button
startIcon={allCollapsed ? <KeyboardArrowDownIcon /> : <KeyboardArrowUpIcon />}
startIcon={
allCollapsed ? <KeyboardArrowDownIcon /> : <KeyboardArrowUpIcon />
}
onClick={() => setAllCollapsed(!allCollapsed)}
size="small"
>
{allCollapsed ? 'Expand All' : 'Collapse All'}
{allCollapsed ? "Expand All" : "Collapse All"}
</Button>
<Tooltip title="Toggle compact layout for widescreen">
<Button
size="small"
style={{
backgroundColor: compactMode
? "rgba(144, 202, 249, 0.08)"
: "inherit",
}}
onClick={toggleCompactMode}
>
Compact mode
</Button>
</Tooltip>
<Tooltip title="Toggle alert mode to show only accounts and planets that need action.">
<Button
size="small"
style={{
backgroundColor: alertMode
? "rgba(144, 202, 249, 0.08)"
: "inherit",
}}
onClick={toggleAlertMode}
>
Alert mode
</Button>
</Tooltip>
<Tooltip title="Toggle plan mode that show layout for widescreen">
<Button
size="small"
style={{
backgroundColor: planMode
? "rgba(144, 202, 249, 0.08)"
: "inherit",
}}
onClick={togglePlanMode}
>
Plan mode
</Button>
</Tooltip>
</Box>
<DragDropContextComponent onDragEnd={handleDragEnd}>
<DroppableComponent droppableId="accounts">
@@ -153,7 +211,7 @@ export const MainGrid = () => {
<Grid
container
spacing={1}
sx={{ padding: 1 }}
sx={{ padding: 1, width: "100%" }}
{...provided.droppableProps}
ref={provided.innerRef}
>
@@ -172,17 +230,18 @@ export const MainGrid = () => {
{...provided.draggableProps}
{...provided.dragHandleProps}
sx={{
'& > *': {
width: '100%',
"& > *": {
width: "100%",
},
}}
>
{groupByAccount[account] && groupByAccount[account].length > 0 && (
<AccountCard
characters={groupByAccount[account]}
isCollapsed={allCollapsed}
/>
)}
{groupByAccount[account] &&
groupByAccount[account].length > 0 && (
<AccountCard
characters={groupByAccount[account]}
isCollapsed={allCollapsed}
/>
)}
</Grid>
)}
</DraggableComponent>

View File

@@ -1,15 +0,0 @@
import { SessionContext } from "@/app/context/Context";
import { Button, Tooltip } from "@mui/material";
import { useContext } from "react";
export const PlanModeButton = () => {
const { planMode, togglePlanMode } = useContext(SessionContext);
return (
<Tooltip title="Toggle plan mode that show layout for widescreen">
<Button onClick={togglePlanMode} style={{backgroundColor: planMode ? 'rgba(144, 202, 249, 0.08)' : 'inherit'}}>
Plan mode
</Button>
</Tooltip>
);
};

View File

@@ -1,5 +1,5 @@
import { ColorContext, SessionContext } from "@/app/context/Context";
import { PI_TYPES_MAP, STORAGE_IDS, STORAGE_CAPACITIES, PI_PRODUCT_VOLUMES } from "@/const";
import { PI_TYPES_MAP, STORAGE_IDS, STORAGE_CAPACITIES, PI_PRODUCT_VOLUMES, EVE_IMAGE_URL } from "@/const";
import { planetCalculations } from "@/planets";
import { AccessToken, PlanetWithInfo } from "@/types";
import CloseIcon from "@mui/icons-material/Close";
@@ -41,6 +41,7 @@ export const PlanetTableRow = ({
character: AccessToken;
}) => {
const theme = useTheme();
const { showProductIcons } = useContext(SessionContext);
const [planetRenderOpen, setPlanetRenderOpen] = useState(false);
const [planetConfigOpen, setPlanetConfigOpen] = useState(false);
@@ -153,6 +154,39 @@ export const PlanetTableRow = ({
});
};
const renderProductDisplay = (typeId: number, amount?: number) => {
if (showProductIcons) {
return (
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }}>
<Image
src={`${EVE_IMAGE_URL}/types/${typeId}/icon?size=32`}
alt={PI_TYPES_MAP[typeId].name}
width={32}
height={32}
style={{ marginRight: "5px" }}
/>
{amount !== undefined && (
<Typography fontSize={theme.custom.smallText} style={{ marginLeft: "5px", flexShrink: 0 }}>
{amount}
</Typography>
)}
</div>
);
}
return (
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%" }}>
<Typography fontSize={theme.custom.smallText}>
{PI_TYPES_MAP[typeId].name}
</Typography>
{amount !== undefined && (
<Typography fontSize={theme.custom.smallText} style={{ marginLeft: "5px", flexShrink: 0 }}>
{amount}
</Typography>
)}
</div>
);
};
return (
<>
<TableRow
@@ -238,11 +272,12 @@ export const PlanetTableRow = ({
<TableCell className="clickable-cell">{planet.upgrade_level}</TableCell>
<TableCell className="clickable-cell">
<div style={{ display: "flex", flexDirection: "column" }}>
{extractors.length === 0 &&<Typography fontSize={theme.custom.smallText}>No extractors</Typography>}
{extractors.map((e, idx) => {
return (
<div
key={`${e}-${idx}-${character.character.characterId}`}
style={{ display: "flex" }}
style={{ display: "flex", alignItems: "center" }}
>
<Typography
color={timeColor(e.expiry_time, colors)}
@@ -258,12 +293,7 @@ export const PlanetTableRow = ({
"STOPPED"
)}
</Typography>
<Typography fontSize={theme.custom.smallText}>
{
PI_TYPES_MAP[e.extractor_details?.product_type_id ?? 0]
?.name
}
</Typography>
{renderProductDisplay(e.extractor_details?.product_type_id ?? 0)}
</div>
);
})}
@@ -273,12 +303,12 @@ export const PlanetTableRow = ({
<div style={{ display: "flex", flexDirection: "column" }}>
{Array.from(localProduction).map((schematic, idx) => {
return (
<Typography
<div
key={`prod-${character.character.characterId}-${planet.planet_id}-${idx}`}
fontSize={theme.custom.smallText}
style={{ display: "flex", alignItems: "center" }}
>
{schematic[1].name}
</Typography>
{renderProductDisplay(schematic[1].outputs[0].type_id)}
</div>
);
})}
</div>
@@ -286,24 +316,24 @@ export const PlanetTableRow = ({
<TableCell className="clickable-cell">
<div style={{ display: "flex", flexDirection: "column" }}>
{localImports.map((i) => (
<Typography
<div
key={`import-${character.character.characterId}-${planet.planet_id}-${i.type_id}`}
fontSize={theme.custom.smallText}
style={{ display: "flex", alignItems: "center" }}
>
{PI_TYPES_MAP[i.type_id].name} ({i.quantity * i.factoryCount}/h)
</Typography>
{renderProductDisplay(i.type_id, i.quantity * i.factoryCount)}
</div>
))}
</div>
</TableCell>
<TableCell className="clickable-cell">
<div style={{ display: "flex", flexDirection: "column" }}>
{localExports.map((exports) => (
<Typography
<div
key={`export-${character.character.characterId}-${planet.planet_id}-${exports.typeId}`}
fontSize={theme.custom.smallText}
style={{ display: "flex", alignItems: "center" }}
>
{PI_TYPES_MAP[exports.typeId].name}
</Typography>
{renderProductDisplay(exports.typeId, exports.amount)}
</div>
))}
</div>
</TableCell>
@@ -371,6 +401,7 @@ export const PlanetTableRow = ({
</TableCell>
<TableCell className="clickable-cell">
<div style={{ display: "flex", flexDirection: "column" }}>
{storageFacilities.length === 0 &&<Typography fontSize={theme.custom.smallText}>No storage</Typography>}
{storageFacilities
.sort((a, b) => {
const isALaunchpad = a.type_id === 2256 || a.type_id === 2542 || a.type_id === 2543 || a.type_id === 2544 || a.type_id === 2552 || a.type_id === 2555 || a.type_id === 2556 || a.type_id === 2557;

View File

@@ -36,8 +36,8 @@ const PlanetaryIteractionTable = ({
return (
<StackItem width="100%">
<TableContainer component={Paper}>
<Table size="small" aria-label="a dense table">
<TableContainer component={Paper} sx={{ width: '100%' }}>
<Table size="small" aria-label="a dense table" sx={{ width: '100%' }}>
<TableHead>
<TableRow>
<TableCell width="8%">
@@ -84,8 +84,8 @@ const PlanetaryIteractionTable = ({
</Tooltip>
</TableCell>
<TableCell width="2%">
<Tooltip title="How many units per hour factories are producing">
<Typography fontSize={theme.custom.smallText}>u/h</Typography>
<Tooltip title="How many units per hour factories are producing on this planet">
<Typography fontSize={theme.custom.smallText}>uph</Typography>
</Tooltip>
</TableCell>
<TableCell width="4%" align="right">
@@ -98,7 +98,7 @@ const PlanetaryIteractionTable = ({
<TableCell width="10%">
<Tooltip title="Storage facility fill rate">
<Typography fontSize={theme.custom.smallText}>
Storage Fill rate
Storage%
</Typography>
</Tooltip>
</TableCell>
@@ -159,8 +159,8 @@ export const PlanetaryInteractionRow = ({
const theme = useTheme();
return theme.custom.compactMode ? (
<PlanetaryInteractionIconsRow character={character} />
<div style={{ marginTop: "1.2rem" }}><PlanetaryInteractionIconsRow character={character} /></div>
) : (
<PlanetaryIteractionTable character={character} />
<div style={{ marginTop: "1.4rem" }}><PlanetaryIteractionTable character={character} /></div>
);
};

View File

@@ -13,13 +13,15 @@ import {
Typography,
TextField,
Box,
FormControlLabel,
Checkbox,
} from "@mui/material";
import { ColorChangeHandler, ColorResult, CompactPicker } from "react-color";
import React, { useState, useContext } from "react";
export const SettingsButton = () => {
const { colors, setColors } = useContext(ColorContext);
const { balanceThreshold, setBalanceThreshold } = useContext(SessionContext);
const { balanceThreshold, setBalanceThreshold, showProductIcons, setShowProductIcons } = useContext(SessionContext);
const [open, setOpen] = useState(false);
const handleClickOpen = () => {
@@ -45,6 +47,10 @@ export const SettingsButton = () => {
}
};
const handleShowProductIconsChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setShowProductIcons(event.target.checked);
};
return (
<Tooltip title="Toggle settings dialog">
<>
@@ -57,13 +63,24 @@ export const SettingsButton = () => {
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{"Settings"}
</DialogTitle>
<DialogContent style={{ paddingTop: "1rem" }}>
<Box sx={{ mt: 2 }}>
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle1">Display Settings</Typography>
<FormControlLabel
control={
<Checkbox
checked={showProductIcons}
onChange={handleShowProductIconsChange}
/>
}
label="Show product icons instead of names"
/>
</Box>
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle1">Balance Threshold</Typography>
<TextField
type="number"
@@ -87,7 +104,6 @@ export const SettingsButton = () => {
</div>
);
})}
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Close</Button>

View File

@@ -199,8 +199,8 @@ export const Summary = ({ characters }: { characters: AccessToken[] }) => {
sx={{ width: '100px' }}
/>
</Box>
<TableContainer component={Paper}>
<Table size="small" aria-label="a dense table">
<TableContainer component={Paper} sx={{ width: '100%' }}>
<Table size="small" aria-label="a dense table" sx={{ width: '100%' }}>
<TableHead>
<TableRow>
<TableCell width="20%">

View File

@@ -1,23 +0,0 @@
import { Box, Button, Tooltip } from "@mui/material";
export const SupportButton = () => {
return (
<Box>
<Tooltip
title={`
Consider using code 'CALLIEVE' on EVE store checkout to support the project! Click to copy to clipboard ;)
`}
>
<Button
href=""
style={{ width: "100%" }}
sx={{ color: "white", display: "block" }}
onClick={() => {
navigator.clipboard.writeText("CALLIEVE");
}}
>
CALLIEVE
</Button>
</Tooltip>
</Box>
);
};

View File

@@ -37,6 +37,8 @@ export const SessionContext = createContext<{
}) => PlanetConfig;
balanceThreshold: number;
setBalanceThreshold: Dispatch<SetStateAction<number>>;
showProductIcons: boolean;
setShowProductIcons: (show: boolean) => void;
}>({
sessionReady: false,
refreshSession: () => {},
@@ -62,7 +64,10 @@ export const SessionContext = createContext<{
},
balanceThreshold: 1000,
setBalanceThreshold: () => {},
showProductIcons: false,
setShowProductIcons: () => {},
});
export type ColorSelectionType = {
defaultColor: string;
expiredColor: string;
@@ -84,6 +89,7 @@ export const defaultColors = {
dayColor: "#2F695A",
twoDaysColor: "#2F695A",
};
export const ColorContext = createContext<{
colors: ColorSelectionType;
setColors: (colors: ColorSelectionType) => void;

View File

@@ -30,6 +30,7 @@ const Home = () => {
undefined,
);
const [balanceThreshold, setBalanceThreshold] = useState(1000);
const [showProductIcons, setShowProductIcons] = useState(false);
const [colors, setColors] = useState<ColorSelectionType>(defaultColors);
const [alertMode, setAlertMode] = useState(false);
@@ -248,7 +249,7 @@ const Home = () => {
}, []);
useEffect(() => {
const ESI_CACHE_TIME_MS = 600000;
const ESI_CACHE_TIME_MS = 3000000;
const interval = setInterval(() => {
const characters = initializeCharacters();
refreshSession(characters)
@@ -278,6 +279,8 @@ const Home = () => {
readPlanetConfig,
balanceThreshold,
setBalanceThreshold,
showProductIcons,
setShowProductIcons,
}}
>
<CharacterContext.Provider