Optimize Search results rendering, replace favicon

main
Kevin Belisle 2021-07-08 23:10:04 -04:00
parent 6e74b0fe8d
commit f6e6b90d8d
9 changed files with 128 additions and 74 deletions

32
spa/package-lock.json generated
View File

@ -4777,6 +4777,11 @@
"wrap-ansi": "^6.2.0" "wrap-ansi": "^6.2.0"
} }
}, },
"clsx": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
},
"co": { "co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -5789,6 +5794,15 @@
"utila": "~0.4" "utila": "~0.4"
} }
}, },
"dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"requires": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"dom-serializer": { "dom-serializer": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
@ -13008,6 +13022,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-query": { "react-query": {
"version": "3.18.1", "version": "3.18.1",
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.18.1.tgz", "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.18.1.tgz",
@ -13166,6 +13185,19 @@
"tslib": "^1.0.0" "tslib": "^1.0.0"
} }
}, },
"react-virtualized": {
"version": "9.22.3",
"resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.3.tgz",
"integrity": "sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw==",
"requires": {
"@babel/runtime": "^7.7.2",
"clsx": "^1.0.4",
"dom-helpers": "^5.1.3",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-lifecycles-compat": "^3.0.4"
}
},
"read-pkg": { "read-pkg": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",

View File

@ -18,7 +18,8 @@
"react-icons": "^4.2.0", "react-icons": "^4.2.0",
"react-query": "^3.18.1", "react-query": "^3.18.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "4.0.3" "react-scripts": "4.0.3",
"react-virtualized": "^9.22.3"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -4,12 +4,14 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#FFFFFF" />
<!--
<meta <meta
name="description" name="description"
content="Web site created using create-react-app" content="Web site created using create-react-app"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
-->
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.
@ -19,7 +21,7 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>React App</title> <title>Stonks!</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import axios from "axios"; import axios from "axios";
import { BrowserRouter, Switch, Redirect, Route } from "react-router-dom"; import { BrowserRouter, Switch, Route } from "react-router-dom";
import { Container } from "@chakra-ui/react"; import { Container } from "@chakra-ui/react";
import Search from "./Search"; import Search from "./Search";

View File

@ -1,23 +1,17 @@
import React, { useMemo, useState } from "react"; import React from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import axios from "axios"; import axios from "axios";
import { import {
Button,
Center, Center,
Flex,
Heading, Heading,
Icon,
Image, Image,
Table, Table,
Thead, Thead,
Tbody, Tbody,
Tfoot,
Tr, Tr,
Th, Th,
Td, Td,
TableCaption,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { FaUserCircle, FaArrowAltCircleRight } from "react-icons/fa";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
const Player = ({ playerId, players }) => { const Player = ({ playerId, players }) => {
@ -33,9 +27,7 @@ const Player = ({ playerId, players }) => {
} }
); );
const playerDict = useMemo(() => { const playerName = players.data.find((x) => x.id === playerId).name;
return Object.assign({}, ...players.data.map((x) => ({ [x.id]: x.name })));
}, players);
return ( return (
<> <>
@ -55,9 +47,9 @@ const Player = ({ playerId, players }) => {
src={`https://minotar.net/avatar/${playerId.replaceAll("-", "")}/48`} src={`https://minotar.net/avatar/${playerId.replaceAll("-", "")}/48`}
mr="4" mr="4"
/> />
{playerDict[playerId]} {playerName}
</Heading> </Heading>
<Table variant="simple" size="sm" variant="striped"> <Table size="sm" variant="striped">
<Thead> <Thead>
<Tr> <Tr>
<Th>Statistic</Th> <Th>Statistic</Th>

View File

@ -1,9 +1,10 @@
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import { Button, Center, Flex, Heading, Icon, Input } from "@chakra-ui/react"; import { Button, Center, Heading, Icon, Input } from "@chakra-ui/react";
import { FaUserCircle, FaArrowAltCircleRight } from "react-icons/fa"; import { FaUserCircle, FaArrowAltCircleRight } from "react-icons/fa";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { AutoSizer, WindowScroller, List } from "react-virtualized";
const Search = ({ statistics, players }) => { const Search = ({ statistics, players }) => {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
@ -39,54 +40,86 @@ const Search = ({ statistics, players }) => {
return searchEngine.search(`'"${searchTerm}"'`); return searchEngine.search(`'"${searchTerm}"'`);
}, [searchEngine, searchTerm]); }, [searchEngine, searchTerm]);
const renderRow = ({
index, // Index of row
isScrolling, // The List is currently being scrolled
isVisible, // This row is visible within the List (eg it is not an overscanned row)
key, // Unique key within array of rendered rows
parent, // Reference to the parent List (instance)
style, // Style object to be applied to row (to position it);
// This must be passed through to the rendered row element.
}) => {
const x = searchResults[index];
return (
<Link
key={
x.item.type === "player"
? `/${x.item.type}/${x.item.value.id}`
: `/${x.item.type}/${x.item.value.type}/${x.item.value.name}`
}
to={
x.item.type === "player"
? `/${x.item.type}/${x.item.value.id}`
: `/${x.item.type}/${x.item.value.type}/${x.item.value.name}`
}
style={style}
>
<Button
size="sm"
leftIcon={
x.item.type === "player" ? (
<Icon as={FaUserCircle} />
) : (
<Icon as={FaArrowAltCircleRight} transform="rotate(-45deg)" />
)
}
variant="ghost"
my="0.5"
display="block"
textAlign="left"
width="100%"
>
{x.item.searchTerm}
</Button>
</Link>
);
};
return ( return (
<> <WindowScroller>
<Heading as="h1" size="4xl" my="8"> {({ height, isScrolling, onChildScroll, scrollTop, registerChild }) => (
<Center>📈</Center> <>
</Heading> <Heading as="h1" size="4xl" my="8">
<Input <Center>📈</Center>
placeholder="Find statistics or players" </Heading>
size="lg" <Input
variant="filled" placeholder="Find statistics or players"
value={searchTerm} size="lg"
onChange={(event) => setSearchTerm(event.target.value)} variant="filled"
/> value={searchTerm}
<Flex direction="column" align="stretch" my="4"> onChange={(event) => setSearchTerm(event.target.value)}
{searchResults.map((x, i) => { />
return ( <div ref={registerChild}>
<Link <AutoSizer disableHeight>
to={ {({ width }) => (
x.item.type === "player" <List
? `/${x.item.type}/${x.item.value.id}` autoHeight
: `/${x.item.type}/${x.item.value.type}/${x.item.value.name}` height={height}
} isScrolling={isScrolling}
> onScroll={onChildScroll}
<Button scrollTop={scrollTop}
key={i} rowCount={searchResults.length}
size="sm" rowHeight={36}
leftIcon={ rowRenderer={renderRow}
x.item.type === "player" ? ( width={width}
<Icon as={FaUserCircle} /> />
) : ( )}
<Icon </AutoSizer>
as={FaArrowAltCircleRight} </div>
transform="rotate(-45deg)" </>
/> )}
) </WindowScroller>
}
variant="ghost"
my="0.5"
display="block"
textAlign="left"
width="100%"
>
{x.item.searchTerm}
</Button>
</Link>
);
})}
</Flex>
</>
); );
}; };

View File

@ -1,23 +1,16 @@
import React, { useMemo, useState } from "react"; import React, { useMemo } from "react";
import { useQuery } from "react-query"; import { useQuery } from "react-query";
import axios from "axios"; import axios from "axios";
import { import {
Button,
Center, Center,
Flex,
Heading, Heading,
Icon,
Input,
Table, Table,
Thead, Thead,
Tbody, Tbody,
Tfoot,
Tr, Tr,
Th, Th,
Td, Td,
TableCaption,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { FaUserCircle, FaArrowAltCircleRight } from "react-icons/fa";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
const Statistic = ({ type, name, players }) => { const Statistic = ({ type, name, players }) => {
@ -34,7 +27,7 @@ const Statistic = ({ type, name, players }) => {
const playerDict = useMemo(() => { const playerDict = useMemo(() => {
return Object.assign({}, ...players.data.map((x) => ({ [x.id]: x.name }))); return Object.assign({}, ...players.data.map((x) => ({ [x.id]: x.name })));
}, players); }, [players]);
return ( return (
<> <>
@ -49,7 +42,7 @@ const Statistic = ({ type, name, players }) => {
<Heading as="h1" size="xl" mb="8"> <Heading as="h1" size="xl" mb="8">
{type} {name} {type} {name}
</Heading> </Heading>
<Table variant="simple" size="sm" variant="striped"> <Table size="sm" variant="striped">
<Thead> <Thead>
<Tr> <Tr>
<Th>Player</Th> <Th>Player</Th>

View File

@ -6,6 +6,7 @@ import { QueryClient, QueryClientProvider } from "react-query";
import App from "./App.js"; import App from "./App.js";
import "./index.css"; import "./index.css";
import "react-virtualized/styles.css";
const queryClient = new QueryClient(); const queryClient = new QueryClient();