Skip to content

Commit

Permalink
Improve performance and slightly reorganise accordion
Browse files Browse the repository at this point in the history
  • Loading branch information
gausie committed Jul 18, 2023
1 parent c2e2830 commit 75eb241
Show file tree
Hide file tree
Showing 17 changed files with 198 additions and 188 deletions.
16 changes: 8 additions & 8 deletions packages/greenbox-web/src/components/Familiars.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { SimpleGrid } from "@chakra-ui/react";
import { RawFamiliar, FamiliarStatus } from "greenbox-data";
import { useMemo } from "react";
import { useSelector } from "react-redux";

import { RootState } from "../store";
import { useAppSelector } from "../hooks";
import { createPlayerDataSelector } from "../store";

import Familiar from "./Familiar";
import Section from "./Section";

type Props = {
familiars: RawFamiliar[];
};
const selectPlayerFamiliars = createPlayerDataSelector("familiars");

export default function Familiars({ familiars: playerFamiliars }: Props) {
const familiars = useSelector((state: RootState) => state.familiars.filter((s) => !s.pokefam));
const loading = useSelector((state: RootState) => state.loading.familiars || false);
export default function Familiars() {
const playerFamiliars = useAppSelector(selectPlayerFamiliars);
const allFamiliars = useAppSelector((state) => state.familiars);
const familiars = useMemo(() => allFamiliars.filter((s) => !s.pokefam), [allFamiliars]);
const loading = useAppSelector((state) => state.loading.familiars || false);

const totalInTerrarium = useMemo(
() => playerFamiliars.filter((f) => f[1] === FamiliarStatus.TERRARIUM).length,
Expand Down
11 changes: 5 additions & 6 deletions packages/greenbox-web/src/components/FavouriteButton.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { IconButton } from "@chakra-ui/react";
import { useCallback } from "react";
import { useDispatch, useSelector } from "react-redux";

import { RootState, updateFavouritePlayerId } from "../store";
import { useAppDispatch, useAppSelector } from "../hooks";
import { updateFavouritePlayerId } from "../store";

import Image from "./Image";

export function FavouriteButton() {
const playerId = useSelector((state: RootState) => state.playerId);

const favouritePlayerId = useSelector((state: RootState) => state.favouritePlayerId);
const playerId = useAppSelector((state) => state.playerId);
const favouritePlayerId = useAppSelector((state) => state.favouritePlayerId);

const current = favouritePlayerId === playerId;

Expand All @@ -21,7 +20,7 @@ export function FavouriteButton() {
favouritePlayerId ? ` (currently your favourite is #${favouritePlayerId})` : ""
}`;

const dispatch = useDispatch();
const dispatch = useAppDispatch();
const toggle = useCallback(
(event: React.MouseEvent) => {
event.preventDefault();
Expand Down
89 changes: 40 additions & 49 deletions packages/greenbox-web/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { QuestionOutlineIcon } from "@chakra-ui/icons";
import {
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
Alert,
AlertIcon,
Box,
Expand All @@ -17,9 +13,9 @@ import {
} from "@chakra-ui/react";
import { RawSnapshotData } from "greenbox-data";
import { useCallback } from "react";
import { useDispatch } from "react-redux";

import { fetchAll, store } from "../store";
import { useAppDispatch } from "../hooks";
import { fetchAll } from "../store";

import MetaInfo from "./MetaInfo";
import Spinner from "./Spinner";
Expand All @@ -40,55 +36,50 @@ It will not collect any new information about you specifically - you still need
`;

export default function Header({ meta, direct, loading, error, errorMessage }: Props) {
const dispatch = useDispatch<typeof store.dispatch>();
const dispatch = useAppDispatch();

const forceUpdate = useCallback(() => {
dispatch(fetchAll(true));
}, [dispatch]);

return (
<AccordionItem>
<AccordionButton fontSize="4xl" as="section" alignItems="start">
<HStack alignItems="center" flex={1} maxWidth="100%" wrap="wrap">
<Heading as="h1">Greenbox</Heading>
<Box>
<SwitchButton />
</Box>
<Box flex={1} />
<Box textAlign="right">
{meta ? (
<MetaInfo direct={direct} meta={meta} />
) : loading ? (
<Spinner />
) : error ? (
<Alert status="error" fontSize="md">
<AlertIcon />
{errorMessage}
</Alert>
) : null}
</Box>
</HStack>
<AccordionIcon height="1.1em" />
</AccordionButton>
<AccordionPanel>
<Stack>
<Text>To get the data from your account, first install the script by running</Text>
<Code p={2} borderRadius={5}>
git checkout loathers/greenbox release
</Code>
<Text>
in KoLmafia's Graphical CLI. Once that's done, you can update the data at this link
whenever you like by running <Code>greenbox</Code>.
</Text>
<Stack direction="row-reverse" pt={3}>
<Tooltip p={2} label={forceRefreshInfo}>
<Button size="xs" colorScheme="red" onClick={forceUpdate}>
Force update game data <QuestionOutlineIcon ml={1} />
</Button>
</Tooltip>
</Stack>
<Stack as="section" alignItems="stretch" py={2}>
<HStack alignItems="center" flex={1} maxWidth="100%" wrap="wrap">
<Heading as="h1">Greenbox</Heading>
<Box>
<SwitchButton />
</Box>
<Box flex={1} />
<Box textAlign="right">
{meta ? (
<MetaInfo direct={direct} meta={meta} />
) : loading ? (
<Spinner />
) : error ? (
<Alert status="error" fontSize="md">
<AlertIcon />
{errorMessage}
</Alert>
) : null}
</Box>
</HStack>
<Stack>
<Text>To get the data from your account, first install the script by running</Text>
<Code p={2} borderRadius={5}>
git checkout loathers/greenbox release
</Code>
<Text>
in KoLmafia's Graphical CLI. Once that's done, you can update the data at this link
whenever you like by running <Code>greenbox</Code>.
</Text>
<Stack direction="row-reverse" pt={3}>
<Tooltip p={2} label={forceRefreshInfo}>
<Button size="xs" colorScheme="red" onClick={forceUpdate}>
Force update game data <QuestionOutlineIcon ml={1} />
</Button>
</Tooltip>
</Stack>
</AccordionPanel>
</AccordionItem>
</Stack>
</Stack>
);
}
39 changes: 10 additions & 29 deletions packages/greenbox-web/src/components/IotMs.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,21 @@
import { Badge, Box, Flex, SimpleGrid, Text } from "@chakra-ui/react";
import { Box, SimpleGrid } from "@chakra-ui/react";
import { IotMStatus, RawIotM } from "greenbox-data";
import { useMemo } from "react";
import { useSelector } from "react-redux";

import { RootState } from "../store";
import { chunk, notNullOrUndefined, useItemMap } from "../utils";
import { useAppSelector, useItemMap } from "../hooks";
import { createPlayerDataSelector } from "../store";
import { chunk, notNullOrUndefined } from "../utils";

import IotM from "./IotM";
import Section from "./Section";
import Year from "./Year";

function Year({ year, complete }: { year: number; complete: boolean }) {
return (
<Flex
gridColumn={["1 / span 3", null, 1]}
key={`year-${year}`}
alignItems="center"
justifyContent={[null, null, "flex-end"]}
>
<Badge
transform={[null, null, "rotate(270deg)"]}
fontSize="sm"
bg={complete ? "complete" : undefined}
>
{year}
</Badge>
</Flex>
);
}

type Props = {
iotms: RawIotM[];
};
const selectPlayerIotMs = createPlayerDataSelector("iotms");

export default function IotMs({ iotms: playerIotMs }: Props) {
const iotms = useSelector((state: RootState) => state.iotms);
const loading = useSelector((state: RootState) => state.loading.iotms || false);
export default function IotMs() {
const playerIotMs = useAppSelector(selectPlayerIotMs);
const iotms = useAppSelector((state) => state.iotms);
const loading = useAppSelector((state) => state.loading.iotms || false);

// Put together a map of item ids to item definitions for this Path
const idToItem = useItemMap(iotms.map((i) => i.id));
Expand Down
44 changes: 22 additions & 22 deletions packages/greenbox-web/src/components/MainPage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Accordion, Container, ToastId, useToast } from "@chakra-ui/react";
import { useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { NumberParam, StringParam, useQueryParam } from "use-query-params";

import { fetchAll, fetchPlayerData, loadPlayerData, RootState, store } from "../store";
import { useAppDispatch, useAppSelector } from "../hooks";
import { fetchAll, fetchPlayerData, loadPlayerData } from "../store";

import Familiars from "./Familiars";
import Header from "./Header";
Expand All @@ -16,9 +16,9 @@ import Trophies from "./Trophies";
export default function MainPage() {
const [directValue] = useQueryParam("d", StringParam);
const [playerId] = useQueryParam("u", NumberParam);
const favouritePlayerId = useSelector((state: RootState) => state.favouritePlayerId);
const favouritePlayerId = useAppSelector((state) => state.favouritePlayerId);

const dispatch = useDispatch<typeof store.dispatch>();
const dispatch = useAppDispatch();

useEffect(() => {
const id = playerId || favouritePlayerId;
Expand All @@ -36,10 +36,10 @@ export default function MainPage() {

const toast = useToast();
const clashToast = useRef<ToastId>();
const data = useSelector((state: RootState) => state.playerData);
const loading = useSelector((state: RootState) => state.loading);
const error = useSelector((state: RootState) => state.error);
const errorMessage = useSelector((state: RootState) => state.errorMessage);
const data = useAppSelector((state) => state.playerData);
const loading = useAppSelector((state) => state.loading);
const error = useAppSelector((state) => state.error);
const errorMessage = useAppSelector((state) => state.errorMessage);

const id = "clash-toast";

Expand Down Expand Up @@ -69,20 +69,20 @@ export default function MainPage() {

return (
<Container maxWidth="1000px" width="100%">
<Accordion allowMultiple defaultIndex={[0]}>
<Header
direct={!!directValue}
meta={data?.meta}
loading={loading.playerData}
error={error.playerData}
errorMessage={errorMessage.playerData}
/>
<IotMs iotms={data?.iotms ?? []} />
<Skills skills={data?.skills ?? []} />
<Paths paths={data?.paths ?? []} />
<Familiars familiars={data?.familiars ?? []} />
<Tattoos outfitTattoos={data?.outfitTattoos ?? []} />
<Trophies trophies={data?.trophies ?? []} />
<Header
direct={!!directValue}
meta={data?.meta}
loading={loading.playerData}
error={error.playerData}
errorMessage={errorMessage.playerData}
/>
<Accordion allowMultiple>
<IotMs />
<Skills />
<Paths />
<Familiars />
<Tattoos />
<Trophies />
</Accordion>
</Container>
);
Expand Down
6 changes: 2 additions & 4 deletions packages/greenbox-web/src/components/Path.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Badge, Box, Heading, Stack } from "@chakra-ui/react";
import { ItemDef, ItemStatus, PathDef } from "greenbox-data";
import { useSelector } from "react-redux";
import { ItemStatus, PathDef } from "greenbox-data";

import { RootState } from "../store";
import { useItemMap } from "../utils";
import { useItemMap } from "../hooks";

import AlphaImage from "./AlphaImage";
import ItemGrid from "./ItemGrid";
Expand Down
15 changes: 7 additions & 8 deletions packages/greenbox-web/src/components/Paths.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { Stack } from "@chakra-ui/react";
import { RawPath } from "greenbox-data";
import { useMemo } from "react";
import { useSelector } from "react-redux";

import { RootState } from "../store";
import { useAppSelector } from "../hooks";
import { createPlayerDataSelector } from "../store";

import Path from "./Path";
import Section from "./Section";

type Props = {
paths: RawPath[];
};
const selectPlayerPaths = createPlayerDataSelector("paths");

export default function Paths({ paths: playerPaths }: Props) {
const paths = useSelector((state: RootState) => state.paths);
const loading = useSelector((state: RootState) => state.loading.paths || false);
export default function Paths() {
const playerPaths = useAppSelector(selectPlayerPaths);
const paths = useAppSelector((state) => state.paths);
const loading = useAppSelector((state) => state.loading.paths || false);

// A map of path id to array, where the index represents a tattoo for that path
// and the value represents the maximum level for that tattoo
Expand Down
5 changes: 2 additions & 3 deletions packages/greenbox-web/src/components/SkillDescription.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Flex } from "@chakra-ui/react";
import { SkillDef } from "greenbox-data";
import { useEffect, useMemo, useRef, useState } from "react";
import { useSelector } from "react-redux";

import { RootState } from "../store";
import { useAppSelector } from "../hooks";
import { useColorModeFilter } from "../theme";

import Spinner from "./Spinner";
Expand All @@ -14,7 +13,7 @@ type Props = {
};

export default function SkillDescription({ skill }: Props) {
const clashes = useSelector((state: RootState) => state.wikiClashes);
const clashes = useAppSelector((state) => state.wikiClashes);
const wikiLink = useMemo(
() => guessWikiLink(undefined, skill.name, "skill", clashes),
[skill, clashes],
Expand Down
18 changes: 9 additions & 9 deletions packages/greenbox-web/src/components/Skills.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { SimpleGrid, Stack } from "@chakra-ui/react";
import { ClassDef, RawSkill, SkillDef, SkillStatus } from "greenbox-data";
import { useMemo } from "react";
import { useSelector } from "react-redux";

import { RootState } from "../store";
import { useAppSelector } from "../hooks";
import { createPlayerDataSelector } from "../store";
import { getSkillBucket } from "../utils";

import MutexSkills from "./MutexSkills";
import Section from "./Section";
import Skill from "./Skill";
import SkillClassHeading from "./SkillClassHeading";

type Props = {
skills: RawSkill[];
};
const selectPlayerSkills = createPlayerDataSelector("skills");

export default function Skills({ skills: playerSkills }: Props) {
const skills = useSelector((state: RootState) => state.skills.filter((s) => s.permable));
const classes = useSelector((state: RootState) => state.classes);
const loading = useSelector((state: RootState) => state.loading.skills || false);
export default function Skills() {
const playerSkills = useAppSelector(selectPlayerSkills);
const allSkills = useAppSelector((state) => state.skills);
const skills = useMemo(() => allSkills.filter((s) => s.permable), [allSkills]);
const classes = useAppSelector((state) => state.classes);
const loading = useAppSelector((state) => state.loading.skills || false);

const totalHardcorePermed = useMemo(
() => playerSkills.filter((s) => s[1] === SkillStatus.HARDCORE).length,
Expand Down
Loading

0 comments on commit 75eb241

Please sign in to comment.