Merge branch 'waka-ui' into main
This commit is contained in:
commit
b80149440d
5 changed files with 145 additions and 38 deletions
|
@ -14,9 +14,9 @@ import connectToDb from "../src/server/db";
|
|||
async function handle(req, res) {
|
||||
const allNcItemNamesAndIdsPromise = loadAllNcItemNamesAndIds();
|
||||
|
||||
let itemValuesByName;
|
||||
let itemValuesByIdOrName;
|
||||
try {
|
||||
itemValuesByName = await loadWakaValuesByName();
|
||||
itemValuesByIdOrName = await loadWakaValuesByIdOrName();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.setHeader("Content-Type", "text/plain");
|
||||
|
@ -28,8 +28,10 @@ async function handle(req, res) {
|
|||
const allNcItemNamesAndIds = await allNcItemNamesAndIdsPromise;
|
||||
const itemValues = {};
|
||||
for (const { name, id } of allNcItemNamesAndIds) {
|
||||
if (name in itemValuesByName) {
|
||||
itemValues[id] = itemValuesByName[name];
|
||||
if (id in itemValuesByIdOrName) {
|
||||
itemValues[id] = itemValuesByIdOrName[id];
|
||||
} else if (name in itemValuesByIdOrName) {
|
||||
itemValues[id] = itemValuesByIdOrName[name];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,10 +58,15 @@ async function loadAllNcItemNamesAndIds() {
|
|||
AND item_translations.locale = "en"
|
||||
`);
|
||||
|
||||
return rows;
|
||||
return rows.map(({ id, name }) => ({ id, name: normalizeItemName(name) }));
|
||||
}
|
||||
|
||||
async function loadWakaValuesByName() {
|
||||
/**
|
||||
* Load all Waka values from the spreadsheet. Returns an object keyed by ID or
|
||||
* name - that is, if the item ID is provided in the sheet, we use that as the
|
||||
* key; or if not, we use the name as the key.
|
||||
*/
|
||||
async function loadWakaValuesByIdOrName() {
|
||||
if (!process.env["GOOGLE_API_KEY"]) {
|
||||
throw new Error(`GOOGLE_API_KEY environment variable must be provided`);
|
||||
}
|
||||
|
@ -92,14 +99,35 @@ async function loadWakaValuesByName() {
|
|||
// the spreadsheet columns that we don't use on DTI, like Notes.
|
||||
//
|
||||
// NOTE: The Sheets API only returns the first non-empty cells of the row.
|
||||
// So, when there's no value specified, it only returns one cell.
|
||||
// That's why we set `""` as the default `value`.
|
||||
const itemValuesByName = {};
|
||||
for (const [itemName, value = ""] of rows) {
|
||||
itemValuesByName[itemName] = { value };
|
||||
// That's why we set `""` as the defaults, in case the value/notes/etc
|
||||
// aren't provided.
|
||||
const itemValuesByIdOrName = {};
|
||||
for (const [
|
||||
itemName,
|
||||
value = "",
|
||||
notes = "",
|
||||
marks = "",
|
||||
itemId = "",
|
||||
] of rows) {
|
||||
const normalizedItemName = normalizeItemName(itemName);
|
||||
itemValuesByIdOrName[itemId || normalizedItemName] = { value };
|
||||
}
|
||||
|
||||
return itemValuesByName;
|
||||
return itemValuesByIdOrName;
|
||||
}
|
||||
|
||||
function normalizeItemName(name) {
|
||||
return (
|
||||
name
|
||||
// Remove all spaces, they're a common source of inconsistency
|
||||
.replace(/\s+/g, "")
|
||||
// Lower case, because capitalization is another common source
|
||||
.toLowerCase()
|
||||
// Remove diacritics: https://stackoverflow.com/a/37511463/107415
|
||||
// Waka has some stray ones in item names, not sure why!
|
||||
.normalize("NFD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
);
|
||||
}
|
||||
|
||||
export default async (req, res) => {
|
||||
|
|
|
@ -74,6 +74,7 @@ export function ItemPageContent({ itemId, isEmbedded }) {
|
|||
thumbnailUrl
|
||||
description
|
||||
createdAt
|
||||
wakaValueText
|
||||
|
||||
# For Support users.
|
||||
rarityIndex
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
Flex,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Portal,
|
||||
|
@ -15,7 +16,11 @@ import {
|
|||
useToast,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { ExternalLinkIcon, ChevronRightIcon } from "@chakra-ui/icons";
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
ChevronRightIcon,
|
||||
QuestionIcon,
|
||||
} from "@chakra-ui/icons";
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
|
||||
import {
|
||||
|
@ -150,6 +155,42 @@ function ItemPageBadges({ item, isEmbedded }) {
|
|||
Jellyneo
|
||||
</LinkBadge>
|
||||
</SubtleSkeleton>
|
||||
{item.isNc && (
|
||||
<SubtleSkeleton
|
||||
isLoaded={
|
||||
// Distinguish between undefined (still loading) and null (loaded and
|
||||
// empty).
|
||||
item.wakaValueText !== undefined
|
||||
}
|
||||
>
|
||||
{item.wakaValueText && (
|
||||
<>
|
||||
{/* For hover-y devices, use a hover popover over the badge. */}
|
||||
<Box sx={{ "@media (hover: none)": { display: "none" } }}>
|
||||
<WakaPopover trigger="hover">
|
||||
<LinkBadge href="http://www.neopets.com/~waka">
|
||||
Waka: {item.wakaValueText}
|
||||
</LinkBadge>
|
||||
</WakaPopover>
|
||||
</Box>
|
||||
{/* For touch-y devices, use a tappable help icon. */}
|
||||
<Flex
|
||||
sx={{ "@media (hover: hover)": { display: "none" } }}
|
||||
align="center"
|
||||
>
|
||||
<LinkBadge href="http://www.neopets.com/~waka">
|
||||
Waka: {item.wakaValueText}
|
||||
</LinkBadge>
|
||||
<WakaPopover>
|
||||
<Flex align="center" fontSize="sm" paddingX="2" tabIndex="0">
|
||||
<QuestionIcon />
|
||||
</Flex>
|
||||
</WakaPopover>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</SubtleSkeleton>
|
||||
)}
|
||||
<SubtleSkeleton isLoaded={searchBadgesAreLoaded}>
|
||||
{!item?.isNc && !item?.isPb && (
|
||||
<LinkBadge
|
||||
|
@ -317,27 +358,36 @@ function ItemKindBadgeWithSupportTools({ item }) {
|
|||
return <ItemKindBadge isNc={item.isNc} isPb={item.isPb} />;
|
||||
}
|
||||
|
||||
function LinkBadge({ children, href, isEmbedded }) {
|
||||
return (
|
||||
<Badge
|
||||
as="a"
|
||||
href={href}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
// Normally we want to act like a normal webpage, and treat links as
|
||||
// normal. But when we're on the wardrobe page, we want to avoid
|
||||
// disrupting the outfit, and open in a new window instead.
|
||||
target={isEmbedded ? "_blank" : undefined}
|
||||
>
|
||||
{children}
|
||||
{
|
||||
// We also change the icon to signal whether this will launch in a new
|
||||
// window or not!
|
||||
isEmbedded ? <ExternalLinkIcon marginLeft="1" /> : <ChevronRightIcon />
|
||||
}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
const LinkBadge = React.forwardRef(
|
||||
({ children, href, isEmbedded, ...props }, ref) => {
|
||||
return (
|
||||
<Badge
|
||||
ref={ref}
|
||||
as="a"
|
||||
href={href}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
// Normally we want to act like a normal webpage, and treat links as
|
||||
// normal. But when we're on the wardrobe page, we want to avoid
|
||||
// disrupting the outfit, and open in a new window instead.
|
||||
target={isEmbedded ? "_blank" : undefined}
|
||||
_focus={{ outline: "none", boxShadow: "outline" }}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{
|
||||
// We also change the icon to signal whether this will launch in a new
|
||||
// window or not!
|
||||
isEmbedded ? (
|
||||
<ExternalLinkIcon marginLeft="1" />
|
||||
) : (
|
||||
<ChevronRightIcon />
|
||||
)
|
||||
}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const fullDateFormatter = new Intl.DateTimeFormat("en-US", {
|
||||
dateStyle: "long",
|
||||
|
@ -380,4 +430,30 @@ function ShortTimestamp({ when }) {
|
|||
);
|
||||
}
|
||||
|
||||
function WakaPopover({ children, ...props }) {
|
||||
return (
|
||||
<Popover placement="bottom" {...props}>
|
||||
<PopoverTrigger>{children}</PopoverTrigger>
|
||||
<Portal>
|
||||
<PopoverContent>
|
||||
<PopoverArrow />
|
||||
<PopoverBody fontSize="sm">
|
||||
<p>
|
||||
Waka is a community resource that tracks the approximate value of
|
||||
NC items, based on real-world trades.
|
||||
</p>
|
||||
<Box height="1em" />
|
||||
<p>
|
||||
The Waka Team aims to maintain the accuracy of the guide as fully
|
||||
as possible, but please remember values are often changing and
|
||||
with certain difficult-to-find or popular items it doesn't hurt to
|
||||
make a value check!
|
||||
</p>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default ItemPageLayout;
|
||||
|
|
|
@ -102,6 +102,7 @@ function ItemTradesPage({
|
|||
thumbnailUrl
|
||||
description
|
||||
createdAt
|
||||
wakaValueText
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
|
|
@ -608,10 +608,11 @@ const buildItemWakaValueLoader = () =>
|
|||
// API would, and avoid putting pressure on our Google Sheets API quotas.
|
||||
// (Some kind of internal memcache or process-level cache would be a more
|
||||
// idiomatic solution in a monolith server environment!)
|
||||
const url =
|
||||
process.env.NODE_ENV === "production"
|
||||
? "https://impress-2020.openneo.net/api/allWakaValues"
|
||||
: "http://localhost:3000/api/allWakaValues";
|
||||
const url = process.env.VERCEL_URL
|
||||
? `https://${process.env.VERCEL_URL}/api/allWakaValues`
|
||||
: process.env.NODE_ENV === "production"
|
||||
? "https://impress-2020.openneo.net/api/allWakaValues"
|
||||
: "http://localhost:3000/api/allWakaValues";
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
|
|
Loading…
Reference in a new issue