Compare commits

..

No commits in common. "main" and "release-0.7" have entirely different histories.

8 changed files with 120 additions and 108 deletions

View File

@ -134,14 +134,6 @@ fun initH2Server(
it[KeyValue.value] = 1 it[KeyValue.value] = 1
} }
} }
if (schemaVersion < 2) {
println("Migrating database to schemaVersion 2.")
TransactionManager.current().exec("TRUNCATE TABLE AGGREGATESTATISTICS")
KeyValue.update({ KeyValue.key eq "SchemaVersion" }) { it[KeyValue.value] = 2 }
}
} }
return database return database

View File

@ -144,13 +144,7 @@ object StatisticsImporter {
WHERE array_contains(array['minecraft:mined'], Live."Type") WHERE array_contains(array['minecraft:mined'], Live."Type")
GROUP BY Live."Type", Live."Name", Live."PlayerId" GROUP BY Live."Type", Live."Name", Live."PlayerId"
) as LiveMax ) as LiveMax
LEFT JOIN ( LEFT JOIN AGGREGATESTATISTICS as Agg
SELECT AGGREGATESTATISTICS."Type",
AGGREGATESTATISTICS."Name",
MAX(AGGREGATESTATISTICS."Value") as "Value"
FROM AGGREGATESTATISTICS
GROUP BY AGGREGATESTATISTICS."Type", AGGREGATESTATISTICS."Name"
) as Agg
ON LiveMax."Type" = Agg."Type" ON LiveMax."Type" = Agg."Type"
GROUP BY LiveMax."Type" GROUP BY LiveMax."Type"
HAVING sum(LiveMax."Value") <> max(Agg."Value") OR max(Agg."Value") IS NULL; HAVING sum(LiveMax."Value") <> max(Agg."Value") OR max(Agg."Value") IS NULL;
@ -169,13 +163,7 @@ object StatisticsImporter {
OR array_contains(array['minecraft:killed', 'minecraft:killed_by'], Live."Type") OR array_contains(array['minecraft:killed', 'minecraft:killed_by'], Live."Type")
GROUP BY Live."Type", Live."Name", Live."PlayerId" GROUP BY Live."Type", Live."Name", Live."PlayerId"
) as LiveMax ) as LiveMax
LEFT JOIN ( LEFT JOIN AGGREGATESTATISTICS as Agg
SELECT AGGREGATESTATISTICS."Type",
AGGREGATESTATISTICS."Name",
MAX(AGGREGATESTATISTICS."Value") as "Value"
FROM AGGREGATESTATISTICS
GROUP BY AGGREGATESTATISTICS."Type", AGGREGATESTATISTICS."Name"
) as Agg
ON LiveMax."Type" = Agg."Type" AND LiveMax."Name" = Agg."Name" ON LiveMax."Type" = Agg."Type" AND LiveMax."Name" = Agg."Name"
GROUP BY LiveMax."Type", LiveMax."Name" GROUP BY LiveMax."Type", LiveMax."Name"
HAVING SUM(LiveMax."Value") <> MAX(Agg."Value") OR MAX(Agg."Value") IS NULL; HAVING SUM(LiveMax."Value") <> MAX(Agg."Value") OR MAX(Agg."Value") IS NULL;

View File

