Replace version page with supply event page
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
https://honkaiimpact3.mihoyo.com/global/en-us/news/17390
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"version": "5.3",
|
||||||
|
"name": "Post Dusk Odyssey",
|
||||||
|
"duration": ["2021-12-02", "2022-01-13"],
|
||||||
|
"newBattlesuits": ["sns"],
|
||||||
|
"newWeapons": ["hekate-s-sombre", "miracle-kissy-pillow"],
|
||||||
|
"verified": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"version": "5.4",
|
||||||
|
"name": "Silverwing Beyond",
|
||||||
|
"duration": ["2022-01-13"],
|
||||||
|
"newBattlesuits": [],
|
||||||
|
"newWeapons": [],
|
||||||
|
"verified": false
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import { Box, Flex, Text } from '@theme-ui/components'
|
||||||
|
import {
|
||||||
|
differenceInCalendarDays,
|
||||||
|
differenceInCalendarWeeks,
|
||||||
|
format,
|
||||||
|
} from 'date-fns'
|
||||||
|
import { times } from 'ramda'
|
||||||
|
import { useMemo, MouseEventHandler } from 'react'
|
||||||
|
|
||||||
|
export interface GanttChartItem {
|
||||||
|
id: string
|
||||||
|
row: number
|
||||||
|
duration: [string, string]
|
||||||
|
label: React.ReactNode
|
||||||
|
onClick?: MouseEventHandler<HTMLDivElement>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GanttChartProps {
|
||||||
|
startDate: string
|
||||||
|
endDate: string
|
||||||
|
today: string
|
||||||
|
items: GanttChartItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const GanttChart = ({
|
||||||
|
startDate: startDateString,
|
||||||
|
endDate: endDateString,
|
||||||
|
today: todayDateString,
|
||||||
|
items,
|
||||||
|
}: GanttChartProps) => {
|
||||||
|
const startDate = new Date(startDateString)
|
||||||
|
const endDate = new Date(endDateString)
|
||||||
|
const todayDate = new Date(todayDateString)
|
||||||
|
|
||||||
|
const totalDays = differenceInCalendarDays(endDate, startDate)
|
||||||
|
const totalWeeks = differenceInCalendarWeeks(endDate, startDate)
|
||||||
|
|
||||||
|
const totalRow = useMemo(() => {
|
||||||
|
return items.reduce((max, item) => {
|
||||||
|
return item.row > max ? item.row : max
|
||||||
|
}, 1)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{items.map((item) => {
|
||||||
|
const offset =
|
||||||
|
differenceInCalendarDays(new Date(item.duration[0]), startDate) * 40
|
||||||
|
const length =
|
||||||
|
differenceInCalendarDays(
|
||||||
|
new Date(item.duration[1]),
|
||||||
|
new Date(item.duration[0])
|
||||||
|
) * 40
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
key={item.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: (item.row - 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',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GanttChart
|
||||||
@@ -16,6 +16,11 @@ const Honkai3rdNavigator = () => {
|
|||||||
</NextLink>
|
</NextLink>
|
||||||
</Heading>
|
</Heading>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<NextLink href='/honkai3rd/versions' passHref>
|
||||||
|
<NavLink mr={3} sx={{ fontFamily: 'monospace' }}>
|
||||||
|
Versions
|
||||||
|
</NavLink>
|
||||||
|
</NextLink>
|
||||||
<Flex sx={{ height: 40, alignItems: 'center' }}>
|
<Flex sx={{ height: 40, alignItems: 'center' }}>
|
||||||
<NextLink href='/honkai3rd/battlesuits' passHref>
|
<NextLink href='/honkai3rd/battlesuits' passHref>
|
||||||
<NavLink mr={3} sx={{ fontFamily: 'monospace' }}>
|
<NavLink mr={3} sx={{ fontFamily: 'monospace' }}>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { readdirSync, readJsonFileSync } from '../../lib/data'
|
import { readdirSync, readJsonFileSync } from '../../lib/data'
|
||||||
|
|
||||||
export interface SupplyEvent {
|
export interface SupplyEventData {
|
||||||
id: string
|
id: string
|
||||||
verified: boolean
|
verified: boolean
|
||||||
track: number
|
track: number
|
||||||
@@ -33,46 +33,20 @@ export interface SupplyEvent {
|
|||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const supplyEventFileNameList = readdirSync('supply-events/global-americas')
|
export function listSupplyEventsByVersion(version: string) {
|
||||||
const supplyEventFileList = supplyEventFileNameList
|
const supplyEventsDirectoryPathname = `versions/${version}/supply-events`
|
||||||
.map((fileName) => {
|
|
||||||
const filePathname = 'supply-events/global-americas/' + fileName
|
|
||||||
const data = readJsonFileSync(filePathname)
|
|
||||||
|
|
||||||
return { ...data, id: fileName.replace(/\.json/, '') } as SupplyEvent
|
const supplyEventFileNameList = readdirSync(supplyEventsDirectoryPathname)
|
||||||
})
|
const supplyEventList = supplyEventFileNameList
|
||||||
.sort((a, b) => {
|
.map((fileName) => {
|
||||||
return -a.id.localeCompare(b.id)
|
const filePathname = `${supplyEventsDirectoryPathname}/` + fileName
|
||||||
})
|
const data = readJsonFileSync(filePathname)
|
||||||
|
|
||||||
const supplyEventMap = supplyEventFileList.reduce((map, stigmata) => {
|
return { ...data, id: fileName.replace(/\.json/, '') } as SupplyEventData
|
||||||
map.set(stigmata.id, stigmata)
|
})
|
||||||
return map
|
.sort((a, b) => {
|
||||||
}, new Map<string, SupplyEvent>())
|
return -a.id.localeCompare(b.id)
|
||||||
|
})
|
||||||
|
|
||||||
const versionSupplyEventListMap = supplyEventFileList.reduce(
|
return supplyEventList
|
||||||
(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) || []
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { format } from 'date-fns'
|
||||||
|
import { readdirSync, readFileSync, readJsonFileSync } from '../../lib/data'
|
||||||
|
import { compareVersion } from '../../lib/string'
|
||||||
|
|
||||||
|
export interface VersionData {
|
||||||
|
version: string
|
||||||
|
name: string
|
||||||
|
duration: [string, string]
|
||||||
|
verified: boolean
|
||||||
|
newBattlesuits: string[]
|
||||||
|
newWeapons: string[]
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionDirectoryList = readdirSync('versions')
|
||||||
|
|
||||||
|
const versionDataList = versionDirectoryList
|
||||||
|
.map((directoryName) => {
|
||||||
|
const directoryPathname = 'versions/' + directoryName
|
||||||
|
const data: VersionData = {
|
||||||
|
version: directoryName,
|
||||||
|
...readJsonFileSync(directoryPathname + '/version-data.json'),
|
||||||
|
description: readFileSync(
|
||||||
|
directoryPathname + '/description.md'
|
||||||
|
).toString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
return compareVersion(b.version, a.version)
|
||||||
|
})
|
||||||
|
|
||||||
|
export function listVersionData() {
|
||||||
|
return versionDataList
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVersion(version: number | string) {
|
||||||
|
return versionDataList.find((versionData) => {
|
||||||
|
return versionData.version.toString() === version.toString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCurrentVersion() {
|
||||||
|
const todayDateString = format(new Date(), 'yyyy-MM-dd')
|
||||||
|
|
||||||
|
return versionDataList.find((versionData) => {
|
||||||
|
const [startDateString] = versionData.duration
|
||||||
|
|
||||||
|
return startDateString.localeCompare(todayDateString) < 0
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export function isVersionString(version: string): boolean {
|
||||||
|
return /[0-9]+\.[0-9]+(\.[0-9]+)?/.test(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compareVersion(aVersion: string, bVersion: string): number {
|
||||||
|
const [aMajor, aMinor = '0', aPatch = '0'] = aVersion.split('.')
|
||||||
|
const [bMajor, bMinor = '0', bPatch = '0'] = bVersion.split('.')
|
||||||
|
|
||||||
|
let result = parseInt(aMajor, 10) - parseInt(bMajor, 10)
|
||||||
|
|
||||||
|
if (result !== 0) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
result = parseInt(aMinor, 10) - parseInt(bMinor, 10)
|
||||||
|
|
||||||
|
if (result !== 0) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(aPatch, 10) - parseInt(bPatch, 10)
|
||||||
|
}
|
||||||
@@ -1,420 +0,0 @@
|
|||||||
/** @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
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/** @jsxImportSource theme-ui */
|
||||||
|
import { NextPageContext } from 'next'
|
||||||
|
import {
|
||||||
|
getVersion,
|
||||||
|
listVersionData,
|
||||||
|
VersionData,
|
||||||
|
} from '../../../data/honkai3rd/versions'
|
||||||
|
|
||||||
|
interface VersionShowPageProps {
|
||||||
|
versionData: VersionData
|
||||||
|
}
|
||||||
|
|
||||||
|
const VersionShowPage = ({ versionData }: VersionShowPageProps) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{versionData.version} : {versionData.name}
|
||||||
|
<pre>
|
||||||
|
<code>{JSON.stringify(versionData, null, 2)}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VersionShowPage
|
||||||
|
|
||||||
|
export async function getStaticProps({
|
||||||
|
params,
|
||||||
|
}: NextPageContext & { params: { versionId: string } }) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
versionData: getVersion(params.versionId),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
return {
|
||||||
|
paths: listVersionData().map((versionData) => {
|
||||||
|
return {
|
||||||
|
params: {
|
||||||
|
versionId: versionData.version.toString(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
fallback: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
/** @jsxImportSource theme-ui */
|
||||||
|
import { Box, Flex, Heading, Link, Text } from '@theme-ui/components'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import NextLink from 'next/link'
|
||||||
|
import SquareImageBox from '../../../components/atoms/SquareImageBox'
|
||||||
|
import Breadcrumb from '../../../components/organisms/Breadcrumb'
|
||||||
|
import GanttChart from '../../../components/organisms/GanttChart'
|
||||||
|
import Honkai3rdNavigator from '../../../components/organisms/Honkai3rdNavigator'
|
||||||
|
import {
|
||||||
|
BattlesuitData,
|
||||||
|
getBattlesuitById,
|
||||||
|
} from '../../../data/honkai3rd/battlesuits'
|
||||||
|
import {
|
||||||
|
listSupplyEventsByVersion,
|
||||||
|
SupplyEventData as SupplyEventData,
|
||||||
|
} from '../../../data/honkai3rd/supply-events'
|
||||||
|
import {
|
||||||
|
getCurrentVersion,
|
||||||
|
listVersionData,
|
||||||
|
VersionData,
|
||||||
|
} from '../../../data/honkai3rd/versions'
|
||||||
|
import { getWeaponById, WeaponData } from '../../../data/honkai3rd/weapons'
|
||||||
|
|
||||||
|
interface VersionIndexPageProps {
|
||||||
|
versionDataList: VersionData[]
|
||||||
|
currentVersionData: VersionData
|
||||||
|
currentVersionNewBattlesuits: BattlesuitData[]
|
||||||
|
currentVersionNewWeapons: WeaponData[]
|
||||||
|
currentVersionSupplyEvents: SupplyEventData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const VersionIndexPage = ({
|
||||||
|
currentVersionData,
|
||||||
|
currentVersionNewBattlesuits,
|
||||||
|
versionDataList,
|
||||||
|
currentVersionNewWeapons,
|
||||||
|
currentVersionSupplyEvents,
|
||||||
|
}: VersionIndexPageProps) => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Honkai3rdNavigator />
|
||||||
|
|
||||||
|
<Box p={3}>
|
||||||
|
<Breadcrumb
|
||||||
|
items={[
|
||||||
|
{ href: '/honkai3rd', label: 'Honkai 3rd' },
|
||||||
|
{ href: '/honkai3rd/versions', label: 'Versions' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Box mb={3}>
|
||||||
|
<Heading as='h2' mb={4}>
|
||||||
|
v{currentVersionData.version} : {currentVersionData.name}
|
||||||
|
<br />
|
||||||
|
<small>(Current Version)</small>
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Heading as='h3'>New Battlesuits</Heading>
|
||||||
|
<Box mb={4}>
|
||||||
|
{currentVersionNewBattlesuits.map((battlesuit) => {
|
||||||
|
return (
|
||||||
|
<NextLink
|
||||||
|
href={`/honkai3rd/battlesuits/${battlesuit.id}`}
|
||||||
|
passHref
|
||||||
|
>
|
||||||
|
<Link>
|
||||||
|
<Flex sx={{ alignItems: 'center' }} mb={2}>
|
||||||
|
<SquareImageBox
|
||||||
|
size={40}
|
||||||
|
src={`/assets/honkai3rd/battlesuits/portrait-${battlesuit.id}.png`}
|
||||||
|
alt={`${battlesuit.name}`}
|
||||||
|
mr={2}
|
||||||
|
/>
|
||||||
|
<Text>{battlesuit.name}</Text>
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
</NextLink>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Heading as='h3'>New Weapons</Heading>
|
||||||
|
<Box mb={4}>
|
||||||
|
{currentVersionNewWeapons.map((weapon) => {
|
||||||
|
return (
|
||||||
|
<NextLink href={`/honkai3rd/weapons/${weapon.id}`} passHref>
|
||||||
|
<Link>
|
||||||
|
<Flex sx={{ alignItems: 'center' }} mb={2}>
|
||||||
|
<SquareImageBox
|
||||||
|
size={40}
|
||||||
|
src={`/assets/honkai3rd/weapons/${weapon.id}.png`}
|
||||||
|
alt={`${weapon.name}`}
|
||||||
|
mr={2}
|
||||||
|
/>
|
||||||
|
<Text>{weapon.name}</Text>
|
||||||
|
</Flex>
|
||||||
|
</Link>
|
||||||
|
</NextLink>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Heading as='h3'>Supply Events</Heading>
|
||||||
|
<Box mb={4}>
|
||||||
|
<GanttChart
|
||||||
|
items={currentVersionSupplyEvents.map((supplyEventData) => {
|
||||||
|
const imgSrc = getIconSrcFromItem(supplyEventData.featured[0])
|
||||||
|
return {
|
||||||
|
id: supplyEventData.id,
|
||||||
|
label: (
|
||||||
|
<Flex sx={{ alignItems: 'center' }}>
|
||||||
|
{imgSrc != null && (
|
||||||
|
<SquareImageBox
|
||||||
|
size={20}
|
||||||
|
src={imgSrc}
|
||||||
|
alt={supplyEventData.featured[0].id}
|
||||||
|
mr={1}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text>{supplyEventData.name}</Text>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
duration: supplyEventData.duration,
|
||||||
|
row: supplyEventData.track,
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
today={format(new Date(), 'yyyy-MM-dd')}
|
||||||
|
startDate={currentVersionData.duration[0]}
|
||||||
|
endDate={currentVersionData.duration[1]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<NextLink
|
||||||
|
href={`/honkai3rd/versions/${currentVersionData.version}`}
|
||||||
|
passHref
|
||||||
|
>
|
||||||
|
<Link>Learn more...</Link>
|
||||||
|
</NextLink>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Heading as='h2'>All Versions</Heading>
|
||||||
|
<Box>
|
||||||
|
{versionDataList.map((versionData) => {
|
||||||
|
return (
|
||||||
|
<Box key={versionData.version}>
|
||||||
|
<Heading as='h3'>
|
||||||
|
<NextLink
|
||||||
|
href={`/honkai3rd/versions/${versionData.version}`}
|
||||||
|
passHref
|
||||||
|
>
|
||||||
|
<Link>
|
||||||
|
{versionData.version} : {versionData.name} (
|
||||||
|
{format(new Date(versionData.duration[0]), 'yyyy/MM/dd')}-
|
||||||
|
{versionData.duration[1] != null
|
||||||
|
? format(
|
||||||
|
new Date(versionData.duration[1]),
|
||||||
|
'yyyy/MM/dd'
|
||||||
|
)
|
||||||
|
: ''}
|
||||||
|
)
|
||||||
|
</Link>
|
||||||
|
</NextLink>
|
||||||
|
</Heading>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticProps() {
|
||||||
|
const currentVersionData = getCurrentVersion()!
|
||||||
|
|
||||||
|
const currentVersionNewBattlesuits = currentVersionData.newBattlesuits.map(
|
||||||
|
(battlesuitId) => {
|
||||||
|
return getBattlesuitById(battlesuitId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const currentVersionNewWeapons = currentVersionData.newWeapons.map(
|
||||||
|
(weaponId) => {
|
||||||
|
return getWeaponById(weaponId)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const currentVersionSupplyEvents = listSupplyEventsByVersion(
|
||||||
|
currentVersionData.version
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
currentVersionData,
|
||||||
|
currentVersionNewBattlesuits,
|
||||||
|
currentVersionNewWeapons,
|
||||||
|
currentVersionSupplyEvents,
|
||||||
|
versionDataList: listVersionData(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VersionIndexPage
|
||||||
|
|
||||||
|
function getIconSrcFromItem(item: { type: string; id: string }): string | null {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'battlesuit':
|
||||||
|
return `/assets/honkai3rd/battlesuits/portrait-${item.id}.png`
|
||||||
|
case 'weapon':
|
||||||
|
return `/assets/honkai3rd/weapons/${item.id}.png`
|
||||||
|
case 'stigmata':
|
||||||
|
return `/assets/honkai3rd/stigmata/icon-${item.id}.png`
|
||||||
|
case 'elf':
|
||||||
|
return `/assets/honkai3rd/elfs/icon-${item.id}.png`
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user