builds renders

This commit is contained in:
Kalle (Sendou) 2020-11-11 16:40:21 +02:00
parent 528ec5e7e4
commit 8f6c7ea0e0
14 changed files with 720 additions and 20 deletions

View File

@ -0,0 +1,220 @@
import {
Box,
BoxProps,
Flex,
IconButton,
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
} from "@chakra-ui/core";
import { t, Trans } from "@lingui/macro";
import WeaponImage from "components/common/WeaponImage";
import { getEmojiFlag } from "countries-list";
import { Unpacked } from "lib/types";
import { useMyTheme } from "lib/useMyTheme";
import Image from "next/image";
import Link from "next/link";
import { GetBuildsByWeaponData } from "prisma/queries/getBuildsByWeapon";
import { useState } from "react";
import { FiBarChart2, FiInfo, FiTarget } from "react-icons/fi";
import Gears from "./Gears";
//import ViewAP from "./ViewAP";
import ViewSlots from "./ViewSlots";
interface BuildCardProps {
build: Unpacked<GetBuildsByWeaponData>;
canModify?: boolean;
//setBuildBeingEdited?: (build: Build) => void;
otherBuildCount?: number;
onShowAllByUser?: () => void;
}
const BuildCard: React.FC<BuildCardProps & BoxProps> = ({
build,
canModify,
//setBuildBeingEdited,
otherBuildCount,
onShowAllByUser,
...props
}) => {
const [apView, setApView] = useState(false);
const [showStats, setShowStats] = useState(false);
const { themeColor, secondaryBgColor, gray } = useMyTheme();
const username = build.user.username;
return (
<>
{/* {showStats && (
<BuildCardStats build={build} closeModal={() => setShowStats(false)} />
)} */}
<Box
w="300px"
rounded="lg"
overflow="hidden"
boxShadow="0px 0px 16px 6px rgba(0,0,0,0.1)"
bg={secondaryBgColor}
p="20px"
{...props}
>
<Box display="flex" flexDirection="column" h="100%">
<Box display="flex" justifyContent="space-between">
<Box width="24">
<WeaponImage name={build.weapon} size={64} />
</Box>
{build.top500 && (
<Image
src={`/layout/xsearch.png`}
alt="x rank top 500 logo"
height={40}
width={40}
title={t`Maker of the build has finished in the top 500 of X Rank with this weapon"`}
/>
)}
{build.jpn && getEmojiFlag("jp")}
</Box>
<Flex alignItems="center">
<Box
color={gray}
fontWeight="semibold"
letterSpacing="wide"
fontSize="xs"
ml="8px"
title={build.updatedAt.toLocaleString()}
>
{build.updatedAt.toLocaleDateString()}
</Box>
{build.user && (
<Box
style={{ textOverflow: "ellipsis" }}
color={gray}
fontWeight="semibold"
letterSpacing="wide"
fontSize="sm"
ml="0.25em"
whiteSpace="nowrap"
overflow="hidden"
title={`${build.user.username}#${build.user.discriminator}`}
>
{" "}
<Link href={`/u/${build.user.discordId}`}>
<a>
{build.user.username}#{build.user.discriminator}
</a>
</Link>
</Box>
)}
</Flex>
{build.title && (
<Box
ml="8px"
fontWeight="semibold"
as="h4"
lineHeight="tight"
mt="0.3em"
>
{build.title}
</Box>
)}
<Flex mt="0.3em">
<IconButton
variant="ghost"
isRound
colorScheme={themeColor}
onClick={() => setApView(!apView)}
aria-label="Set build card view"
fontSize="20px"
icon={<FiTarget />}
mr="0.5em"
/>
<IconButton
variant="ghost"
isRound
colorScheme={themeColor}
onClick={() => setShowStats(!showStats)}
aria-label="Show build stats view"
fontSize="20px"
icon={<FiBarChart2 />}
mr="0.5em"
/>
{build.description && (
<Popover placement="top">
<PopoverTrigger>
<IconButton
variant="ghost"
isRound
colorScheme={themeColor}
aria-label="Show description"
fontSize="20px"
icon={<FiInfo />}
/>
</PopoverTrigger>
<PopoverContent
zIndex={4}
width="220px"
backgroundColor={secondaryBgColor}
>
<PopoverBody textAlign="center" whiteSpace="pre-wrap">
{build.description}
</PopoverBody>
</PopoverContent>
</Popover>
)}
{/* {canModify && (
<IconButton
variant="ghost"
isRound
colorScheme={themeColor}
onClick={
setBuildBeingEdited && (() => setBuildBeingEdited(build))
}
aria-label="Show description"
fontSize="20px"
icon={<FiEdit />}
ml="0.5em"
/>
)} */}
</Flex>
<Box mt="1em">
<Gears build={build} />
</Box>
<Box
display="flex"
flexDirection="column"
flexGrow={1}
justifyContent="center"
mt="1em"
>
{/* {apView ? <ViewAP build={build} /> : <ViewSlots build={build} />} */}
<ViewSlots build={build} />
</Box>
<Box
display="flex"
justifyContent="space-between"
alignItems="center"
mt="1em"
>
{otherBuildCount && (
<Box
mx="auto"
fontSize="0.8em"
color={themeColor}
textAlign="center"
onClick={onShowAllByUser}
cursor="pointer"
_hover={{ textDecoration: "underline" }}
>
<Trans>
Show all {otherBuildCount} builds by {username}
</Trans>
</Box>
)}
</Box>
</Box>
</Box>
</>
);
};
export default BuildCard;