@ -2,43 +2,59 @@ import React from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import axios from "axios"; import axios from "axios";
import { SimpleGrid, Stat, StatLabel, StatNumber } from "@chakra-ui/react"; import {
AspectRatio,
Box,
Flex,
Heading,
HStack,
Icon,
SimpleGrid,
Stat,
StatLabel,
StatNumber,
StatHelpText,
StatArrow,
StatGroup,
Tooltip,
} from "@chakra-ui/react";
import { FaUserCircle, FaArrowAltCircleRight } from "react-icons/fa";
import { Link } from "react-router-dom";
import { AutoSizer, WindowScroller, List } from "react-virtualized";
import prettifyStatisticName from "./PrettifyStatisticName"; import prettifyStatisticName from "./PrettifyStatisticName";
import StackedBar from "./StackedBar"; import StackedBar from "./StackedBar";
const AggregatesShowcase = () => { const AggregatesShowcase = () => {
const aggregates = useQuery(`aggregates`, async () => { const aggregates = useQuery(
const { data } = await axios.get(`/api/aggregates`); `aggregates`,
return data; async () => {
}); const { data } = await axios.get(`/api/aggregates`);
return data;
},
{
placeholderData: [],
}
);
const killedAggregates = aggregates.isSuccess const killedAggregates = aggregates.data
? aggregates.data .filter((x) => x.type === "minecraft:killed")
.filter((x) => x.type === "minecraft:killed") .sort((a, b) => b.value - a.value);
.sort((a, b) => b.value - a.value) const killedTotal = killedAggregates.reduce((acc, val) => acc + val.value, 0);
: [];
// const killedTotal = killedAggregates.reduce((acc, val) => acc + val.value, 0);
const killedByAggregates = aggregates.isSuccess const killedByAggregates = aggregates.data
? aggregates.data .filter((x) => x.type === "minecraft:killed_by")
.filter((x) => x.type === "minecraft:killed_by") .sort((a, b) => b.value - a.value);
.sort((a, b) => b.value - a.value) const killedByTotal = killedByAggregates.reduce(
: []; (acc, val) => acc + val.value,
// const killedByTotal = killedByAggregates.reduce( 0
// (acc, val) => acc + val.value, );
// 0
// );
const travelAggregates = aggregates.isSuccess const travelAggregates = aggregates.data
? aggregates.data .filter((x) => x.type === "minecraft:custom" && x.name.endsWith("one_cm"))
.filter( .sort((a, b) => b.value - a.value);
(x) => x.type === "minecraft:custom" && x.name.endsWith("one_cm") const travelTotal = travelAggregates.reduce((acc, val) => acc + val.value, 0);
)
.sort((a, b) => b.value - a.value)
: [];
// const travelTotal = travelAggregates.reduce((acc, val) => acc + val.value, 0);
return aggregates.isSuccess ? ( return aggregates.isFetched ? (
<> <>
<StackedBar heading="Mobs Killed" aggregates={killedAggregates} /> <StackedBar heading="Mobs Killed" aggregates={killedAggregates} />
@ -57,13 +73,13 @@ const AggregatesShowcase = () => {
.map((x, i) => { .map((x, i) => {
var value = x.value; var value = x.value;
if (x.name === "minecraft:play_one_minute") { if (x.name === "minecraft:play_one_minute") {
value = Math.floor(x.value / 20 / 60); value = x.value / 20 / 60;
} }
return ( return (
<Stat key={i}> <Stat key={i}>
<StatLabel>{prettifyStatisticName(x.type, x.name)}</StatLabel> <StatLabel>{prettifyStatisticName(x.type, x.name)}</StatLabel>
<StatNumber>{`${value <StatNumber>{`${x.value
.toString() .toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}</StatNumber> .replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`}</StatNumber>
</Stat> </Stat>

View File

@ -41,17 +41,14 @@ const App = () => {
<Statistic <Statistic
type={routeProps.match.params.type} type={routeProps.match.params.type}
name={routeProps.match.params.name} name={routeProps.match.params.name}
players={players.data} players={players}
/> />
)} )}
/> />
<Route <Route
path="/player/:id" path="/player/:id"
render={(routeProps) => ( render={(routeProps) => (
<Player <Player playerId={routeProps.match.params.id} players={players} />
playerId={routeProps.match.params.id}
players={players.data}
/>
)} )}
/> />
<Route> <Route>

View File

@ -16,13 +16,19 @@ import { Link } from "react-router-dom";
import prettifyStatisticName from "./PrettifyStatisticName"; import prettifyStatisticName from "./PrettifyStatisticName";
const Player = ({ playerId, players }) => { const Player = ({ playerId, players }) => {
const playerStats = useQuery(`player ${playerId}`, async () => { const playerStats = useQuery(
const { data } = await axios.get(`/api/players/${playerId}`); `player ${playerId}`,
data.sort((a, b) => a.rank - b.rank || b.value - a.value); async () => {
return data.filter((x) => x.value > 0); const { data } = await axios.get(`/api/players/${playerId}`);
}); data.sort((a, b) => a.rank - b.rank);
return data.filter((x) => x.value > 0);
},
{
placeholderData: [],
}
);
const playerName = players?.find((x) => x.id === playerId)?.name; const playerName = players.data.find((x) => x.id === playerId)?.name;
return ( return (
<> <>
@ -54,23 +60,19 @@ const Player = ({ playerId, players }) => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{playerStats.isSuccess ? ( {playerStats.data.map((x, i) => {
playerStats.data.map((x, i) => { return (
return ( <Tr key={i}>
<Tr key={i}> <Td>
<Td> <Link to={`/statistic/${x.type}/${x.name}`}>
<Link to={`/statistic/${x.type}/${x.name}`}> {prettifyStatisticName(x.type, x.name)}
{prettifyStatisticName(x.type, x.name)} </Link>
</Link> </Td>
</Td> <Td isNumeric>{x.rank}</Td>
<Td isNumeric>{x.rank}</Td> <Td isNumeric>{x.value}</Td>
<Td isNumeric>{x.value}</Td> </Tr>
</Tr> );
); })}
})
) : (
<></>
)}
</Tbody> </Tbody>
</Table> </Table>
</> </>

View File

@ -1,6 +1,21 @@
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import useHover from "./useHover"; import useHover from "./useHover";
import { Box, Heading, HStack, Tooltip } from "@chakra-ui/react"; import {
AspectRatio,
Box,
Button,
Heading,
HStack,
Icon,
Input,
Stat,
StatLabel,
StatNumber,
StatHelpText,
StatArrow,
StatGroup,
Tooltip,
} from "@chakra-ui/react";
import prettifyStatisticName from "./PrettifyStatisticName"; import prettifyStatisticName from "./PrettifyStatisticName";
const randomColor = () => const randomColor = () =>
@ -26,7 +41,7 @@ const StackedBarSegment = ({ aggregate, total }) => {
<Box <Box
width={aggregate.value / total} width={aggregate.value / total}
height="100%" height="100%"
filter={isHovered ? "clear" : "saturate(0.5)"} filter={isHovered ? "clear" : "saturate(0.15)"}
backgroundColor={color} backgroundColor={color}
ref={hoverRef} ref={hoverRef}
></Box> ></Box>

View File

@ -15,13 +15,19 @@ import { Link } from "react-router-dom";
import prettifyStatisticName from "./PrettifyStatisticName"; import prettifyStatisticName from "./PrettifyStatisticName";
const Statistic = ({ type, name, players }) => { const Statistic = ({ type, name, players }) => {
const ranking = useQuery(`statistic ${type} ${name}`, async () => { const ranking = useQuery(
const { data } = await axios.get(`/api/statistics/${type}/${name}`); `statistic ${type} ${name}`,
return data.filter((x) => x.value > 0); async () => {
}); const { data } = await axios.get(`/api/statistics/${type}/${name}`);
return data.filter((x) => x.value > 0);
},
{
placeholderData: [],
}
);
const playerDict = useMemo(() => { const playerDict = useMemo(() => {
return Object.assign({}, ...players.map((x) => ({ [x.id]: x.name }))); return Object.assign({}, ...players.data.map((x) => ({ [x.id]: x.name })));
}, [players]); }, [players]);
return ( return (
@ -46,23 +52,19 @@ const Statistic = ({ type, name, players }) => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{ranking.isSuccess ? ( {ranking.data.map((x, i) => {
ranking.data.map((x, i) => { return (
return ( <Tr key={i}>
<Tr key={i}> <Td>
<Td> <Link to={`/player/${x.playerId}`}>
<Link to={`/player/${x.playerId}`}> {playerDict[x.playerId]}
{playerDict[x.playerId]} </Link>
</Link> </Td>
</Td> <Td isNumeric>{x.rank}</Td>
<Td isNumeric>{x.rank}</Td> <Td isNumeric>{x.value}</Td>
<Td isNumeric>{x.value}</Td> </Tr>
</Tr> );
); })}
})
) : (
<></>
)}
</Tbody> </Tbody>
</Table> </Table>
</> </>

View File

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
function useHover() { function useHover() {
const [value, setValue] = useState(false); const [value, setValue] = useState(false);
@ -16,8 +16,8 @@ function useHover() {
node.removeEventListener("mouseout", handleMouseOut); node.removeEventListener("mouseout", handleMouseOut);
}; };
} }
} //, },
//[ref.current] // Recall only if ref changes [ref.current] // Recall only if ref changes
); );
return [ref, value]; return [ref, value];
} }