build cards including ap view
BIN
frontend-react/src/assets/abilityIcons/AD.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
frontend-react/src/assets/abilityIcons/BDU.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
frontend-react/src/assets/abilityIcons/BRU.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
frontend-react/src/assets/abilityIcons/CB.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
frontend-react/src/assets/abilityIcons/DR.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
frontend-react/src/assets/abilityIcons/EMPTY.png
Normal file
|
After Width: | Height: | Size: 289 B |
BIN
frontend-react/src/assets/abilityIcons/H.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
frontend-react/src/assets/abilityIcons/ISM.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
frontend-react/src/assets/abilityIcons/ISS.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
frontend-react/src/assets/abilityIcons/LDE.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
frontend-react/src/assets/abilityIcons/MPU.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
frontend-react/src/assets/abilityIcons/NS.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
frontend-react/src/assets/abilityIcons/OG.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
frontend-react/src/assets/abilityIcons/OS.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
frontend-react/src/assets/abilityIcons/QR.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
frontend-react/src/assets/abilityIcons/QSJ.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
frontend-react/src/assets/abilityIcons/REC.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
frontend-react/src/assets/abilityIcons/RES.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
frontend-react/src/assets/abilityIcons/RP.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
frontend-react/src/assets/abilityIcons/RSU.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
frontend-react/src/assets/abilityIcons/SCU.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
frontend-react/src/assets/abilityIcons/SJ.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
frontend-react/src/assets/abilityIcons/SPU.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
frontend-react/src/assets/abilityIcons/SS.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
frontend-react/src/assets/abilityIcons/SSU.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
frontend-react/src/assets/abilityIcons/T.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
frontend-react/src/assets/abilityIcons/TI.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
frontend-react/src/assets/abilityIcons/UNKNOWN.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
@@ -1,3 +1,32 @@
|
||||
import BDU from "./abilityIcons/BDU.png"
|
||||
import BRU from "./abilityIcons/BRU.png"
|
||||
import CB from "./abilityIcons/CB.png"
|
||||
import DR from "./abilityIcons/DR.png"
|
||||
import H from "./abilityIcons/H.png"
|
||||
import ISM from "./abilityIcons/ISM.png"
|
||||
import ISS from "./abilityIcons/ISS.png"
|
||||
import LDE from "./abilityIcons/LDE.png"
|
||||
import MPU from "./abilityIcons/MPU.png"
|
||||
import NS from "./abilityIcons/NS.png"
|
||||
import OG from "./abilityIcons/OG.png"
|
||||
import QR from "./abilityIcons/QR.png"
|
||||
import QSJ from "./abilityIcons/QSJ.png"
|
||||
import REC from "./abilityIcons/REC.png"
|
||||
import RES from "./abilityIcons/RES.png"
|
||||
import RP from "./abilityIcons/RP.png"
|
||||
import RSU from "./abilityIcons/RSU.png"
|
||||
import SCU from "./abilityIcons/SCU.png"
|
||||
import SJ from "./abilityIcons/SJ.png"
|
||||
import SPU from "./abilityIcons/SPU.png"
|
||||
import SS from "./abilityIcons/SS.png"
|
||||
import SSU from "./abilityIcons/SSU.png"
|
||||
import T from "./abilityIcons/T.png"
|
||||
import TI from "./abilityIcons/TI.png"
|
||||
import OS from "./abilityIcons/OS.png"
|
||||
import AD from "./abilityIcons/AD.png"
|
||||
import UNKNOWN from "./abilityIcons/UNKNOWN.png"
|
||||
import * as top500logo from "./top500.png"
|
||||
|
||||
//https://stackoverflow.com/questions/42118296/dynamically-import-images-from-a-directory-using-webpack
|
||||
export const wpnSmall: object = importAll(require.context("./wpnSmall", false))
|
||||
export const wpnMedium: object = importAll(
|
||||
@@ -11,3 +40,36 @@ function importAll(r: __WebpackModuleApi.RequireContext) {
|
||||
})
|
||||
return images
|
||||
}
|
||||
|
||||
export const abilityIcons = {
|
||||
BDU: BDU,
|
||||
BRU: BRU,
|
||||
CB: CB,
|
||||
DR: DR,
|
||||
H: H,
|
||||
ISM: ISM,
|
||||
ISS: ISS,
|
||||
LDE: LDE,
|
||||
MPU: MPU,
|
||||
NS: NS,
|
||||
OG: OG,
|
||||
QR: QR,
|
||||
QSJ: QSJ,
|
||||
REC: REC,
|
||||
RES: RES,
|
||||
RP: RP,
|
||||
RSU: RSU,
|
||||
SCU: SCU,
|
||||
SJ: SJ,
|
||||
SPU: SPU,
|
||||
SS: SS,
|
||||
SSU: SSU,
|
||||
T: T,
|
||||
TI: TI,
|
||||
OS: OS,
|
||||
AD: AD,
|
||||
UNKNOWN: UNKNOWN,
|
||||
"": UNKNOWN,
|
||||
} as const
|
||||
|
||||
export const top500 = top500logo
|
||||
|
||||
BIN
frontend-react/src/assets/top500.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
58
frontend-react/src/components/builds/AbilityIcon.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from "react"
|
||||
import {
|
||||
StackableAbility,
|
||||
HeadOnlyAbility,
|
||||
ClothingOnlyAbility,
|
||||
ShoesOnlyAbility,
|
||||
} from "../../types"
|
||||
import { abilityIcons } from "../../assets/imageImports"
|
||||
|
||||
const mainAbilityStyle = {
|
||||
//https://github.com/loadout-ink/splat2-calc
|
||||
zIndex: 2,
|
||||
borderRadius: "50%",
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
background: "#000",
|
||||
border: "2px solid #888",
|
||||
borderRight: "0px",
|
||||
borderBottom: "0px",
|
||||
backgroundSize: "100%",
|
||||
boxShadow: "0 0 0 1px #000",
|
||||
userSelect: "none",
|
||||
} as const
|
||||
const subAbilityStyle = {
|
||||
//https://github.com/loadout-ink/splat2-calc
|
||||
zIndex: 2,
|
||||
borderRadius: "50%",
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
background: "#000",
|
||||
border: "2px solid #888",
|
||||
borderRight: "0px",
|
||||
borderBottom: "0px",
|
||||
backgroundSize: "100%",
|
||||
boxShadow: "0 0 0 1px #000",
|
||||
userSelect: "none",
|
||||
} as const
|
||||
|
||||
interface AbilityIconProps {
|
||||
ability:
|
||||
| StackableAbility
|
||||
| HeadOnlyAbility
|
||||
| ClothingOnlyAbility
|
||||
| ShoesOnlyAbility
|
||||
size: "MAIN" | "SUB"
|
||||
}
|
||||
|
||||
const AbilityIcon: React.FC<AbilityIconProps> = ({ ability, size }) => {
|
||||
return (
|
||||
<img
|
||||
src={abilityIcons[ability]}
|
||||
style={size === "MAIN" ? mainAbilityStyle : subAbilityStyle}
|
||||
alt={ability}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default AbilityIcon
|
||||
@@ -1,13 +1,108 @@
|
||||
import React from "react"
|
||||
import React, { useState } from "react"
|
||||
import { Build } from "../../types"
|
||||
import { Box } from "@chakra-ui/core"
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Image,
|
||||
IconButton,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverBody,
|
||||
} from "@chakra-ui/core"
|
||||
import useTheme from "../../hooks/useTheme"
|
||||
import WeaponImage from "../common/WeaponImage"
|
||||
import { top500 } from "../../assets/imageImports"
|
||||
import { FaInfo, FaPlus, FaMinus } from "react-icons/fa"
|
||||
import ViewGear from "./ViewGear"
|
||||
import ViewAP from "./ViewAP"
|
||||
|
||||
interface BuildCardProps {
|
||||
build: Build
|
||||
defaultToAPView: Boolean
|
||||
}
|
||||
|
||||
const BuildCard: React.FC<BuildCardProps> = ({ build }) => {
|
||||
return <Box>asd</Box>
|
||||
const BuildCard: React.FC<BuildCardProps> = ({ build, defaultToAPView }) => {
|
||||
const [apView, setApView] = useState(defaultToAPView)
|
||||
const { borderStyle, themeColor, darkerBgColor, grayWithShade } = useTheme()
|
||||
|
||||
return (
|
||||
<Box
|
||||
width="325px"
|
||||
borderWidth="1px"
|
||||
border={borderStyle}
|
||||
rounded="lg"
|
||||
overflow="hidden"
|
||||
pt="2"
|
||||
pb="6"
|
||||
px="6"
|
||||
>
|
||||
<Flex justifyContent="space-between">
|
||||
<Box width="24" height="auto">
|
||||
<WeaponImage englishName={build.weapon} size="MEDIUM" />
|
||||
</Box>
|
||||
{build.top && (
|
||||
<Image
|
||||
src={top500}
|
||||
alt="x rank top 500 logo"
|
||||
height="50px"
|
||||
width="auto"
|
||||
title="Maker of the build has reached top 500 with this weapon"
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<Box
|
||||
color={grayWithShade}
|
||||
fontWeight="semibold"
|
||||
letterSpacing="wide"
|
||||
fontSize="xs"
|
||||
>
|
||||
{new Date(parseInt(build.updatedAt)).toLocaleString()}
|
||||
</Box>
|
||||
{build.title && (
|
||||
<Box mt="1" fontWeight="semibold" as="h4" lineHeight="tight">
|
||||
{build.title}
|
||||
</Box>
|
||||
)}
|
||||
{apView ? <ViewAP build={build} /> : <ViewGear build={build} />}
|
||||
<Flex justifyContent="space-between" mt="1em">
|
||||
{build.description ? (
|
||||
<Popover placement="top">
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
isRound
|
||||
variantColor={themeColor}
|
||||
aria-label="Show description"
|
||||
fontSize="20px"
|
||||
icon={FaInfo}
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
zIndex={4}
|
||||
width="220px"
|
||||
backgroundColor={darkerBgColor}
|
||||
>
|
||||
<PopoverBody textAlign="center" whiteSpace="pre-wrap">
|
||||
{build.description}
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : (
|
||||
<Box />
|
||||
)}
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
isRound
|
||||
variantColor={themeColor}
|
||||
aria-label={apView ? "Show gear" : "Show ability point count"}
|
||||
fontSize="20px"
|
||||
icon={apView ? FaMinus : FaPlus}
|
||||
onClick={() => setApView(!apView)}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default BuildCard
|
||||
|
||||
26
frontend-react/src/components/builds/GearImage.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react"
|
||||
import { HeadGear, ClothingGear, ShoesGear } from "../../types"
|
||||
import english_internal from "../../utils/english_internal.json"
|
||||
import { Box } from "@chakra-ui/core"
|
||||
|
||||
interface GearImageProps {
|
||||
englishName?: HeadGear | ClothingGear | ShoesGear
|
||||
renderNullIfNoName: boolean
|
||||
}
|
||||
|
||||
const GearImage: React.FC<GearImageProps> = ({
|
||||
englishName,
|
||||
renderNullIfNoName,
|
||||
}) => {
|
||||
if (!englishName && renderNullIfNoName) return null
|
||||
if (!englishName) return <Box />
|
||||
return (
|
||||
<img
|
||||
alt={englishName}
|
||||
src={`https://raw.githubusercontent.com/Leanny/leanny.github.io/master/splat2/gear/${english_internal[englishName]}.png`}
|
||||
title={englishName}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default GearImage
|
||||
122
frontend-react/src/components/builds/ViewAP.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
// This while is bit of a mess from TS PoV - might be worth while to do it better later
|
||||
|
||||
import React from "react"
|
||||
import { Build, Ability } from "../../types"
|
||||
import { Box, Flex } from "@chakra-ui/core"
|
||||
import DividingBox from "../ui/DividingBox"
|
||||
import AbilityIcon from "./AbilityIcon"
|
||||
import useTheme from "../../hooks/useTheme"
|
||||
|
||||
const mainOnlyAbilities = [
|
||||
"CB",
|
||||
"LDE",
|
||||
"OG",
|
||||
"T",
|
||||
"H",
|
||||
"NS",
|
||||
"TI",
|
||||
"RP",
|
||||
"AD",
|
||||
"DR",
|
||||
"SJ",
|
||||
"OS",
|
||||
] as const
|
||||
|
||||
interface ViewAPProps {
|
||||
build: Build
|
||||
}
|
||||
|
||||
const ViewAP: React.FC<ViewAPProps> = ({ build }) => {
|
||||
const { grayWithShade } = useTheme()
|
||||
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 />
|
||||
AP
|
||||
</>
|
||||
) : 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
|
||||
56
frontend-react/src/components/builds/ViewGear.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from "react"
|
||||
import { Grid } from "@chakra-ui/core"
|
||||
import GearImage from "./GearImage"
|
||||
import { Build } from "../../types"
|
||||
import AbilityIcon from "./AbilityIcon"
|
||||
|
||||
interface ViewGearProps {
|
||||
build: Build
|
||||
}
|
||||
|
||||
const ViewGear: React.FC<ViewGearProps> = ({ build }) => {
|
||||
const noItems =
|
||||
!build.headgearItem && !build.clothingItem && !build.clothingItem
|
||||
return (
|
||||
<Grid
|
||||
gridTemplateColumns={`${noItems ? "" : "75px "} 60px 45px 45px 45px`}
|
||||
gridRowGap="10px"
|
||||
justifyItems="center"
|
||||
alignItems="center"
|
||||
mt={noItems ? "2" : "0"}
|
||||
>
|
||||
<GearImage
|
||||
englishName={build.headgearItem}
|
||||
renderNullIfNoName={noItems}
|
||||
/>
|
||||
{build.headgear.map((ability, index) => (
|
||||
<AbilityIcon
|
||||
key={index}
|
||||
ability={ability}
|
||||
size={index === 0 ? "MAIN" : "SUB"}
|
||||
/>
|
||||
))}
|
||||
<GearImage
|
||||
englishName={build.clothingItem}
|
||||
renderNullIfNoName={noItems}
|
||||
/>
|
||||
{build.clothing.map((ability, index) => (
|
||||
<AbilityIcon
|
||||
key={index}
|
||||
ability={ability}
|
||||
size={index === 0 ? "MAIN" : "SUB"}
|
||||
/>
|
||||
))}
|
||||
<GearImage englishName={build.shoesItem} renderNullIfNoName={noItems} />
|
||||
{build.shoes.map((ability, index) => (
|
||||
<AbilityIcon
|
||||
key={index}
|
||||
ability={ability}
|
||||
size={index === 0 ? "MAIN" : "SUB"}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
export default ViewGear
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from "react"
|
||||
import { Spinner, useColorMode, Box } from "@chakra-ui/core"
|
||||
import { Spinner, Box } from "@chakra-ui/core"
|
||||
import useTheme from "../../hooks/useTheme"
|
||||
|
||||
interface LoadingProps {}
|
||||
|
||||
const Loading: React.FC<LoadingProps> = () => {
|
||||
const { colorMode } = useColorMode()
|
||||
const color = { light: "orange.500", dark: "pink.200" }
|
||||
const { themeColorWithShade } = useTheme()
|
||||
return (
|
||||
<Box textAlign="center">
|
||||
<Spinner
|
||||
color={color[colorMode]}
|
||||
color={themeColorWithShade}
|
||||
size="xl"
|
||||
thickness="4px"
|
||||
speed="0.65s"
|
||||
|
||||
@@ -12,7 +12,11 @@ interface WeaponImageProps {
|
||||
const WeaponImage: React.FC<WeaponImageProps> = ({ englishName, size }) => {
|
||||
const dictToUse: any = size === "MEDIUM" ? wpnMedium : wpnSmall
|
||||
return (
|
||||
<img src={dictToUse[english_internal[englishName]]} alt={englishName} />
|
||||
<img
|
||||
src={dictToUse[english_internal[englishName]]}
|
||||
alt={englishName}
|
||||
title={englishName}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react"
|
||||
import React from "react"
|
||||
import { Box } from "@chakra-ui/core"
|
||||
|
||||
import { MenuBar } from "./MenuBar"
|
||||
@@ -9,15 +9,13 @@ import useTheme from "../../hooks/useTheme"
|
||||
const App: React.FC = () => {
|
||||
const { bgColor, textColor } = useTheme()
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.backgroundColor = bgColor
|
||||
}, [bgColor])
|
||||
|
||||
return (
|
||||
<Box
|
||||
marginLeft={[null, null, "250px"]}
|
||||
mt={["4em", null, "0"]}
|
||||
color={textColor}
|
||||
bg={bgColor}
|
||||
minH="100vh"
|
||||
>
|
||||
<SideNav />
|
||||
<MenuBar />
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Popover,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
useColorMode,
|
||||
useTheme as useChakraTheme,
|
||||
} from "@chakra-ui/core"
|
||||
import { CirclePicker, ColorResult } from "react-color"
|
||||
@@ -13,12 +12,10 @@ import { writeStorage } from "@rehooks/local-storage"
|
||||
import { themeColors as choices } from "../../utils/lists"
|
||||
import useTheme from "../../hooks/useTheme"
|
||||
|
||||
const bgColor = { light: "#eff0f3", dark: "#232946" } as const
|
||||
const size = "20px" as const
|
||||
|
||||
const ColorPicker: React.FC = () => {
|
||||
const { colorMode } = useColorMode()
|
||||
const { themeColorHex } = useTheme()
|
||||
const { themeColorHex, bgColor } = useTheme()
|
||||
const theme = useChakraTheme()
|
||||
|
||||
const hexCodes = choices.map(color =>
|
||||
@@ -46,11 +43,7 @@ const ColorPicker: React.FC = () => {
|
||||
ml="10px"
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
zIndex={4}
|
||||
width="220px"
|
||||
backgroundColor={bgColor[colorMode]}
|
||||
>
|
||||
<PopoverContent zIndex={4} width="220px" backgroundColor={bgColor}>
|
||||
<PopoverBody textAlign="center">
|
||||
<CirclePicker
|
||||
width="220px"
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { Suspense, lazy } from "react"
|
||||
import { Router } from "@reach/router"
|
||||
import Loading from "../common/Loading"
|
||||
|
||||
const UserPage = lazy(() => import("../user/UserPage"))
|
||||
|
||||
const Routes: React.FC = () => {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Router>
|
||||
<UserPage path="/u/:id" />
|
||||
</Router>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { Box, useColorMode } from "@chakra-ui/core"
|
||||
import { Box } from "@chakra-ui/core"
|
||||
import React from "react"
|
||||
import { SideNavContent } from "./SideNavContent"
|
||||
import useTheme from "../../hooks/useTheme"
|
||||
|
||||
const shadow = {
|
||||
light: "0px 1px 10px 8px rgba(0,0,0,0.15)",
|
||||
dark: "0px 1px 10px 8px rgba(255,255,255,0.04)",
|
||||
} as const
|
||||
|
||||
const SideNav = () => {
|
||||
const { colorMode } = useColorMode()
|
||||
const bgColor = { light: "#D6D7DA", dark: "#0A102D" }
|
||||
const shadow = {
|
||||
light: "0px 1px 10px 8px rgba(0,0,0,0.15)",
|
||||
dark: "0px 1px 10px 8px rgba(255,255,255,0.04)",
|
||||
}
|
||||
const { darkerBgColor, colorMode } = useTheme()
|
||||
return (
|
||||
<Box
|
||||
bg={bgColor[colorMode]}
|
||||
bg={darkerBgColor}
|
||||
boxShadow={shadow[colorMode]}
|
||||
height="100%"
|
||||
width="250px"
|
||||
|
||||
@@ -3,14 +3,16 @@ import { Box } from "@chakra-ui/core"
|
||||
import useTheme from "../../hooks/useTheme"
|
||||
|
||||
interface DividingBoxProps {
|
||||
children: JSX.Element | JSX.Element[]
|
||||
location: "top" | "left" | "bottom"
|
||||
children: JSX.Element | JSX.Element[] | string
|
||||
location: "top" | "left" | "bottom" | "right"
|
||||
margin?: string
|
||||
width?: string
|
||||
}
|
||||
|
||||
const DividingBox: React.FC<DividingBoxProps> = ({
|
||||
children,
|
||||
location,
|
||||
width = undefined,
|
||||
margin = "0.4em",
|
||||
}) => {
|
||||
const { borderStyle } = useTheme()
|
||||
@@ -22,6 +24,9 @@ const DividingBox: React.FC<DividingBoxProps> = ({
|
||||
mt={location === "top" ? margin : undefined}
|
||||
borderBottom={location === "bottom" ? borderStyle : undefined}
|
||||
mb={location === "bottom" ? margin : undefined}
|
||||
borderRight={location === "right" ? borderStyle : undefined}
|
||||
mr={location === "right" ? margin : undefined}
|
||||
width={width}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { FaUserAlt, FaTwitter, FaTwitch, FaGamepad } from "react-icons/fa"
|
||||
|
||||
import UserAvatar from "../common/UserAvatar"
|
||||
import { User } from "../../types"
|
||||
import DividingBox from "../ui/DividingBox"
|
||||
import Flag from "../common/Flag"
|
||||
import { countries } from "../../utils/lists"
|
||||
|
||||
@@ -22,48 +21,57 @@ interface AvatarWithInfoProps {
|
||||
|
||||
const AvatarWithInfo: React.FC<AvatarWithInfoProps> = ({ user }) => {
|
||||
return (
|
||||
<Flex justifyContent="center" flexWrap="wrap" alignItems="center">
|
||||
<Flex
|
||||
justifyContent="center"
|
||||
flexWrap="wrap"
|
||||
alignItems="center"
|
||||
flexDirection="column"
|
||||
>
|
||||
<UserAvatar
|
||||
name={user.username}
|
||||
twitterName={user.twitter_name}
|
||||
size="2xl"
|
||||
/>
|
||||
<DividingBox location="left">
|
||||
<List spacing={2} mx="0.5em" fontWeight="light">
|
||||
<List
|
||||
spacing={2}
|
||||
mt="1em"
|
||||
mx="0.5em"
|
||||
fontWeight="light"
|
||||
textAlign="center"
|
||||
>
|
||||
<ListItem>
|
||||
<ListIcon icon={FaUserAlt} />
|
||||
{user.username}#{user.discriminator}
|
||||
</ListItem>
|
||||
{user.country && (
|
||||
<ListItem>
|
||||
<ListIcon icon={FaUserAlt} />
|
||||
{user.username}#{user.discriminator}
|
||||
<Flag code={user.country} />
|
||||
{countries.find(obj => obj.code === user.country)?.name}
|
||||
</ListItem>
|
||||
{user.country && (
|
||||
<ListItem>
|
||||
<Flag code={user.country} />
|
||||
{countries.find(obj => obj.code === user.country)?.name}
|
||||
</ListItem>
|
||||
)}
|
||||
{user.twitter_name && (
|
||||
<ListItem>
|
||||
<ListIcon icon={FaTwitter} />
|
||||
<a href={`https://twitter.com/${user.twitter_name}`}>
|
||||
{user.twitter_name}
|
||||
</a>
|
||||
</ListItem>
|
||||
)}
|
||||
{user.twitch_name && (
|
||||
<ListItem>
|
||||
<ListIcon icon={FaTwitch} />
|
||||
<a href={`https://www.twitch.tv/${user.twitch_name}`}>
|
||||
{user.twitch_name}
|
||||
</a>
|
||||
</ListItem>
|
||||
)}
|
||||
{user.sens?.stick && (
|
||||
<ListItem>
|
||||
<ListIcon icon={FaGamepad} />
|
||||
{getSensString(user.sens.motion, user.sens.stick)}
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</DividingBox>
|
||||
)}
|
||||
{user.twitter_name && (
|
||||
<ListItem>
|
||||
<ListIcon icon={FaTwitter} />
|
||||
<a href={`https://twitter.com/${user.twitter_name}`}>
|
||||
{user.twitter_name}
|
||||
</a>
|
||||
</ListItem>
|
||||
)}
|
||||
{user.twitch_name && (
|
||||
<ListItem>
|
||||
<ListIcon icon={FaTwitch} />
|
||||
<a href={`https://www.twitch.tv/${user.twitch_name}`}>
|
||||
{user.twitch_name}
|
||||
</a>
|
||||
</ListItem>
|
||||
)}
|
||||
{user.sens?.stick && (
|
||||
<ListItem>
|
||||
<ListIcon icon={FaGamepad} />
|
||||
{getSensString(user.sens.motion, user.sens.stick)}
|
||||
</ListItem>
|
||||
)}
|
||||
</List>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
30
frontend-react/src/components/user/BuildTab.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React from "react"
|
||||
import { Build } from "../../types"
|
||||
import { Flex, Box } from "@chakra-ui/core"
|
||||
import BuildCard from "../builds/BuildCard"
|
||||
import useLocalStorage from "@rehooks/local-storage"
|
||||
|
||||
interface BuildTabProps {
|
||||
builds: Build[]
|
||||
canModifyBuilds: boolean
|
||||
}
|
||||
|
||||
const BuildTab: React.FC<BuildTabProps> = ({ builds, canModifyBuilds }) => {
|
||||
const [APView] = useLocalStorage<boolean>("prefersAPView")
|
||||
return (
|
||||
<>
|
||||
<Flex flexWrap="wrap" justifyContent="center">
|
||||
{builds.map(build => (
|
||||
<Box key={build.id} p="0.2em">
|
||||
<BuildCard
|
||||
build={build}
|
||||
defaultToAPView={APView !== null ? APView : false}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default BuildTab
|
||||
@@ -21,6 +21,7 @@ import { Box, Tabs, TabList, Tab, TabPanels, TabPanel } from "@chakra-ui/core"
|
||||
import useTheme from "../../hooks/useTheme"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
import { SEARCH_FOR_BUILDS } from "../../graphql/queries/searchForBuilds"
|
||||
import BuildTab from "./BuildTab"
|
||||
|
||||
interface Tab {
|
||||
id: number
|
||||
@@ -67,14 +68,17 @@ const UserPage: React.FC<RouteComponentProps & UserPageProps> = ({ id }) => {
|
||||
|
||||
const tabs = [] as Tab[]
|
||||
|
||||
if (builds.length > 0) {
|
||||
if (builds.length > 0 || userLean?.discord_id === user.discord_id) {
|
||||
tabs.push({
|
||||
id: 1,
|
||||
icon: FaTshirt,
|
||||
title: "Builds",
|
||||
content: (
|
||||
<TabPanel key={1}>
|
||||
<p>hi</p>
|
||||
<BuildTab
|
||||
builds={builds}
|
||||
canModifyBuilds={userLean?.discord_id === user.discord_id}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
})
|
||||
@@ -100,7 +104,7 @@ const UserPage: React.FC<RouteComponentProps & UserPageProps> = ({ id }) => {
|
||||
</Helmet>
|
||||
<AvatarWithInfo user={user} />
|
||||
{user.weapons && user.weapons.length > 0 && (
|
||||
<Box textAlign="center" mt="2em">
|
||||
<Box textAlign="center" mt="1em">
|
||||
<WeaponPool weapons={user.weapons} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from "react"
|
||||
import { Weapon } from "../../types"
|
||||
import { Box, Flex, useColorMode } from "@chakra-ui/core"
|
||||
import { Box, Flex } from "@chakra-ui/core"
|
||||
import WeaponImage from "../common/WeaponImage"
|
||||
import useTheme from "../../hooks/useTheme"
|
||||
|
||||
interface WeaponPoolProps {
|
||||
weapons: Weapon[]
|
||||
@@ -13,7 +14,7 @@ const styles = {
|
||||
} as const
|
||||
|
||||
const WeaponPool: React.FC<WeaponPoolProps> = ({ weapons }) => {
|
||||
const { colorMode } = useColorMode()
|
||||
const { colorMode, grayWithShade } = useTheme()
|
||||
const borderStyle: string = styles[colorMode]
|
||||
|
||||
return (
|
||||
@@ -28,12 +29,11 @@ const WeaponPool: React.FC<WeaponPoolProps> = ({ weapons }) => {
|
||||
>
|
||||
<Box
|
||||
as="legend"
|
||||
color="gray.500"
|
||||
color={grayWithShade}
|
||||
fontWeight="semibold"
|
||||
letterSpacing="wide"
|
||||
fontSize="xs"
|
||||
textTransform="uppercase"
|
||||
textAlign="left"
|
||||
>
|
||||
Weapon pool
|
||||
</Box>
|
||||
|
||||
@@ -3,11 +3,14 @@ import { useLocalStorage } from "@rehooks/local-storage"
|
||||
import { ThemeColor } from "../types"
|
||||
|
||||
interface Theme {
|
||||
colorMode: "light" | "dark"
|
||||
bgColor: "#eff0f3" | "#232946"
|
||||
darkerBgColor: "#D6D7DA" | "#0A102D"
|
||||
textColor: "#0d0d0d" | "#fffffe"
|
||||
borderStyle:
|
||||
| "1px solid rgba(0, 0, 0, .2)"
|
||||
| "1px solid rgba(255, 255, 255, .2)"
|
||||
grayWithShade: "gray.300" | "gray.600"
|
||||
themeColorWithShade: string
|
||||
themeColorHex: string
|
||||
themeColor: ThemeColor
|
||||
@@ -18,7 +21,9 @@ function useTheme(): Theme {
|
||||
const chakraTheme = useChakraTheme()
|
||||
const light = colorMode === "light"
|
||||
const bgColor = light ? "#eff0f3" : "#232946"
|
||||
const darkerBgColor = light ? "#D6D7DA" : "#0A102D"
|
||||
const textColor = light ? "#0d0d0d" : "#fffffe"
|
||||
const grayWithShade = light ? "gray.600" : "gray.300"
|
||||
const borderStyle = light
|
||||
? "1px solid rgba(0, 0, 0, .2)"
|
||||
: "1px solid rgba(255, 255, 255, .2)"
|
||||
@@ -35,10 +40,13 @@ function useTheme(): Theme {
|
||||
const themeColorHex = chakraTheme.colors[themeColor]["500"]
|
||||
|
||||
return {
|
||||
colorMode,
|
||||
bgColor,
|
||||
darkerBgColor,
|
||||
textColor,
|
||||
borderStyle,
|
||||
themeColorWithShade,
|
||||
grayWithShade,
|
||||
themeColorHex,
|
||||
themeColor,
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { ThemeProvider, ColorModeProvider, CSSReset } from "@chakra-ui/core"
|
||||
import { ApolloProvider } from "@apollo/react-hooks"
|
||||
import { HelmetProvider } from "react-helmet-async"
|
||||
import ApolloClient from "apollo-boost"
|
||||
import "./index.css"
|
||||
import * as serviceWorker from "./serviceWorker"
|
||||
|
||||
const client = new ApolloClient({
|
||||
|
||||
5
frontend-react/src/react-app-env.d.ts
vendored
@@ -1 +1,6 @@
|
||||
/// <reference types="react-scripts" />
|
||||
|
||||
declare module "*.png" {
|
||||
const value: any
|
||||
export = value
|
||||
}
|
||||
|
||||
@@ -21,10 +21,17 @@ type ElementType<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<
|
||||
export type Weapon = ElementType<typeof weapons>
|
||||
export type CountryCode = ElementType<typeof countryCodes>
|
||||
export type ThemeColor = ElementType<typeof themeColors>
|
||||
|
||||
export type HeadOnlyAbility = ElementType<typeof headOnlyAbilities>
|
||||
export type ClothingOnlyAbility = ElementType<typeof clothingOnlyAbilities>
|
||||
export type ShoesOnlyAbility = ElementType<typeof shoesOnlyAbilities>
|
||||
export type StackableAbility = ElementType<typeof stackableAbilities>
|
||||
export type Ability =
|
||||
| HeadOnlyAbility
|
||||
| ClothingOnlyAbility
|
||||
| ShoesOnlyAbility
|
||||
| StackableAbility
|
||||
|
||||
export type HeadGear = ElementType<typeof headGearEnglish>
|
||||
export type ClothingGear = ElementType<typeof clothingGearEnglish>
|
||||
export type ShoesGear = ElementType<typeof shoesGearEnglish>
|
||||
|
||||
3
index.js
@@ -67,8 +67,9 @@ passport.deserializeUser(function(discord_id, done) {
|
||||
})
|
||||
|
||||
console.log("connecting to MongoDB")
|
||||
const dbName =
|
||||
let dbName =
|
||||
process.env.NODE_ENV === "development" ? "development" : "production"
|
||||
if (process.env.USE_PRODUCTION_DB) dbName = "production"
|
||||
|
||||
mongoose
|
||||
.connect(process.env.MONGODB_URI, {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "cross-env LOGGED_IN=TRUE node index.js",
|
||||
"withprod": "cross-env LOGGED_IN=TRUE USE_PRODUCTION_DB=TRUE node index.js",
|
||||
"loggedout": "node index.js",
|
||||
"watch": "cross-env LOGGED_IN=TRUE nodemon index.js",
|
||||
"build": "cd frontend-react && npm install && npm run build && mv build ../build",
|
||||
|
||||