View File

@ -0,0 +1,30 @@
import { Box, Flex } from "@chakra-ui/core";
import GearImage from "components/common/GearImage";
import { Unpacked } from "lib/types";
import { GetBuildsByWeaponData } from "prisma/queries/getBuildsByWeapon";
import React from "react";
interface GearsProps {
build: Unpacked<GetBuildsByWeaponData>;
}
const Gears: React.FC<GearsProps> = ({ build }) => {
if (!build.headGear && !build.clothingGear && !build.shoesGear) {
return <Box h="30px" />;
}
return (
<Flex justifyContent="center">
<Box w={build.headGear ? "85px" : undefined} h="85px" mx="2px">
{build.headGear && <GearImage englishName={build.headGear} />}
</Box>
<Box w={build.clothingGear ? "85px" : undefined} h="85px" mx="2px">
{build.clothingGear && <GearImage englishName={build.clothingGear} />}
</Box>
<Box w={build.shoesGear ? "85px" : undefined} h="85px" mx="2px">
{build.shoesGear && <GearImage englishName={build.shoesGear} />}
</Box>
</Flex>
);
};
export default Gears;

View File

@ -0,0 +1,110 @@
// This while is bit of a mess from TS PoV - might be worth while to do it better later
import { Box, Flex } from "@chakra-ui/core";
import React, { useContext } from "react";
import { useTranslation } from "react-i18next";
import MyThemeContext from "../../themeContext";
import { Ability, Build } from "../../types";
import { mainOnlyAbilities } from "../../utils/lists";
import DividingBox from "../common/DividingBox";
import AbilityIcon from "./AbilityIcon";
interface ViewAPProps {
build: Build;
}
const ViewAP: React.FC<ViewAPProps> = ({ build }) => {
const { t } = useTranslation();
const { grayWithShade } = useContext(MyThemeContext);
const abilityArrays: Ability[][] = [
build.headgear,
build.clothing,
build.shoes,
];
const abilityToPoints: Partial<Record<Ability, number>> = {};
abilityArrays.forEach((arr) =>
arr.forEach((ability, index) => {
let abilityPoints = index === 0 ? 10 : 3;
if (mainOnlyAbilities.indexOf(ability as any) !== -1) abilityPoints = 999;
abilityToPoints[ability] = abilityToPoints.hasOwnProperty(ability)
? (abilityToPoints[ability] as any) + abilityPoints
: abilityPoints;
})
);
const pointsToAbilities: Record<string, Ability[]> = {};
(Object.keys(abilityToPoints) as Array<keyof typeof abilityToPoints>).forEach(
(ability: Ability) => {
const points = abilityToPoints[ability];
pointsToAbilities.hasOwnProperty(points as any)
? pointsToAbilities[points as any].push(ability)
: (pointsToAbilities[points as any] = [ability]);
}
);
const APArrays = (Object.keys(pointsToAbilities) as Array<
keyof typeof pointsToAbilities
>)
.map((points) => [points, pointsToAbilities[points as any]])
.sort((a1, a2) => parseInt(a2[0] as string) - parseInt(a1[0] as string));
let indexToPrintAPAt = APArrays[0][0] === "999" ? 1 : 0;
return (
<Box mt="2">
{APArrays.map((arr, index) => {
return (
<Flex
key={arr[0] as any}
justifyContent="flex-start"
alignItems="center"
gridRowGap="2em"
mt={index === 0 ? "0" : "1em"}
>
{arr[0] !== "999" ? (
<DividingBox location="right">
<Box
color={grayWithShade}
width="32px"
minH="45px"
letterSpacing="wide"
fontSize="s"
fontWeight="semibold"
textAlign="center"
pt={indexToPrintAPAt !== index ? "10px" : undefined}
>
{arr[0] as string}
{indexToPrintAPAt === index ? (
<>
<br />
{t("analyzer;abilityPointShort")}
</>
) : null}
</Box>
</DividingBox>
) : (
<Box width="37px" />
)}
{(arr[1] as Array<Ability>).map((ability, abilityIndex) => (
<Box
width="45px"
key={ability}
ml={
abilityIndex !== 0 && arr[1].length > 5
? `-${(arr[1].length - 5) * 5}px`
: undefined
}
>
<AbilityIcon ability={ability} size="SUB" />
</Box>
))}
</Flex>
);
})}
</Box>
);
};
export default ViewAP;

