Add prototype supply event page

This commit is contained in:
Sakura Knoll
2021-12-27 09:06:00 +09:00 Unverified
parent c960ab72cc
commit 9cd2f1a5ce
29 changed files with 4325 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
/** @jsxImportSource theme-ui */
import { Box } from '@theme-ui/components'
import Image from 'next/image'
interface SquareImageBoxProps {
size: number | string
src: string
alt?: string
m?: number | string
mt?: number | string
mb?: number | string
ml?: number | string
mr?: number | string
mx?: number | string
my?: number | string
}
const SquareImageBox = ({
size,
src,
alt,
m,
mx,
my,
mt,
mb,
ml,
mr,
}: SquareImageBoxProps) => {
return (
<Box
m={m}
mx={mx}
my={my}
mt={mt}
mb={mb}
ml={ml}
mr={mr}
sx={{
position: 'relative',
overflow: 'hidden',
width: size,
height: size,
borderRadius: 4,
flexShrink: 0,
}}
>
<Image alt={alt} layout='fill' objectFit='cover' src={src} />
</Box>
)
}
export default SquareImageBox
+78
View File
@@ -0,0 +1,78 @@
import { readdirSync, readJsonFileSync } from '../../lib/data'
export interface SupplyEvent {
id: string
verified: boolean
track: number
name: string
type: string
token: string
featured: {
type: string
id: string
}[]
extra: string[]
version: number
duration: [string, string]
guarantees: {
count: number
recurring: boolean
rewards: { type: string; id: string; amount: number | [number, number] }[]
}[]
mileages: {
count: number
type: string
id: string
amount: number
}[]
drops: {
type: string
id: string
rate: number
amount: number | [number, number]
}[]
}
const supplyEventFileNameList = readdirSync('supply-events/global-americas')
const supplyEventFileList = supplyEventFileNameList
.map((fileName) => {
const filePathname = 'supply-events/global-americas/' + fileName
const data = readJsonFileSync(filePathname)
return { ...data, id: fileName.replace(/\.json/, '') } as SupplyEvent
})
.sort((a, b) => {
return -a.id.localeCompare(b.id)
})
const supplyEventMap = supplyEventFileList.reduce((map, stigmata) => {
map.set(stigmata.id, stigmata)
return map
}, new Map<string, SupplyEvent>())
const versionSupplyEventListMap = supplyEventFileList.reduce(
(map, supplyEvent) => {
let supplyEvents = map.get(supplyEvent.version)
if (supplyEvents == null) {
supplyEvents = []
map.set(supplyEvent.version, supplyEvents)
}
supplyEvents.push(supplyEvent)
return map
},
new Map<number, SupplyEvent[]>()
)
export function listSupplyEvents() {
return supplyEventFileList
}
export function getSupplyEventById(id: string) {
return supplyEventMap.get(id)
}
export function getSupplyEventListByVersion(version: number) {
return versionSupplyEventListMap.get(version) || []
}
+420
View File
@@ -0,0 +1,420 @@
/** @jsxImportSource theme-ui */
import { Text, Box, Heading, Link, Flex } from '@theme-ui/components'
import NextLink from 'next/link'
import Breadcrumb from '../../../components/organisms/Breadcrumb'
import Honkai3rdNavigator from '../../../components/organisms/Honkai3rdNavigator'
import { pick, times } from 'ramda'
import {
listSupplyEvents,
SupplyEvent,
} from '../../../data/honkai3rd/supply-events'
import { getStigmataById, StigmataData } from '../../../data/honkai3rd/stigmata'
import { getWeaponById, WeaponData } from '../../../data/honkai3rd/weapons'
import {
BattlesuitData,
getBattlesuitById,
} from '../../../data/honkai3rd/battlesuits'
import { ElfData, getElfById } from '../../../data/honkai3rd/elfs'
import {
format as formatDate,
differenceInCalendarDays,
differenceInCalendarWeeks,
format,
} from 'date-fns'
import SquareImageBox from '../../../components/atoms/SquareImageBox'
import { useMemo } from 'react'
type ObjectMap<T> = { [key: string]: T }
type StigmataObjectMap = ObjectMap<Pick<StigmataData, 'id' | 'name'>>
type WeaponObjectMap = ObjectMap<Pick<WeaponData, 'id' | 'name'>>
type BattlesuitObjectMap = ObjectMap<Pick<BattlesuitData, 'id' | 'name'>>
type ElfObjectMap = ObjectMap<Pick<ElfData, 'id' | 'name'>>
interface SupplyEventListPageProps {
supplyEventList: Pick<
SupplyEvent,
'id' | 'version' | 'name' | 'featured' | 'duration' | 'track' | 'verified'
>[]
stigmataObjectMap: StigmataObjectMap
weaponObjectMap: WeaponObjectMap
battlesuitObjectMap: BattlesuitObjectMap
elfObjectMap: ElfObjectMap
}
const SupplyEventListPage = ({
supplyEventList,
battlesuitObjectMap,
weaponObjectMap,
stigmataObjectMap,
elfObjectMap,
}: SupplyEventListPageProps) => {
const startDate = new Date('2021-12-02')
const endDate = new Date('2022-01-13')
const todayDate = new Date(formatDate(new Date(), 'yyyy-MM-dd'))
const totalDays = differenceInCalendarDays(endDate, startDate)
const totalWeeks = differenceInCalendarWeeks(endDate, startDate)
const totalRow = useMemo(() => {
return supplyEventList.reduce((max, supplyEvent) => {
return supplyEvent.track > max ? supplyEvent.track : max
}, 1)
}, [])
return (
<Box>
<Honkai3rdNavigator />
<Box p={3}>
<Breadcrumb
items={[
{ href: '/honkai3rd', label: 'Honkai 3rd' },
{ href: '/honkai3rd/supply-events', label: 'Supply Events' },
]}
/>
<Heading as='h1' mb={3}>
Supply Events
</Heading>
<Box mb={3} sx={{ width: totalWeeks * 280 }}>
<Flex
sx={{
marginLeft: -20,
}}
>
{times((index) => {
const weekNumber = index + 1
return (
<Box
key={`week-${weekNumber}`}
sx={{
width: 280,
textAlign: 'center',
fontWeight: 700,
}}
>
Week {weekNumber}
</Box>
)
}, totalWeeks)}
</Flex>
<Box
sx={{
position: 'relative',
height: totalRow * 50 + 40,
borderBottomStyle: 'solid',
borderBottomWidth: 1,
borderBottomColor: 'gray.5',
borderTopStyle: 'solid',
borderTopWidth: 1,
borderTopColor: 'gray.5',
}}
>
{times((index) => {
return (
<Box
sx={{
position: 'absolute',
height: totalRow * 50 + 40,
borderRightStyle: 'solid',
borderRightWidth: 1,
borderRightColor: index % 7 === 6 ? 'gray.5' : 'gray.3',
left: (index + 1) * 40 - 1 - 20,
}}
/>
)
}, totalDays)}
<Box
sx={{
boxSizing: 'border-box',
position: 'absolute',
width: 40,
height: totalRow * 50 + 40,
backgroundColor: 'blue.5',
borderStyle: 'solid',
borderColor: 'blue.7',
opacity: 0.2,
zIndex: 20,
borderRadius: 4,
left: differenceInCalendarDays(todayDate, startDate) * 40 - 20,
pointerEvents: 'none',
}}
/>
<Text
sx={{
position: 'absolute',
color: 'blue.7',
transform: 'rotate(90deg)',
zIndex: 21,
top: 150,
left:
differenceInCalendarDays(new Date(todayDate), startDate) *
40 -
150,
width: 300,
pointerEvents: 'none',
}}
>
Today ({format(todayDate, 'P')})
</Text>
{supplyEventList
.slice()
.reverse()
.map((supplyEvent) => {
const offset =
differenceInCalendarDays(
new Date(supplyEvent.duration[0]),
startDate
) * 40
const length =
differenceInCalendarDays(
new Date(supplyEvent.duration[1]),
new Date(supplyEvent.duration[0])
) * 40
const featuredIconProps = getIconPropsOfItem(
supplyEvent.featured[0],
{
battlesuitObjectMap,
weaponObjectMap,
stigmataObjectMap,
elfObjectMap,
}
)
return (
<Flex
key={supplyEvent.id}
py={2}
px={3}
sx={{
boxSizing: 'border-box',
position: 'absolute',
borderColor: 'gray.5',
borderWidth: 1,
borderStyle: 'solid',
backgroundColor: 'white',
width: length,
left: offset,
top: (supplyEvent.track - 1) * 50 + 20,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
height: 40,
borderRadius: 40,
fontWeight: 700,
zIndex: 10,
'&:hover': {
minWidth: length,
width: 'inherit',
zIndex: 25,
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
transition: 'box-shadow 200ms ease-in-out',
},
}}
>
{featuredIconProps != null && (
<SquareImageBox
size={20}
src={featuredIconProps.src}
alt={featuredIconProps.alt}
mr={2}
/>
)}
<Text
sx={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{supplyEvent.name}
{!supplyEvent.verified && ' (❓)'}
</Text>
</Flex>
)
})}
</Box>
</Box>
<Box>
{supplyEventList.map((supplyEvent) => {
return (
<Box key={supplyEvent.id}>
<Box mb={3}>
<Heading>
<NextLink
href={`/honkai3rd/supply-events/${supplyEvent.id}`}
key={supplyEvent.id}
passHref={true}
>
<Link>
[{supplyEvent.version}] {supplyEvent.name} (
{supplyEvent.duration.join(' ~ ')})
</Link>
</NextLink>
</Heading>
<Flex>
{supplyEvent.featured.map((item) => {
const props = getIconPropsOfItem(item, {
battlesuitObjectMap,
weaponObjectMap,
stigmataObjectMap,
elfObjectMap,
})
if (props == null) {
return (
<Text>
{item.type} : {item.id}
</Text>
)
}
return (
<SquareImageBox
key={`${item.type}/${item.id}`}
mr={2}
size={50}
src={props.src}
alt={props.alt}
/>
)
})}
</Flex>
</Box>
</Box>
)
})}
</Box>
</Box>
</Box>
)
}
export default SupplyEventListPage
export async function getStaticProps() {
const supplyEventList = listSupplyEvents()
const stigmataObjectMap: {
[key: string]: Pick<StigmataData, 'id' | 'name'>
} = {}
const weaponObjectMap: {
[key: string]: Pick<WeaponData, 'id' | 'name'>
} = {}
const battlesuitObjectMap: {
[key: string]: Pick<BattlesuitData, 'id' | 'name'>
} = {}
const elfObjectMap: {
[key: string]: Pick<ElfData, 'id' | 'name'>
} = {}
supplyEventList.forEach((supplyEvent) => {
supplyEvent.featured.forEach((item) => {
switch (item.type) {
case 'battlesuit':
const battlesuit = getBattlesuitById(item.id)
if (battlesuit != null) {
battlesuitObjectMap[battlesuit.id] = pick(
['id', 'name'],
battlesuit
)
}
break
case 'weapon':
const weapon = getWeaponById(item.id)
if (weapon != null) {
weaponObjectMap[weapon.id] = pick(['id', 'name'], weapon)
}
break
case 'stigmata':
const stigmata = getStigmataById(item.id)
if (stigmata != null) {
stigmataObjectMap[stigmata.id] = pick(['id', 'name'], stigmata)
}
break
case 'elf':
const elf = getElfById(item.id)
if (elf != null) {
elfObjectMap[elf.id] = pick(['id', 'name'], elf)
}
break
}
})
})
return {
props: {
supplyEventList: supplyEventList.map((supplyEvent) => {
return pick(
[
'id',
'version',
'name',
'featured',
'duration',
'track',
'verified',
],
supplyEvent
)
}),
battlesuitObjectMap,
weaponObjectMap,
stigmataObjectMap,
elfObjectMap,
},
revalidate: true,
}
}
function getIconPropsOfItem(
item: { type: string; id: string },
{
battlesuitObjectMap,
weaponObjectMap,
stigmataObjectMap,
elfObjectMap,
}: {
stigmataObjectMap: StigmataObjectMap
weaponObjectMap: WeaponObjectMap
battlesuitObjectMap: BattlesuitObjectMap
elfObjectMap: ElfObjectMap
}
): { src: string; alt: string } | null {
switch (item.type) {
case 'battlesuit':
const battlesuit = battlesuitObjectMap[item.id]
if (battlesuit != null) {
return {
src: `/assets/honkai3rd/battlesuits/portrait-${battlesuit.id}.png`,
alt: battlesuit.name,
}
}
case 'weapon':
const weapon = weaponObjectMap[item.id]
if (weapon != null) {
return {
src: `/assets/honkai3rd/weapons/${weapon.id}.png`,
alt: weapon.name,
}
}
case 'stigmata':
const stigmata = stigmataObjectMap[item.id]
if (stigmata != null) {
return {
src: `/assets/honkai3rd/stigmata/icon-${stigmata.id}.png`,
alt: stigmata.name,
}
}
case 'elf':
const elf = elfObjectMap[item.id]
if (elf != null) {
return {
src: `/assets/honkai3rd/elfs/icon-${elf.id}.png`,
alt: elf.name,
}
}
}
return null
}