View File

@ -0,0 +1,83 @@
import { Box, BoxProps, Flex } from "@chakra-ui/core";
import AbilityIcon from "components/common/AbilityIcon";
import { Unpacked } from "lib/types";
import { GetBuildsByWeaponData } from "prisma/queries/getBuildsByWeapon";
interface ViewSlotsProps {
build: Unpacked<GetBuildsByWeaponData>;
onAbilityClick?: (gear: "HEAD" | "CLOTHING" | "SHOES", index: number) => void;
}
// FIXME: fix any
const defaultAbilityRow = ["UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN"] as any[];
const ViewSlots: React.FC<ViewSlotsProps & BoxProps> = ({
build,
onAbilityClick,
...props
}) => {
return (
<Box {...props}>
<Flex alignItems="center" justifyContent="center">
{(build.headAbilities ?? defaultAbilityRow).map((ability, index) => (
<Box
mx="3px"
key={index}
onClick={
onAbilityClick ? () => onAbilityClick("HEAD", index) : undefined
}
cursor={onAbilityClick ? "pointer" : undefined}
>
<AbilityIcon
key={index}
ability={ability}
size={index === 0 ? "MAIN" : "SUB"}
/>
</Box>
))}
</Flex>
<Flex alignItems="center" justifyContent="center" my="0.5em">
{(build.clothingAbilities ?? defaultAbilityRow).map(
(ability, index) => (
<Box
mx="3px"
key={index}
onClick={
onAbilityClick
? () => onAbilityClick("CLOTHING", index)
: undefined
}
cursor={onAbilityClick ? "pointer" : undefined}
>
<AbilityIcon
key={index}
ability={ability}
size={index === 0 ? "MAIN" : "SUB"}
/>
</Box>
)
)}
</Flex>
<Flex alignItems="center" justifyContent="center">
{(build.shoesAbilities ?? defaultAbilityRow).map((ability, index) => (
<Box
mx="3px"
key={index}
onClick={
onAbilityClick ? () => onAbilityClick("SHOES", index) : undefined
}
cursor={onAbilityClick ? "pointer" : undefined}
>
<AbilityIcon
key={index}
ability={ability}
size={index === 0 ? "MAIN" : "SUB"}
/>
</Box>
))}
</Flex>
</Box>
);
};
export default ViewSlots;

View File

@ -24,7 +24,20 @@ const Layout = ({ Component, pageProps }: AppProps) => {
<SWRConfig
value={{
fetcher: (resource, init) =>
fetch(resource, init).then((res) => res.json()),
fetch(resource, init).then(async (res) => {
let data = await res.json();
if (Array.isArray(data)) {
data.map((entry) => {
if (!entry.updatedAt) return entry;
entry.updatedAt = new Date(entry.updatedAt);
return entry;
});
}
return data;
}),
revalidateOnFocus: false,
revalidateOnReconnect: false,
onError: (error) => {

View File

@ -5,3 +5,5 @@ export type Unwrap<T> = T extends Promise<infer U>
: T extends (...args: any) => infer U
? U
: T;
export type Unpacked<T> = T extends (infer U)[] ? U : T;

View File

@ -1,14 +1,16 @@
import { t } from "@lingui/macro";
import BuildCard from "components/builds/BuildCard";
import Breadcrumbs from "components/common/Breadcrumbs";
import WeaponSelector from "components/common/WeaponSelector";
import { weaponToCode } from "lib/lists/weaponCodes";
import { GetBuildsByWeaponData } from "prisma/queries/getBuildsByWeapon";
import { useState } from "react";
import useSWR from "swr";
const BuildsPage = () => {
const [weapon, setWeapon] = useState("");
const { data } = useSWR(() => {
const { data = [] } = useSWR<GetBuildsByWeaponData>(() => {
if (!weapon) return null;
const key = weapon as keyof typeof weaponToCode;
@ -21,6 +23,9 @@ const BuildsPage = () => {
<>
<Breadcrumbs pages={[{ name: t`Builds` }]} />
<WeaponSelector value={weapon} onChange={setWeapon} excludeAlt isHeader />
{data.map((build) => (
<BuildCard build={build} />
))}
</>
);
};

View File

@ -0,0 +1,72 @@
# Migration `20201111140528-init`
This migration has been generated by Kalle (Sendou) at 11/11/2020, 4:05:28 PM.
You can check out the [state of the schema](./schema.prisma) after the migration.
## Database Steps
```sql
ALTER TABLE "Build" DROP COLUMN "mainAbilities",
DROP COLUMN "subAbilities",
ADD COLUMN "headAbilities" "Ability"[],
ADD COLUMN "clothingAbilities" "Ability"[],
ADD COLUMN "shoesAbilities" "Ability"[]
```
## Changes
```diff
diff --git schema.prisma schema.prisma
migration 20201109140409-init..20201111140528-init
--- datamodel.dml
+++ datamodel.dml
@@ -1,8 +1,8 @@
datasource db {
provider = "postgresql"
// FIXME: should use same .env system as Next.JS
- url = "***"
+ url = "***"
}
generator client {
provider = "prisma-client-js"
@@ -93,23 +93,24 @@
BRU
}
model Build {
- id Int @id @default(autoincrement())
- userId Int
- weapon String
- title String?
- description String?
- mainAbilities Ability[]
- subAbilities Ability[]
- abilityPoints Json
- headGear String?
- clothingGear String?
- shoesGear String?
- top500 Boolean
- jpn Boolean
- updatedAt DateTime @updatedAt
- user User @relation(fields: [userId], references: [id])
+ id Int @id @default(autoincrement())
+ userId Int
+ weapon String
+ title String?
+ description String?
+ abilityPoints Json
+ headGear String?
+ headAbilities Ability[]
+ clothingGear String?
+ clothingAbilities Ability[]
+ shoesGear String?
+ shoesAbilities Ability[]
+ top500 Boolean
+ jpn Boolean
+ updatedAt DateTime @updatedAt
+ user User @relation(fields: [userId], references: [id])
@@index(weapon)
@@index(userId)
@@index(abilityPoints)
```

View File

@ -0,0 +1,117 @@
datasource db {
provider = "postgresql"
// FIXME: should use same .env system as Next.JS
url = "***"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
username String
discriminator String
discordId String @unique
discordAvatar String?
profile Profile?
player Player?
Build Build[]
}
model Profile {
twitterName String?
twitchName String?
youtubeId String?
country String?
sensMotion Float?
sensStick Float?
bio String?
weaponPool String[]
customUrlPath String? @unique
user User @relation(fields: [userId], references: [id])
userId Int @unique
}
enum RankedMode {
SZ
TC
RM
CB
}
model XRankPlacement {
id Int @id @default(autoincrement())
switchAccountId String
player Player @relation(fields: [switchAccountId], references: [switchAccountId])
playerName String
ranking Int
xPower Float
weapon String
mode RankedMode
month Int
year Int
@@unique([switchAccountId, mode, month, year])
}
model Player {
switchAccountId String @unique
userId Int? @unique
name String
placements XRankPlacement[]
user User? @relation(fields: [userId], references: [id])
}
enum Ability {
CB
LDE
OG
T
H
NS
TI
RP
AD
DR
SJ
OS
BDU
REC
RES
ISM
ISS
MPU
QR
QSJ
RSU
SSU
SCU
SPU
SS
BRU
}
model Build {
id Int @id @default(autoincrement())
userId Int
weapon String
title String?
description String?
abilityPoints Json
headGear String?
headAbilities Ability[]
clothingGear String?
clothingAbilities Ability[]
shoesGear String?
shoesAbilities Ability[]
top500 Boolean
jpn Boolean
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@index(weapon)
@@index(userId)
@@index(abilityPoints)
}

View File

@ -0,0 +1,36 @@
{
"version": "0.3.14-fixed",
"steps": [
{
"tag": "CreateField",
"model": "Build",
"field": "headAbilities",
"type": "Ability",
"arity": "List"
},
{
"tag": "CreateField",
"model": "Build",
"field": "clothingAbilities",
"type": "Ability",
"arity": "List"
},
{
"tag": "CreateField",
"model": "Build",
"field": "shoesAbilities",
"type": "Ability",
"arity": "List"
},
{
"tag": "DeleteField",
"model": "Build",
"field": "mainAbilities"
},
{
"tag": "DeleteField",
"model": "Build",
"field": "subAbilities"
}
]
}

View File

@ -2,4 +2,5 @@
20201103144752-init
20201109002900-init
20201109140409-init
20201109140409-init
20201111140528-init

View File

@ -15,5 +15,14 @@ export const getBuildsByWeapon = async ({
return prisma.build.findMany({
where: { weapon },
orderBy: [{ top500: "desc" }, { jpn: "desc" }, { updatedAt: "desc" }],
include: {
user: {
select: {
username: true,
discriminator: true,
discordId: true,
},
},
},
});
};

View File

@ -94,21 +94,22 @@ enum Ability {
}
model Build {
id Int @id @default(autoincrement())
userId Int
weapon String
title String?
description String?
mainAbilities Ability[]
subAbilities Ability[]
abilityPoints Json
headGear String?
clothingGear String?
shoesGear String?
top500 Boolean
jpn Boolean
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
id Int @id @default(autoincrement())
userId Int
weapon String
title String?
description String?
abilityPoints Json
headGear String?
headAbilities Ability[]
clothingGear String?
clothingAbilities Ability[]
shoesGear String?
shoesAbilities Ability[]
top500 Boolean
jpn Boolean
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@index(weapon)
@@index(userId)

View File

@ -118,8 +118,9 @@ const main = async () => {
},
},
weapon: "Splattershot Jr.",
mainAbilities: ["SS", "SS", "SS"],
subAbilities: ["SS", "SS", "SS", "SS", "SS", "SS", "SS", "SS", "SS"],
headAbilities: ["SS", "SS", "SS", "SS"],
clothingAbilities: ["SS", "SS", "SS", "SS"],
shoesAbilities: ["SS", "SS", "SS", "SS"],
title: "Amazing test build",
description: "Just testing.",
top500: true,